Compare commits

..

No commits in common. "main" and "v2.4.1" have entirely different histories.
main ... v2.4.1

166 changed files with 34162 additions and 10472 deletions

View file

@ -3,12 +3,12 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space indent_style = space
indent_size = 2
insert_final_newline = true insert_final_newline = true
max_line_length = 100
trim_trailing_whitespace = true trim_trailing_whitespace = true
end_of_line = lf
[COMMIT_EDITMSG] [*.md]
max_line_length = 72 max_line_length = off
trim_trailing_whitespace = false

View file

@ -1,19 +0,0 @@
extends:
- remcohaszing
- remcohaszing/typechecking
rules:
no-restricted-globals: off
'@typescript-eslint/no-misused-promises': off
'@typescript-eslint/no-shadow': off
'@typescript-eslint/no-unnecessary-condition': off
import/extensions: off
import/no-extraneous-dependencies: off
import/no-unresolved: off
jsdoc/require-jsdoc: off
node/no-extraneous-import: off
node/no-unsupported-features/es-syntax: off
node/no-unsupported-features/node-builtins: off

View file

@ -1,66 +0,0 @@
name: ci
on:
pull_request:
push:
branches: [main]
tags: ['*']
jobs:
eslint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with: { node-version: 16 }
- run: npm ci
- run: npx eslint .
examples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with: { node-version: 16 }
- run: npm ci
- run: npm run prepack
- run: npm run build --workspaces
pack:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with: { node-version: 16 }
- run: npm ci
- run: npm pack
prettier:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with: { node-version: 16 }
- run: npm ci
- run: npx prettier --check .
tsc:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
with: { node-version: 16 }
- run: npm ci
- run: npx tsc
# release:
# runs-on: ubuntu-latest
# needs: [eslint, pack, prettier, tsc]
# if: startsWith(github.ref, 'refs/tags/')
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-node@v2
# with: { node-version: 16 }
# - run: npm ci
# - run: npm publish
# env:
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

10
.gitignore vendored
View file

@ -1,7 +1,5 @@
dist/ /node_modules/
node_modules/ /out/
/index.js /lib/
/yaml.worker.js
*.log *.log
*.map
*.tgz

View file

@ -1,3 +0,0 @@
#!/usr/bin/env sh
npx lint-staged

7
.npmignore Normal file
View file

@ -0,0 +1,7 @@
/.vscode/
/out/
/scripts/
/src/
/test/
/.gitignore
/.npmignore

1
.npmrc
View file

@ -1 +0,0 @@
lockfile-version = 3

1
.nvmrc
View file

@ -1 +0,0 @@
16

View file

@ -1,3 +0,0 @@
dist/
/index.js
/yaml.worker.js

View file

@ -1,3 +0,0 @@
proseWrap: always
singleQuote: true
trailingComma: all

18
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,18 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"name": "vscode-jest-tests",
"request": "launch",
"args": ["--runInBand"],
"cwd": "${workspaceFolder}",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
}
]
}

11
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,11 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.trimTrailingWhitespace": true,
"search.exclude": {
"**/node_modules": true,
"**/lib": true,
"**/out": true
},
"javascript.preferences.quoteStyle": "single",
"typescript.preferences.quoteStyle": "single"
}

View file

@ -1,36 +0,0 @@
# Contributing
## Prerequisites
The following are required to start working on this project:
- [Git](https://git-scm.com)
- [NodeJS](https://nodejs.org) 16 or higher
- [npm](https://github.com/npm/cli) 8.1.2 or higher
## Getting started
To get started with contributing, clone the repository and install its dependencies.
```sh
git clone https://github.com/remcohaszing/monaco-yaml
cd monaco-yaml
npm ci
```
## Building
To build the repository, run:
```sh
npm run prepack
```
## Running
To test it, run one of the
[examples](https://github.com/remcohaszing/monaco-yaml/tree/main/examples).
```sh
npm --workspace demo start
```

View file

@ -1,18 +1,21 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) Microsoft Corporation Copyright (c) Microsoft Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and Permission is hereby granted, free of charge, to any person obtaining a copy
associated documentation files (the "Software"), to deal in the Software without restriction, of this software and associated documentation files (the "Software"), to deal
including without limitation the rights to use, copy, modify, merge, publish, distribute, in the Software without restriction, including without limitation the rights
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
furnished to do so, subject to the following conditions: 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 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 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 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.

235
README.md
View file

@ -1,219 +1,42 @@
# Monaco YAML # Monaco YAML
[![ci workflow](https://github.com/remcohaszing/monaco-yaml/actions/workflows/ci.yaml/badge.svg)](https://github.com/remcohaszing/monaco-yaml/actions/workflows/ci.yaml) YAML language plugin for the Monaco Editor. It provides the following features when editing YAML files:
[![npm version](https://img.shields.io/npm/v/monaco-yaml)](https://www.npmjs.com/package/monaco-yaml) * Code completion, based on JSON schemas or by looking at similar objects in the same file
[![prettier code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io) * Hovers, based on JSON schemas
[![demo](https://img.shields.io/badge/demo-monaco--yaml.js.org-61ffcf.svg)](https://monaco-yaml.js.org) * Validation: Syntax errors and schema validation
[![netlify status](https://api.netlify.com/api/v1/badges/20b08937-99d0-4882-b9a3-d5f09ddd29b7/deploy-status)](https://app.netlify.com/sites/monaco-yaml/deploys) * Formatting
* Document Symbols
* Syntax highlighting
* Automatically load remote schema files (by enabling DiagnosticsOptions.enableSchemaRequest)
YAML language plugin for the Monaco Editor. It provides the following features when editing YAML Schemas can also be provided by configuration. See [here](https://github.com/Microsoft/monaco-json/blob/master/src/monaco.d.ts)
files: for the API that the JSON plugin offers to configure the JSON language support.
- Code completion, based on JSON schemas or by looking at similar objects in the same file ## Installing
- Hovers, based on JSON schemas
- Validation: Syntax errors and schema validation
- Formatting using Prettier
- Document Symbols
- Automatically load remote schema files (by enabling DiagnosticsOptions.enableSchemaRequest)
- Links from JSON references.
- Links and hover effects from YAML anchors.
Schemas can also be provided by configuration. See `yarn add monaco-yaml`
[here](https://github.com/remcohaszing/monaco-yaml/blob/main/index.d.ts) for the API that the plugin Both vs loader and ESM are supported.
offers to configure the YAML language support. See `examples` directory for esm and umd examples.
## Installation ## Development
```sh * `git clone https://github.com/pengx17/monaco-yaml`
npm install monaco-yaml * `cd monaco-yaml`
``` * `yarn`
* open `$/monaco-yaml/demo/index.html` in your favorite browser.
## Usage A running example:
![demo-image](test-demo.png)
Import `monaco-yaml` and configure it before an editor instance is created.
```typescript
import { editor, Uri } from 'monaco-editor';
import { setDiagnosticsOptions } from 'monaco-yaml';
// The uri is used for the schema file match.
const modelUri = Uri.parse('a://b/foo.yaml');
setDiagnosticsOptions({
enableSchemaRequest: true,
hover: true,
completion: true,
validate: true,
format: true,
schemas: [
{
// Id of the first schema
uri: 'http://myserver/foo-schema.json',
// Associate with our model
fileMatch: [String(modelUri)],
schema: {
type: 'object',
properties: {
p1: {
enum: ['v1', 'v2'],
},
p2: {
// Reference the second schema
$ref: 'http://myserver/bar-schema.json',
},
},
},
},
{
// Id of the first schema
uri: 'http://myserver/bar-schema.json',
schema: {
type: 'object',
properties: {
q1: {
enum: ['x1', 'x2'],
},
},
},
},
],
});
editor.create(document.createElement('editor'), {
// Monaco-yaml features should just work if the editor language is set to 'yaml'.
language: 'yaml',
model: editor.createModel('p1: \n', 'yaml', modelUri),
});
```
Also make sure to register the web worker. When using Webpack 5, this looks like the code below.
Other bundlers may use a different syntax, but the idea is the same. Languages you dont used can be
omitted.
```js
window.MonacoEnvironment = {
getWorker(moduleId, label) {
switch (label) {
case 'editorWorkerService':
return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url));
case 'css':
case 'less':
case 'scss':
return new Worker(new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url));
case 'handlebars':
case 'html':
case 'razor':
return new Worker(
new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url),
);
case 'json':
return new Worker(
new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url),
);
case 'javascript':
case 'typescript':
return new Worker(
new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url),
);
case 'yaml':
return new Worker(new URL('monaco-yaml/yaml.worker', import.meta.url));
default:
throw new Error(`Unknown label ${label}`);
}
},
};
```
## Examples
A demo is available on [monaco-yaml.js.org](https://monaco-yaml.js.org).
A running example: ![demo-image](test-demo.png)
Some usage examples can be found in the
[examples](https://github.com/remcohaszing/monaco-yaml/tree/main/examples) directory.
## FAQ
### Does this work with the Monaco UMD bundle?
No. Only ESM is supported.
### Does this work with Monaco Editor from a CDN?
No, because these use a UMD bundle, which isnt supported.
### Does this work with `@monaco-editor/loader` or `@monaco-editor/react`?
No. These packages pull in the Monaco UMD bundle from a CDN. Because UMD isnt supported, neither
are these packages.
### Is the web worker necessary?
Yes. The web worker provides the core functionality of `monaco-yaml`.
### Does it work without a bundler?
No. `monaco-yaml` uses dependencies from `node_modules`, so they can be deduped and your bundle size
is decreased. This comes at the cost of not being able to use it without a bundler.
### How do I integrate `monaco-yaml` with a framework? (Angular, React, Vue, etc.)
`monaco-yaml` only uses the Monaco Editor. Its not tied to a framework, all thats needed is a DOM
node to attach the Monaco Editor to. See the
[Monaco Editor examples](https://github.com/microsoft/monaco-editor/tree/main/monaco-editor-samples)
for examples on how to integrate Monaco Editor in your project, then configure `monaco-yaml` as
described above.
### Does `monaco-yaml` work with `create-react-app`?
Yes, but youll have to eject. See
[#92 (comment)](https://github.com/remcohaszing/monaco-yaml/issues/92#issuecomment-905836058) for
details.
### Why isnt `monaco-yaml` working? Official Monaco language extensions do work.
This is most likely due to the fact that `monaco-yaml` is using a different instance of the
`monaco-editor` package than you are. This is something youll want to avoid regardless of
`monaco-editor`, because it means your bundle is significantly larger than it needs to be. This is
likely caused by one of the following issues:
- A code splitting misconfiguration
To solve this, try inspecting your bundle using for example
[webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer). If
`monaco-editor` is in there twice, this is the issue. Its up to you to solve this, as its
project-specific.
- Youre using a package which imports `monaco-editor` for you, but its using a different version.
You can find out why the `monaco-editor` is installed using `npm ls monaco-editor` or
`yarn why monaco-editor`. It should exist only once, but its ok if its deduped.
You may be able to solve this by deleting your `node_modules` folder and `package-lock.json` or
`yarn.lock`, then running `npm install` or `yarn install` respectively.
## Contributing
Please see our [contributing guidelines](CONTRIBUTING.md)
## Credits ## Credits
- https://github.com/redhat-developer/yaml-language-server
Originally [@kpdecker](https://github.com/kpdecker) forked this repository from ### Maintain
[`monaco-json`](https://github.com/microsoft/monaco-json) by Manually clone dependencies list below and update the project files accordingly:
[@microsoft](https://github.com/microsoft) and rewrote it to work with - `src/languageservice`: https://github.com/redhat-developer/yaml-language-server
[`yaml-language-server`](https://github.com/redhat-developer/yaml-language-server) instead. Later - `cp yaml-language-server/src/languageservice monaco-yaml/src/languageservice`
the repository maintenance was taken over by [@pengx17](https://github.com/pengx17). Eventually the - Modify the import paths, go to the test page and see if it still works
repository was tranferred to the account of [@remcohaszing](https://github.com/remcohaszing), who is - `src/yaml-ast-parser-custom-tags`: https://github.com/JPinkney/yaml-ast-parser/tree/master/src
currently maintaining this repository with the help of [@fleon](https://github.com/fleon) and
[@yazaabed](https://github.com/yazaabed).
The heavy processing is done in
[`yaml-language-server`](https://github.com/redhat-developer/yaml-language-server), best known for
being the backbone for [`vscode-yaml`](https://github.com/redhat-developer/vscode-yaml). This
repository provides a thin layer to add functionality provided by `yaml-language-server` into
`monaco-editor`.
## License ## License
[MIT](https://github.com/pengx17/monaco-yaml/blob/master/LICENSE.md)
[MIT](https://github.com/remcohaszing/monaco-yaml/blob/main/LICENSE.md)

View file

@ -1,58 +0,0 @@
const { build } = require('esbuild');
const { dependencies, peerDependencies } = require('./package.json');
build({
entryPoints: ['src/index.ts', 'src/yaml.worker.ts'],
bundle: true,
external: Object.keys({ ...dependencies, ...peerDependencies }),
logLevel: 'info',
outdir: '.',
sourcemap: true,
format: 'esm',
target: ['es2019'],
plugins: [
{
name: 'alias',
setup({ onResolve }) {
// The file monaco-yaml/lib/esm/schemaSelectionHandlers.js imports code from the language
// server part that we dont want.
onResolve({ filter: /\/schemaSelectionHandlers$/ }, () => ({
path: require.resolve('./src/fillers/schemaSelectionHandlers.ts'),
}));
// The yaml language service only imports re-exports of vscode-languageserver-types from
// vscode-languageserver.
onResolve({ filter: /^vscode-languageserver(\/node|-protocol)?$/ }, () => ({
path: 'vscode-languageserver-types',
external: true,
}));
// The yaml language service uses path. We can stub it using path-browserify.
onResolve({ filter: /^path$/ }, () => ({
path: 'path-browserify',
external: true,
}));
// The main prettier entry point contains all of Prettier.
// The standalone bundle is smaller and works fine for us.
onResolve({ filter: /^prettier/ }, ({ path }) => ({
path: path === 'prettier' ? 'prettier/standalone.js' : `${path}.js`,
external: true,
}));
// This tiny filler implementation serves all our needs.
onResolve({ filter: /vscode-nls/ }, () => ({
path: require.resolve('./src/fillers/vscode-nls.ts'),
}));
// The language server dependencies tend to write both ESM and UMD output alongside each
// other, then use UMD for imports. We prefer ESM.
onResolve({ filter: /\/umd\// }, (args) => ({
path: require.resolve(args.path.replace(/\/umd\//, '/esm/'), {
paths: [args.resolveDir],
}),
}));
},
},
],
}).catch((error) => {
// eslint-disable-next-line no-console
console.error(error);
process.exit(1);
});

View file

@ -1,31 +0,0 @@
# Demo
This demo is deployed to [monaco-yaml.js.org](https://monaco-yaml.js.org). It shows how
`monaco-editor` and `monaco-yaml` can be used with
[Webpack 5](https://webpack.js.org/concepts/entry-points).
## Prerequisites
- [NodeJS](https://nodejs.org) 16 or higher
- [npm](https://github.com/npm/cli) 8.1.2 or higher
## Setup
To run the project locally, clone the repository and set it up:
```sh
git clone https://github.com/remcohaszing/monaco-yaml
cd monaco-yaml
npm ci
npm run prepack
```
## Running
To start it, simply run:
```sh
npm --workspace demo start
```
The demo will open in your browser.

View file

@ -1,24 +0,0 @@
{
"name": "demo",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "webpack serve --open --mode development",
"build": "webpack --mode production"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^6.0.0",
"@schemastore/schema-catalog": "^0.0.5",
"css-loader": "^6.0.0",
"css-minimizer-webpack-plugin": "^3.0.0",
"html-webpack-plugin": "^5.0.0",
"mini-css-extract-plugin": "^2.0.0",
"monaco-editor": "^0.31.0",
"monaco-yaml": "file:../..",
"ts-loader": "^9.0.0",
"typescript": "^4.0.0",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0",
"webpack-dev-server": "^4.0.0"
}
}

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 512 470.647">
<style>
@media (prefers-color-scheme: dark) {
.char {
fill: #ffffff;
}
}
</style>
<polygon class="char" points="512 422.735 395.638 422.735 395.638 250.125 347.442 250.125 347.442 469.647 512 469.647 512 422.737 512 422.735" />
<polygon class="char" points="87.701 250.177 87.701 470.647 135.004 470.647 135.004 318.569 184.509 420.789 221.743 420.789 272.939 314.976 272.939 470.602 318.318 470.602 318.318 250.177 256.358 250.177 201.381 349.883 149.021 250.177 87.701 250.177 87.701 250.177" />
<path fill="#cb171e" d="M330.294,195.39H228.433l-20.717,50.024H162.61L257.99,20.465h46.137l91.51,224.949h-48.2L330.293,195.39Zm-16.92-44.911-31.226-82.55-34.837,82.55h66.063Z" transform="translate(0 -19.939)" />
<polygon class="char" points="235.793 0 143.978 137.674 143.978 224.949 87.702 224.949 87.702 137.674 0 0 63.25 0 119.018 88.646 175.243 0 235.793 0 235.793 0" />
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

View file

@ -1,122 +0,0 @@
:root {
--background-color: hsl(0, 0%, 96%);
--editor-background: hsl(60, 100%, 100%);
--foreground-color: hsl(0, 0%, 0%);
--primary-color: hsl(189, 100%, 63%);
--shadow-color: hsla(0, 0%, 27%, 0.239);
--warning-color: hsl(49, 100%, 40%);
}
@media (prefers-color-scheme: dark) {
:root {
--background-color: hsl(0, 0%, 23%);
--editor-background: hsl(0, 0%, 12%);
--foreground-color: hsl(0, 0%, 100%);
--shadow-color: hsl(0, 0%, 43%);
--warning-color: hsl(49, 100%, 40%);
}
}
body {
background: var(--background-color);
display: flex;
flex-flow: column;
font-family: sans-serif;
height: 100vh;
margin: 0;
}
h1 {
margin: 0 1rem;
}
nav {
align-items: center;
background-color: var(--primary-color);
box-shadow: 0px 5px 5px var(--shadow-color);
display: flex;
flex: 0 0 auto;
height: 3rem;
justify-content: space-between;
}
.nav-icon {
text-decoration: none;
}
.nav-icon > img {
height: 2rem;
margin-right: 1rem;
vertical-align: middle;
}
.editor-wrapper {
background: var(--editor-background);
box-shadow: 0 0 10px var(--shadow-color);
display: flex;
flex: 1 1 auto;
flex-flow: column;
margin: 1.5rem;
}
#schema-selection {
background-color: var(--editor-background);
border: none;
border-bottom: 1px solid var(--shadow-color);
color: var(--foreground-color);
width: 100%;
}
#breadcrumbs {
border-bottom: 1px solid var(--shadow-color);
color: var(--foreground-color);
flex: 0 0 1rem;
}
.breadcrumb {
cursor: pointer;
}
#breadcrumbs::before,
.breadcrumb:not(:last-child)::after {
content: '';
margin: 0 0.2rem;
}
.breadcrumb.array::before {
content: '[]';
}
.breadcrumb.object::before {
content: '{}';
}
#editor {
flex: 1 1 auto;
}
#problems {
border-top: 1px solid var(--shadow-color);
flex: 0 0 20vh;
color: var(--foreground-color);
overflow-y: scroll;
}
.problem {
align-items: center;
cursor: pointer;
display: flex;
padding: 0.25rem;
}
.problem:hover {
background-color: var(--shadow-color);
}
.problem-text {
margin-left: 0.5rem;
}
.problem .codicon-warning {
color: var(--warning-color);
}

View file

@ -1,37 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="<%= require('./icon.svg') %>" />
<title>Monaco YAML</title>
</head>
<body>
<nav>
<h1>Monaco YAML</h1>
<div>
<a href="https://npmjs.com/package/monaco-yaml" class="nav-icon">
<img
alt="npm icon"
src="<%= require('@fortawesome/fontawesome-free/svgs/brands/npm.svg') %>"
/>
</a>
<a href="https://github.com/remcohaszing/monaco-yaml" class="nav-icon">
<img
alt="GitHub icon"
src="<%= require('@fortawesome/fontawesome-free/svgs/brands/github.svg') %>"
/>
</a>
</div>
</nav>
<div class="editor-wrapper">
<div>
<select id="schema-selection">
<option value="monaco-yaml.yaml">Monaco YAML example</option>
</select>
</div>
<div id="breadcrumbs"></div>
<div id="editor"></div>
<div id="problems"></div>
</div>
</body>
</html>

View file

@ -1,230 +0,0 @@
import './index.css';
import { JSONSchemaForSchemaStoreOrgCatalogFiles } from '@schemastore/schema-catalog';
import { CancellationToken } from 'monaco-editor/esm/vs/base/common/cancellation';
import { getDocumentSymbols } from 'monaco-editor/esm/vs/editor/contrib/documentSymbols/documentSymbols';
import {
editor,
Environment,
languages,
Position,
Range,
Uri,
} from 'monaco-editor/esm/vs/editor/editor.api';
import { SchemasSettings, setDiagnosticsOptions } from 'monaco-yaml';
// NOTE: This will give you all editor featues. If you would prefer to limit to only the editor
// features you want to use, import them each individually. See this example: (https://github.com/microsoft/monaco-editor-samples/blob/main/browser-esm-webpack-small/index.js#L1-L91)
import 'monaco-editor';
import defaultSchemaUri from './schema.json';
declare global {
interface Window {
MonacoEnvironment: Environment;
}
}
window.MonacoEnvironment = {
getWorker(moduleId, label) {
switch (label) {
case 'editorWorkerService':
return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url));
case 'yaml':
return new Worker(new URL('monaco-yaml/yaml.worker', import.meta.url));
default:
throw new Error(`Unknown label ${label}`);
}
},
};
const defaultSchema: SchemasSettings = {
uri: defaultSchemaUri,
fileMatch: ['monaco-yaml.yaml'],
};
setDiagnosticsOptions({
schemas: [defaultSchema],
});
const value = `
# Property descriptions are displayed when hovering over properties using your cursor
property: This property has a JSON schema description
# Titles work too!
titledProperty: Titles work too!
# Even markdown descriptions work
markdown: hover me to get a markdown based description 😮
# Enums can be autocompleted by placing the cursor after the colon and pressing Ctrl+Space
enum:
# Unused anchors will be reported
unused anchor: &unused anchor
# Of course numbers are supported!
number: 12
# As well as booleans!
boolean: true
# And strings
string: I am a string
# This property is using the JSON schema recursively
reference:
boolean: Not a boolean
# Also works in arrays
array:
- string: 12
enum: Mewtwo
reference:
reference:
boolean: true
# JSON referenses can be clicked for navigation
pointer:
$ref: '#/array'
# This anchor can be referenced
anchorRef: &anchor can be clicked as well
# Press control while hovering over the anchor
anchorPointer: *anchor
formatting: Formatting is supported too! Under the hood this is powered by Prettier. Just press Ctrl+Shift+I or right click and press Format to format this document.
`.replace(/:$/m, ': ');
const ed = editor.create(document.getElementById('editor'), {
automaticLayout: true,
model: editor.createModel(value, 'yaml', Uri.parse('monaco-yaml.yaml')),
theme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'vs-dark' : 'vs-light',
});
const select = document.getElementById('schema-selection') as HTMLSelectElement;
fetch('https://www.schemastore.org/api/json/catalog.json').then(async (response) => {
if (!response.ok) {
return;
}
const catalog = (await response.json()) as JSONSchemaForSchemaStoreOrgCatalogFiles;
const schemas = [defaultSchema];
catalog.schemas.sort((a, b) => a.name.localeCompare(b.name));
for (const { fileMatch, name, url } of catalog.schemas) {
const match =
typeof name === 'string' && fileMatch?.find((filename) => /\.ya?ml$/i.test(filename));
if (!match) {
continue;
}
const option = document.createElement('option');
option.value = match;
option.textContent = name;
select.append(option);
schemas.push({
fileMatch: [match],
uri: url,
});
}
setDiagnosticsOptions({
validate: true,
enableSchemaRequest: true,
format: true,
hover: true,
completion: true,
schemas,
});
});
select.addEventListener('change', () => {
const oldModel = ed.getModel();
const newModel = editor.createModel(oldModel.getValue(), 'yaml', Uri.parse(select.value));
ed.setModel(newModel);
oldModel.dispose();
});
function* iterateSymbols(
symbols: languages.DocumentSymbol[],
position: Position,
): Iterable<languages.DocumentSymbol> {
for (const symbol of symbols) {
if (Range.containsPosition(symbol.range, position)) {
yield symbol;
yield* iterateSymbols(symbol.children, position);
}
}
}
ed.onDidChangeCursorPosition(async (event) => {
const breadcrumbs = document.getElementById('breadcrumbs');
const symbols = await getDocumentSymbols(ed.getModel(), false, CancellationToken.None);
while (breadcrumbs.lastChild) {
breadcrumbs.lastChild.remove();
}
for (const symbol of iterateSymbols(symbols, event.position)) {
const breadcrumb = document.createElement('span');
breadcrumb.setAttribute('role', 'button');
breadcrumb.classList.add('breadcrumb');
breadcrumb.textContent = symbol.name;
breadcrumb.title = symbol.detail;
if (symbol.kind === languages.SymbolKind.Array) {
breadcrumb.classList.add('array');
} else if (symbol.kind === languages.SymbolKind.Module) {
breadcrumb.classList.add('object');
}
breadcrumb.addEventListener('click', () => {
ed.setPosition({
lineNumber: symbol.range.startLineNumber,
column: symbol.range.startColumn,
});
ed.focus();
});
breadcrumbs.append(breadcrumb);
}
});
editor.onDidChangeMarkers(([resource]) => {
const problems = document.getElementById('problems');
const markers = editor.getModelMarkers({ resource });
while (problems.lastChild) {
problems.lastChild.remove();
}
for (const marker of markers) {
const wrapper = document.createElement('div');
wrapper.setAttribute('role', 'button');
const codicon = document.createElement('div');
const text = document.createElement('div');
wrapper.classList.add('problem');
codicon.classList.add('codicon', 'codicon-warning');
text.classList.add('problem-text');
text.textContent = marker.message;
wrapper.append(codicon, text);
wrapper.addEventListener('click', () => {
ed.setPosition({ lineNumber: marker.startLineNumber, column: marker.startColumn });
ed.focus();
});
problems.append(wrapper);
}
});

View file

@ -1,41 +0,0 @@
{
"type": "object",
"properties": {
"property": {
"description": "I have a description"
},
"titledProperty": {
"title": "I have a title",
"description": "I also have a description"
},
"markdown": {
"markdownDescription": "Even **markdown** _descriptions_ `are` ~~not~~ supported!"
},
"enum": {
"description": "Pick your starter",
"enum": ["Bulbasaur", "Squirtle", "Charmander", "Pikachu"]
},
"number": {
"description": "Numbers work!",
"minimum": 42,
"maximum": 1337
},
"boolean": {
"description": "Are boolean supported?",
"type": "boolean"
},
"string": {
"type": "string"
},
"reference": {
"description": "JSON schemas can be referenced, even recursively",
"$ref": "#"
},
"array": {
"description": "It also works in arrays",
"items": {
"$ref": "#"
}
}
}
}

View file

@ -1,29 +0,0 @@
declare module 'monaco-editor/esm/vs/base/common/cancellation' {
export enum CancellationToken {
None,
}
}
declare module 'monaco-editor/esm/vs/editor/contrib/documentSymbols/documentSymbols' {
import { ITextModel, languages } from 'monaco-editor';
import { CancellationToken } from 'monaco-editor/esm/vs/base/common/cancellation';
export function getDocumentSymbols(
model: ITextModel,
flat: boolean,
token: CancellationToken,
): Promise<languages.DocumentSymbol[]>;
}
declare module 'monaco-editor/esm/vs/editor/editor.worker.js' {
import { worker } from 'monaco-editor/esm/vs/editor/editor.api';
export function initialize(
fn: (ctx: worker.IWorkerContext, createData: unknown) => unknown,
): void;
}
declare module '*.json' {
declare const uri: string;
export default uri;
}

View file

@ -1,39 +0,0 @@
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const HtmlWebPackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
output: {
filename: '[contenthash].js',
},
devtool: 'source-map',
resolve: {
extensions: ['.mjs', '.js', '.ts'],
},
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
{
// Monaco editor uses .ttf icons.
test: /\.(svg|ttf)$/,
type: 'asset/resource',
},
{
test: /schema\.json$/,
type: 'asset/resource',
},
{
test: /\.ts$/,
loader: 'ts-loader',
options: { transpileOnly: true },
},
],
},
optimization: {
minimizer: ['...', new CssMinimizerPlugin()],
},
plugins: [new HtmlWebPackPlugin(), new MiniCssExtractPlugin({ filename: '[contenthash].css' })],
};

View file

@ -1,35 +0,0 @@
# Monaco Editor Webpack Loader Plugin Example
This demo demonstrates how bundle `monaco-editor` and `monaco-yaml` with
[monaco-editor-webpack-plugin](https://github.com/microsoft/monaco-editor/tree/main/webpack-plugin).
The build output is
[esm library](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). Example is
based on
[link](https://github.com/microsoft/monaco-editor/tree/main/samples/browser-esm-webpack-monaco-plugin).
To start it, simply run:
## Prerequisites
- [NodeJS](https://nodejs.org) 16 or higher
- [npm](https://github.com/npm/cli) 8.1.2 or higher
## Setup
To run the project locally, clone the repository and set it up:
```sh
git clone https://github.com/remcohaszing/monaco-yaml
cd monaco-yaml
npm ci
npm run prepack
```
## Running
To start it, simply run:
```sh
npm --workspace monaco-editor-webpack-plugin-example start
```
The demo will open in your browser.

View file

@ -1,4 +0,0 @@
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
export { setDiagnosticsOptions } from 'monaco-yaml';
export default monaco;

View file

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Monaco Editor Webpack Plugin Example</title>
<style>
.editor {
width: 800px;
height: 600px;
border: 1px solid #ccc;
}
</style>
</head>
<body>
<div class="editor"></div>
<script src="index.js" type="module"></script>
</body>
</html>

View file

@ -1,37 +0,0 @@
const value = `
number: 0xfe
boolean: true
`;
async function create() {
// Dynamic import is possible
const { default: monaco } = await import('./main.js');
// Define schema first
monaco.languages.yaml.yamlDefaults.setDiagnosticsOptions({
schemas: [
{
fileMatch: ['*'],
uri: 'my-schema.json',
schema: {
type: 'object',
properties: {
number: {
description: 'number property',
type: 'number',
},
},
},
},
],
});
// Create editor
monaco.editor.create(document.querySelector('.editor'), {
language: 'yaml',
tabSize: 2,
value,
});
}
create();

View file

@ -1,20 +0,0 @@
{
"name": "monaco-editor-webpack-plugin-example",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"start": "webpack serve --open --mode development",
"build": "webpack --mode production"
},
"dependencies": {
"css-loader": "^6.0.0",
"monaco-editor": "^0.31.0",
"monaco-editor-webpack-plugin": "^7.0.0",
"monaco-yaml": "file:../..",
"style-loader": "^3.0.0",
"webpack": "^5.0.0",
"webpack-cli": "^4.0.0",
"webpack-dev-server": "^4.0.0"
}
}

View file

@ -1,49 +0,0 @@
import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin';
export default {
entry: './editor.js',
output: {
filename: '[name].js',
library: {
type: 'module',
},
clean: true,
},
target: 'es2020',
experiments: {
outputModule: true,
},
devtool: 'source-map',
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.ttf$/,
type: 'asset',
},
],
},
plugins: [
new MonacoWebpackPlugin({
languages: [],
customLanguages: [
{
label: 'yaml',
entry: ['monaco-yaml', 'vs/basic-languages/yaml/yaml.contribution'],
worker: {
id: 'monaco-yaml/yamlWorker',
entry: 'monaco-yaml/yaml.worker',
},
},
],
}),
],
devServer: {
static: {
directory: './',
},
},
};

View file

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}

View file

@ -0,0 +1,2 @@
dist/
node_modules/

View file

@ -0,0 +1,9 @@
# Demo: React + Weback + Worker Loader + Babel
To run:
```
yarn && yarn start
```
The demo will open in your browser. See (index.jsx)[index.jsx#L34-L36] for the schema loaded.

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>React Example</title>
</head>
<body>
<div id="react"></div>
</body>
</html>

View file

@ -0,0 +1,80 @@
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import MonacoEditor from 'react-monaco-editor';
import 'monaco-yaml/esm/monaco.contribution';
import { languages } from 'monaco-editor/esm/vs/editor/editor.api';
// NOTE: This will give you all editor featues. If you would prefer to limit to only the editor
// features you want to use, import them each individually. See this example: (https://github.com/microsoft/monaco-editor-samples/blob/master/browser-esm-webpack-small/index.js#L1-L91)
import 'monaco-editor';
// NOTE: using loader syntax becuase Yaml worker imports editor.worker directly and that
// import shouldn't go through loader syntax.
import EditorWorker from 'worker-loader!monaco-editor/esm/vs/editor/editor.worker';
import YamlWorker from 'worker-loader!monaco-yaml/esm/yaml.worker';
window.MonacoEnvironment = {
getWorker(workerId, label) {
if (label === 'yaml') {
return new YamlWorker();
}
return new EditorWorker();
},
};
const { yaml } = languages || {};
const Editor = () => {
const [value, setValue] = useState('p1: ');
useEffect(() => {
yaml &&
yaml.yamlDefaults.setDiagnosticsOptions({
validate: true,
enableSchemaRequest: true,
hover: true,
completion: true,
schemas: [
{
uri: 'http://myserver/foo-schema.json', // id of the first schema
fileMatch: ['*'], // associate with our model
schema: {
id: 'http://myserver/foo-schema.json', // id of the first 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: {
id: 'http://myserver/bar-schema.json', // id of the first schema
type: 'object',
properties: {
q1: {
enum: ['x1', 'x2'],
},
},
},
},
],
});
}, []);
return (
<MonacoEditor
width="800"
height="600"
language="yaml"
value={value}
onChange={setValue}
/>
);
};
ReactDOM.render(<Editor />, document.getElementById('react'));

View file

@ -0,0 +1,31 @@
{
"name": "react-webpack-worker-loader-example",
"version": "1.0.0",
"description": "",
"private": true,
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --open --mode development",
"build": "webpack --mode production"
},
"author": "",
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/preset-env": "^7.8.4",
"@babel/preset-react": "^7.8.3",
"babel-loader": "^8.0.6",
"css-loader": "^3.4.2",
"file-loader": "^5.1.0",
"html-webpack-plugin": "^3.2.0",
"monaco-editor": "^0.20.0",
"monaco-yaml": "^2.4.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-monaco-editor": "^0.34.0",
"style-loader": "^1.1.3",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3",
"worker-loader": "^2.0.0"
}
}

View file

@ -0,0 +1,37 @@
const HtmlWebPackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: {
main: './index.jsx',
},
output: {
globalObject: 'this',
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.ttf$/,
loader: 'file-loader',
},
],
},
plugins: [
new HtmlWebPackPlugin({
template: './index.html',
}),
],
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,3 @@
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}

2
examples/react-webpack/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
dist/
node_modules/

View file

@ -0,0 +1,9 @@
# Demo: React + Weback + Babel
To run:
```
yarn && yarn start
```
The demo will open in your browser. See (index.jsx)[index.jsx#L34-L36] for the schema loaded.

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>React Example</title>
</head>
<body>
<div id="react"></div>
</body>
</html>

View file

@ -0,0 +1,75 @@
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import MonacoEditor from 'react-monaco-editor';
import 'monaco-yaml/esm/monaco.contribution';
import { languages } from 'monaco-editor/esm/vs/editor/editor.api';
// NOTE: This will give you all editor featues. If you would prefer to limit to only the editor
// features you want to use, import them each individually. See this example: (https://github.com/microsoft/monaco-editor-samples/blob/master/browser-esm-webpack-small/index.js#L1-L91)
import 'monaco-editor';
window.MonacoEnvironment = {
getWorkerUrl(moduleId, label) {
if (label === 'yaml') {
return './yaml.worker.bundle.js';
}
return './editor.worker.bundle.js';
},
};
const { yaml } = languages || {};
const Editor = () => {
const [value, setValue] = useState('p1: ');
useEffect(() => {
yaml &&
yaml.yamlDefaults.setDiagnosticsOptions({
validate: true,
enableSchemaRequest: true,
hover: true,
completion: true,
schemas: [
{
uri: 'http://myserver/foo-schema.json', // id of the first schema
fileMatch: ['*'], // associate with our model
schema: {
id: 'http://myserver/foo-schema.json', // id of the first 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: {
id: 'http://myserver/bar-schema.json', // id of the first schema
type: 'object',
properties: {
q1: {
enum: ['x1', 'x2'],
},
},
},
},
],
});
}, []);
return (
<MonacoEditor
width="800"
height="600"
language="yaml"
value={value}
onChange={setValue}
/>
);
};
ReactDOM.render(<Editor />, document.getElementById('react'));

View file

@ -0,0 +1,30 @@
{
"name": "react-webpack-example",
"version": "1.0.0",
"description": "",
"private": true,
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --open --mode development",
"build": "webpack --mode production"
},
"author": "",
"devDependencies": {
"@babel/core": "^7.8.4",
"@babel/preset-env": "^7.8.4",
"@babel/preset-react": "^7.8.3",
"babel-loader": "^8.0.6",
"css-loader": "^3.4.2",
"file-loader": "^5.1.0",
"html-webpack-plugin": "^3.2.0",
"monaco-editor": "^0.20.0",
"monaco-yaml": "^2.4.0",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-monaco-editor": "^0.34.0",
"style-loader": "^1.1.3",
"webpack": "^4.41.6",
"webpack-cli": "^3.3.11",
"webpack-dev-server": "^3.10.3"
}
}

View file

@ -0,0 +1,39 @@
const HtmlWebPackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: {
main: './index.jsx',
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
'yaml.worker': 'monaco-yaml/esm/yaml.worker.js',
},
output: {
globalObject: 'this',
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.ttf$/,
loader: 'file-loader',
},
],
},
plugins: [
new HtmlWebPackPlugin({
template: './index.html',
}),
],
};

File diff suppressed because it is too large Load diff

162
examples/umd/index.html Normal file
View file

@ -0,0 +1,162 @@
<!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"
/>
</head>
<body>
<h2>Monaco Editor YAML test page</h2>
<code id="path"></code>
<div
id="container"
style="width:800px;height:600px;border:1px solid grey"
></div>
<style>
.x-highlight-range {
background-color: lightblue;
}
</style>
<script>
// Loading basic-languages to get the YAML language definition
var paths = {
'vs/basic-languages': '../node_modules/monaco-languages/release/dev',
'vs/language/yaml': '../lib/dev',
vs: '../node_modules/monaco-editor-core/dev/vs',
prettier: '../node_modules/prettier',
};
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 + '/lib/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/monaco.contribution',
'vs/language/yaml/monaco.contribution',
'prettier/standalone',
'prettier/parser-yaml',
], function() {
const yaml = `p1: `;
const modelUri = monaco.Uri.parse('a://b/foo.json');
const editor = monaco.editor.create(
document.getElementById('container'),
{
language: 'yaml',
showFoldingControls: 'always',
model: monaco.editor.createModel(yaml, 'yaml', modelUri),
}
);
monaco.languages.yaml.yamlDefaults.setDiagnosticsOptions({
enableSchemaRequest: true,
hover: true,
completion: true,
validate: true,
format: true,
schemas: [
{
uri: 'http://myserver/foo-schema.json', // id of the first schema
fileMatch: [modelUri.toString()], // associate with our model
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>
</body>
</html>

View file

@ -1,29 +0,0 @@
# Vite Example
This minimal example shows how `monaco-yaml` can be used with [Vite](https://vitejs.dev).
## Prerequisites
- [NodeJS](https://nodejs.org) 16 or higher
- [npm](https://github.com/npm/cli) 8.1.2 or higher
## Setup
To run the project locally, clone the repository and set it up:
```sh
git clone https://github.com/remcohaszing/monaco-yaml
cd monaco-yaml
npm ci
npm run prepack
```
## Running
To start it, simply run:
```sh
npm --workspace vite-example start
```
The demo will be available on http://localhost:3000.

View file

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Monaco YAML</title>
</head>
<body>
<div id="editor" style="width: 800px; height: 600px;"></div>
<script type="module" src="/index.js"></script>
</body>
</html>

View file

@ -1,67 +0,0 @@
import { editor, Uri } from 'monaco-editor';
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import { setDiagnosticsOptions } from 'monaco-yaml';
import YamlWorker from 'monaco-yaml/yaml.worker?worker';
window.MonacoEnvironment = {
getWorker(moduleId, label) {
switch (label) {
case 'editorWorkerService':
return new EditorWorker();
case 'yaml':
return new YamlWorker();
default:
throw new Error(`Unknown label ${label}`);
}
},
};
// The uri is used for the schema file match.
const modelUri = Uri.parse('a://b/foo.yaml');
setDiagnosticsOptions({
enableSchemaRequest: true,
hover: true,
completion: true,
validate: true,
format: true,
schemas: [
{
// Id of the first schema
uri: 'http://myserver/foo-schema.json',
// Associate with our model
fileMatch: [String(modelUri)],
schema: {
type: 'object',
properties: {
p1: {
enum: ['v1', 'v2'],
},
p2: {
// Reference the second schema
$ref: 'http://myserver/bar-schema.json',
},
},
},
},
{
// Id of the first schema
uri: 'http://myserver/bar-schema.json',
schema: {
type: 'object',
properties: {
q1: {
enum: ['x1', 'x2'],
},
},
},
},
],
});
const value = 'p1: \np2: \n';
editor.create(document.getElementById('editor'), {
automaticLayout: true,
model: editor.createModel(value, 'yaml', modelUri),
});

View file

@ -1,14 +0,0 @@
{
"name": "vite-example",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "vite",
"build": "vite build"
},
"dependencies": {
"monaco-editor": "^0.31.0",
"monaco-yaml": "file:../..",
"vite": "^2.0.0"
}
}

112
index.d.ts vendored
View file

@ -1,112 +0,0 @@
import { JSONSchema4, JSONSchema6, JSONSchema7 } from 'json-schema';
import { IEvent, languages } from 'monaco-editor/esm/vs/editor/editor.api';
export interface SchemasSettings {
/**
* A `Uri` file match which will trigger the schema validation. This may be a glob or an exact
* path.
*
* @example '.gitlab-ci.yml'
* @example 'file://**\/.github/actions/*.yaml'
*/
fileMatch: string[];
/**
* The JSON schema which will be used for validation. If not specified, it will be downloaded from
* `uri`.
*/
schema?: JSONSchema4 | JSONSchema6 | JSONSchema7;
/**
* The source URI of the JSON schema. The JSON schema will be downloaded from here if no schema
* was supplied. It will also be displayed as the source in hover tooltips.
*/
uri: string;
}
declare module 'monaco-editor/esm/vs/editor/editor.api' {
namespace languages.yaml {
export interface DiagnosticsOptions {
/**
* If set, enable schema based autocompletion.
*
* @default true
*/
readonly completion?: boolean;
/**
* A list of custom tags.
*
* @default []
*/
readonly customTags?: string[];
/**
* If set, the schema service would load schema content on-demand with 'fetch' if available
*
* @default false
*/
readonly enableSchemaRequest?: boolean;
/**
* If true, formatting using Prettier is enabled. Setting this to `false` does **not** exclude
* Prettier from the bundle.
*
* @default true
*/
readonly format?: boolean;
/**
* If set, enable hover typs based the JSON schema.
*
* @default true
*/
readonly hover?: boolean;
/**
* If true, a different diffing algorithm is used to generate error messages.
*
* @default false
*/
readonly isKubernetes?: boolean;
/**
* A list of known schemas and/or associations of schemas to file names.
*
* @default []
*/
readonly schemas?: SchemasSettings[];
/**
* If set, the validator will be enabled and perform syntax validation as well as schema
* based validation.
*
* @default true
*/
readonly validate?: boolean;
/**
* The YAML version to use for parsing.
*
* @default '1.2'
*/
readonly yamlVersion?: '1.1' | '1.2';
}
export interface LanguageServiceDefaults {
readonly onDidChange: IEvent<LanguageServiceDefaults>;
readonly languageId: string;
readonly diagnosticsOptions: DiagnosticsOptions;
setDiagnosticsOptions: (options: DiagnosticsOptions) => void;
}
export const yamlDefaults: LanguageServiceDefaults;
}
}
/**
* Configure `monaco-yaml` diagnostics options.
*
* @param options - The options to set.
*/
export function setDiagnosticsOptions(options?: languages.yaml.DiagnosticsOptions): void;

View file

@ -1,3 +0,0 @@
[build]
publish = 'examples/demo/dist/'
command = 'npm ci && npm run prepack && npm --workspace demo run build'

8500
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,71 +1,104 @@
{ {
"name": "monaco-yaml", "name": "monaco-yaml",
"version": "4.0.0-alpha.1", "version": "2.4.1",
"description": "YAML plugin for the Monaco Editor", "description": "YAML plugin for the Monaco Editor",
"homepage": "https://monaco-yaml.js.org",
"scripts": { "scripts": {
"prepack": "node build.js", "watch": "tsc -p ./src --watch",
"prepare": "husky install" "compile": "rimraf ./out && yarn compile:umd && yarn compile:esm",
"compile:umd": "tsc -p ./src/tsconfig.json",
"compile:esm": "tsc -p ./src/tsconfig.esm.json",
"bundle": "rimraf ./lib && yarn bundle:umd && yarn bundle:esm && mcopy ./src/monaco.d.ts ./lib/monaco.d.ts",
"bundle:umd": "node ./scripts/bundle-umd",
"bundle:esm": "node ./scripts/bundle-esm",
"build": "yarn compile && yarn bundle",
"prepare": "yarn build",
"lint": "prettier \"{src,test}/**/*.{json,scss,html,ts}\" --write",
"test": "jest --verbose"
},
"main": "./lib/esm/monaco.contribution.js",
"module": "./lib/esm/monaco.contribution.js",
"typings": "./lib/monaco.d.ts",
"directories": {
"lib": "./lib"
}, },
"typings": "./index.d.ts",
"files": [
"index.js",
"index.d.ts",
"yaml.worker.js"
],
"workspaces": [
"examples/*"
],
"author": "Kevin Decker <kpdecker@gmail.com> (http://incaseofstairs.com)", "author": "Kevin Decker <kpdecker@gmail.com> (http://incaseofstairs.com)",
"maintainers": [ "maintainers": [
"Remco Haszing <remcohaszing@gmail.com> (https://github.com/remcohaszing)" "kpdecker",
"pengx17"
], ],
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/remcohaszing/monaco-yaml" "url": "https://github.com/pengx17/monaco-yaml"
}, },
"bugs": { "bugs": {
"url": "https://github.com/remcohaszing/monaco-yaml/issues" "url": "https://github.com/pengx17/monaco-yaml/issues"
},
"optionalDependencies": {
"prettier": "^1.19.1"
}, },
"keywords": [
"editor",
"frontend",
"front-end",
"monaco",
"monaco-editor",
"yaml"
],
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.0", "js-yaml": "^3.12.0",
"jsonc-parser": "^3.0.0", "vscode-json-languageservice": "^3.4.11",
"path-browserify": "^1.0.0", "vscode-languageserver": "^5.2.1",
"prettier": "2.0.5", "vscode-uri": "^2.1.1"
"vscode-languageserver-textdocument": "^1.0.0",
"vscode-languageserver-types": "^3.0.0",
"yaml": "2.0.0-10"
}, },
"peerDependencies": { "peerDependencies": {
"monaco-editor": ">=0.30" "monaco-editor": "^0.19.2"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.0.0", "@types/jest": "^23.3.10",
"esbuild": "^0.14.0", "@types/node": "^10.9.3",
"eslint": "^7.0.0", "husky": "^1.2.1",
"eslint-config-remcohaszing": "^3.0.0", "jest": "^23.6.0",
"husky": "^7.0.0", "jsonc-parser": "^2.0.2",
"lint-staged": "^12.0.0", "lint-staged": "^8.1.0",
"monaco-editor": "^0.31.0", "monaco-editor": "^0.16.2",
"type-fest": "^2.0.0", "monaco-editor-core": "0.16.1",
"typescript": "^4.0.0", "monaco-languages": "1.6.0",
"yaml-language-server": "^1.0.0" "monaco-plugin-helpers": "^1.0.2",
"prettier": "^1.19.1",
"request-light": "^0.2.5",
"requirejs": "^2.3.5",
"rimraf": "^2.6.2",
"ts-jest": "^23.10.5",
"typescript": "^3.7.4",
"uglify-es": "^3.3.9",
"vscode-languageserver-types": "3.12.0"
},
"prettier": {
"singleQuote": true,
"trailingComma": "es5",
"semi": true
}, },
"lint-staged": { "lint-staged": {
"*.{css,json,md,html,yaml}": [ "linters": {
"prettier --write" "*.{json,scss,html,ts,js,jsx}": [
"prettier --write",
"git add"
]
}
},
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"jest": {
"globals": {
"ts-jest": {
"tsConfig": "./test/tsconfig.json"
}
},
"moduleFileExtensions": [
"js",
"ts"
], ],
"*.{js,ts}": [ "transform": {
"eslint" "^.+\\.(ts|tsx)$": "ts-jest"
},
"testMatch": [
"**/test/*.test.+(ts|js)"
] ]
} }
} }

33
scripts/bundle-esm.js Normal file
View file

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* 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: "lib/esm",
entryPoints: ["monaco.contribution.js", "yamlMode.js", "yaml.worker.js"],
resolveAlias: {
"vscode-nls": path.join(REPO_ROOT, "out/esm/fillers/vscode-nls.js"),
"vscode-json-languageservice/lib/umd/services/jsonValidation": path.join(REPO_ROOT, 'node_modules/vscode-json-languageservice/lib/esm/services/jsonValidation.js'),
"vscode-json-languageservice": path.join(REPO_ROOT, "node_modules/vscode-json-languageservice/lib/esm/jsonLanguageService.js"),
"vscode-json-languageservice/lib/umd/services/jsonHover": path.join(REPO_ROOT, 'node_modules/vscode-json-languageservice/lib/esm/services/jsonHover.js'),
"vscode-json-languageservice/lib/umd/services/jsonDocumentSymbols": path.join(REPO_ROOT, 'node_modules/vscode-json-languageservice/lib/esm/services/jsonDocumentSymbols.js'),
"vscode-json-languageservice/lib/umd/services/jsonSchemaService": path.join(REPO_ROOT, 'node_modules/vscode-json-languageservice/lib/esm/services/jsonSchemaService.js'),
},
resolveSkip: ["monaco-editor", "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/umd": "vscode-json-languageservice/lib/esm",
// "js-yaml/dist": "js-yaml"
}
});

92
scripts/bundle-umd.js Normal file
View file

@ -0,0 +1,92 @@
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: 'lib/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: 'vscode-json-languageservice',
location: path.join(REPO_ROOT, 'node_modules/vscode-json-languageservice'),
main: 'lib/umd/jsonLanguageService'
},
{
name: 'vscode-languageserver-textdocument',
location: path.join(REPO_ROOT, 'node_modules/vscode-languageserver-textdocument'),
main: 'lib/umd/main'
},
{
name: 'yaml-ast-parser-custom-tags',
location: path.join(REPO_ROOT, 'node_modules/yaml-ast-parser-custom-tags'),
main: 'dist/src/index'
},
{
name: 'jsonc-parser',
location: path.join(REPO_ROOT, 'node_modules/jsonc-parser/lib/umd'),
main: 'main'
},
{
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, 'lib/dev/' + moduleId + '.js');
const minFilePath = path.join(REPO_ROOT, 'lib/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, 'lib/min')) } catch (err) { }
fs.writeFileSync(minFilePath, BUNDLED_FILE_HEADER + result.code);
})
}

View file

@ -1 +0,0 @@
export const languageId = 'yaml';

1
src/fillers/os.ts Normal file
View file

@ -0,0 +1 @@
export const EOL = '\n';

View file

@ -1,5 +0,0 @@
/**
* This is a stub for `monaco-yaml/lib/esm/schemaSelectionHandlers.js`.
*/
// eslint-disable-next-line @typescript-eslint/no-empty-function
export function JSONSchemaSelection(): void {}

View file

@ -1,3 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface Options { export interface Options {
locale?: string; locale?: string;
cacheLanguageResolution?: boolean; cacheLanguageResolution?: boolean;
@ -6,30 +11,38 @@ export interface LocalizeInfo {
key: string; key: string;
comment: string[]; comment: string[];
} }
export type LocalizeFunc = ( export interface LocalizeFunc {
info: LocalizeInfo | string, (info: LocalizeInfo, message: string, ...args: any[]): string;
message: string, (key: string, message: string, ...args: any[]): string;
...args: unknown[] }
) => string;
export type LoadFunc = (file?: string) => LocalizeFunc; export type LoadFunc = (file?: string) => LocalizeFunc;
function format(message: string, args: string[]): string { function format(message: string, args: any[]): string {
return args.length === 0 let result: string;
? message
: message.replace(/{(\d+)}/g, (match, rest: number[]) => { if (args.length === 0) {
const [index] = rest; result = message;
return typeof args[index] === 'undefined' ? match : args[index]; } else {
}); result = message.replace(/\{(\d+)\}/g, (match, rest) => {
const index = rest[0];
return typeof args[index] !== 'undefined' ? args[index] : match;
});
}
return result;
} }
function localize(key: LocalizeInfo | string, message: string, ...args: string[]): string { function localize(
key: string | LocalizeInfo,
message: string,
...args: any[]
): string {
return format(message, args); return format(message, args);
} }
export function loadMessageBundle(): LocalizeFunc { export function loadMessageBundle(file?: string): LocalizeFunc {
return localize; return localize;
} }
export function config(): LoadFunc { export function config(opt?: Options | string): LoadFunc {
return loadMessageBundle; return loadMessageBundle;
} }

View file

@ -1,78 +0,0 @@
import { Emitter, languages } from 'monaco-editor/esm/vs/editor/editor.api.js';
import { languageId } from './constants';
import { setupMode } from './yamlMode';
// --- YAML configuration and defaults ---------
const diagnosticDefault: languages.yaml.DiagnosticsOptions = {
completion: true,
customTags: [],
enableSchemaRequest: false,
format: true,
isKubernetes: false,
hover: true,
schemas: [],
validate: true,
yamlVersion: '1.2',
};
export function createLanguageServiceDefaults(
initialDiagnosticsOptions: languages.yaml.DiagnosticsOptions,
): languages.yaml.LanguageServiceDefaults {
const onDidChange = new Emitter<languages.yaml.LanguageServiceDefaults>();
let diagnosticsOptions = initialDiagnosticsOptions;
const languageServiceDefaults: languages.yaml.LanguageServiceDefaults = {
get onDidChange() {
return onDidChange.event;
},
get languageId() {
return languageId;
},
get diagnosticsOptions() {
return diagnosticsOptions;
},
setDiagnosticsOptions(options) {
diagnosticsOptions = { ...diagnosticDefault, ...options };
onDidChange.fire(languageServiceDefaults);
},
};
return languageServiceDefaults;
}
const yamlDefaults = createLanguageServiceDefaults(diagnosticDefault);
// Export API
function createAPI(): typeof languages.yaml {
return {
yamlDefaults,
};
}
languages.yaml = createAPI();
// --- Registration to monaco editor ---
languages.register({
id: languageId,
extensions: ['.yaml', '.yml'],
aliases: ['YAML', 'yaml', 'YML', 'yml'],
mimetypes: ['application/x-yaml'],
});
languages.onLanguage('yaml', () => {
setupMode(yamlDefaults);
});
/**
* Configure `monaco-yaml` diagnostics options.
*
* @param options - The options to set.
*/
export function setDiagnosticsOptions(options: languages.yaml.DiagnosticsOptions = {}): void {
languages.yaml.yamlDefaults.setDiagnosticsOptions(options);
}

View file

@ -1,49 +1,149 @@
import { /*---------------------------------------------------------------------------------------------
editor, * Copyright (c) Microsoft Corporation. All rights reserved.
IDisposable, * Licensed under the MIT License. See License.txt in the project root for license information.
languages, *--------------------------------------------------------------------------------------------*/
MarkerSeverity, 'use strict';
MarkerTag,
Position,
Range,
Uri,
} from 'monaco-editor/esm/vs/editor/editor.api.js';
import * as ls from 'vscode-languageserver-types';
import { CustomFormatterOptions } from 'yaml-language-server/lib/esm/languageservice/yamlLanguageService.js';
import { languageId } from './constants'; import { LanguageServiceDefaultsImpl } from './monaco.contribution';
import { YAMLWorker } from './yamlWorker'; import { YAMLWorker } from './yamlWorker';
export type WorkerAccessor = (...more: Uri[]) => PromiseLike<YAMLWorker>; 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 CancellationToken = monaco.CancellationToken;
import IDisposable = monaco.IDisposable;
import { CustomFormatterOptions } from './languageservice/yamlLanguageService';
export type WorkerAccessor = (...more: Uri[]) => Thenable<YAMLWorker>;
// --- diagnostics --- --- // --- diagnostics --- ---
function toSeverity(lsSeverity: ls.DiagnosticSeverity): MarkerSeverity { export class DiagnosticsAdapter {
private _disposables: IDisposable[] = [];
private _listener: { [uri: string]: IDisposable } = Object.create(null);
constructor(
private _languageId: string,
private _worker: WorkerAccessor,
defaults: LanguageServiceDefaultsImpl
) {
const onModelAdd = (model: monaco.editor.IModel): void => {
const modeId = model.getModeId();
if (modeId !== this._languageId) {
return;
}
let handle: NodeJS.Timer;
this._listener[model.uri.toString()] = model.onDidChangeContent(() => {
clearTimeout(handle);
handle = setTimeout(() => this._doValidate(model.uri, modeId), 500);
});
this._doValidate(model.uri, modeId);
};
const onModelRemoved = (model: monaco.editor.IModel): void => {
monaco.editor.setModelMarkers(model, this._languageId, []);
const uriStr = model.uri.toString();
const listener = this._listener[uriStr];
if (listener) {
listener.dispose();
delete this._listener[uriStr];
}
};
this._disposables.push(monaco.editor.onDidCreateModel(onModelAdd));
this._disposables.push(
monaco.editor.onWillDisposeModel(model => {
onModelRemoved(model);
this._resetSchema(model.uri);
})
);
this._disposables.push(
monaco.editor.onDidChangeModelLanguage(event => {
onModelRemoved(event.model);
onModelAdd(event.model);
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 (const key in this._listener) {
this._listener[key].dispose();
}
},
});
monaco.editor.getModels().forEach(onModelAdd);
}
public dispose(): void {
this._disposables.forEach(d => d && d.dispose());
this._disposables = [];
}
private _resetSchema(resource: Uri): void {
this._worker().then(worker => {
worker.resetSchema(resource.toString());
});
}
private _doValidate(resource: Uri, languageId: string): void {
this._worker(resource)
.then(worker => {
return worker.doValidation(resource.toString()).then(diagnostics => {
const markers = diagnostics.map(d => toDiagnostics(resource, d));
const model = monaco.editor.getModel(resource);
if (model.getModeId() === languageId) {
monaco.editor.setModelMarkers(model, languageId, markers);
}
});
})
.then(undefined, err => {
console.error(err);
});
}
}
function toSeverity(lsSeverity: number): monaco.MarkerSeverity {
switch (lsSeverity) { switch (lsSeverity) {
case ls.DiagnosticSeverity.Error: case ls.DiagnosticSeverity.Error:
return MarkerSeverity.Error; return monaco.MarkerSeverity.Error;
case ls.DiagnosticSeverity.Warning: case ls.DiagnosticSeverity.Warning:
return MarkerSeverity.Warning; return monaco.MarkerSeverity.Warning;
case ls.DiagnosticSeverity.Information: case ls.DiagnosticSeverity.Information:
return MarkerSeverity.Info; return monaco.MarkerSeverity.Info;
case ls.DiagnosticSeverity.Hint: case ls.DiagnosticSeverity.Hint:
return MarkerSeverity.Hint; return monaco.MarkerSeverity.Hint;
default: default:
return MarkerSeverity.Info; return monaco.MarkerSeverity.Info;
} }
} }
function toMarkerDataTag(tag: ls.DiagnosticTag): MarkerTag { function toDiagnostics(
switch (tag) { resource: Uri,
case ls.DiagnosticTag.Deprecated: diag: ls.Diagnostic
return MarkerTag.Deprecated; ): monaco.editor.IMarkerData {
case ls.DiagnosticTag.Unnecessary: const code =
return MarkerTag.Unnecessary; typeof diag.code === 'number' ? String(diag.code) : (diag.code as string);
default:
}
}
function toDiagnostics(diag: ls.Diagnostic): editor.IMarkerData {
return { return {
severity: toSeverity(diag.severity), severity: toSeverity(diag.severity),
startLineNumber: diag.range.start.line + 1, startLineNumber: diag.range.start.line + 1,
@ -51,109 +151,48 @@ function toDiagnostics(diag: ls.Diagnostic): editor.IMarkerData {
endLineNumber: diag.range.end.line + 1, endLineNumber: diag.range.end.line + 1,
endColumn: diag.range.end.character + 1, endColumn: diag.range.end.character + 1,
message: diag.message, message: diag.message,
code: String(diag.code), code,
source: diag.source, source: diag.source,
tags: diag.tags?.map(toMarkerDataTag),
}; };
} }
export function createDiagnosticsAdapter(
getWorker: WorkerAccessor,
defaults: languages.yaml.LanguageServiceDefaults,
): void {
const listeners = new Map<string, IDisposable>();
const resetSchema = async (resource: Uri): Promise<void> => {
const worker = await getWorker();
worker.resetSchema(String(resource));
};
const doValidate = async (resource: Uri): Promise<void> => {
const worker = await getWorker(resource);
const diagnostics = await worker.doValidation(String(resource));
const markers = diagnostics.map(toDiagnostics);
const model = editor.getModel(resource);
// Return value from getModel can be null if model not found
// (e.g. if user navigates away from editor)
if (model && model.getLanguageId() === languageId) {
editor.setModelMarkers(model, languageId, markers);
}
};
const onModelAdd = (model: editor.IModel): void => {
if (model.getLanguageId() !== languageId) {
return;
}
let handle: ReturnType<typeof setTimeout>;
listeners.set(
String(model.uri),
model.onDidChangeContent(() => {
clearTimeout(handle);
handle = setTimeout(() => doValidate(model.uri), 500);
}),
);
doValidate(model.uri);
};
const onModelRemoved = (model: editor.IModel): void => {
editor.setModelMarkers(model, languageId, []);
const uriStr = String(model.uri);
const listener = listeners.get(uriStr);
if (listener) {
listener.dispose();
listeners.delete(uriStr);
}
};
editor.onDidCreateModel(onModelAdd);
editor.onWillDisposeModel((model) => {
onModelRemoved(model);
resetSchema(model.uri);
});
editor.onDidChangeModelLanguage((event) => {
onModelRemoved(event.model);
onModelAdd(event.model);
resetSchema(event.model.uri);
});
defaults.onDidChange(() => {
for (const model of editor.getModels()) {
if (model.getLanguageId() === languageId) {
onModelRemoved(model);
onModelAdd(model);
}
}
});
for (const model of editor.getModels()) {
onModelAdd(model);
}
}
// --- completion ------ // --- completion ------
function fromPosition(position: Position): ls.Position { function fromPosition(position: Position): ls.Position {
if (!position) { if (!position) {
return; return void 0;
} }
return { character: position.column - 1, line: position.lineNumber - 1 }; return { character: position.column - 1, line: position.lineNumber - 1 };
} }
function fromRange(range: IRange): ls.Range {
if (!range) {
return void 0;
}
return {
start: {
line: range.startLineNumber - 1,
character: range.startColumn - 1,
},
end: { line: range.endLineNumber - 1, character: range.endColumn - 1 },
};
}
function toRange(range: ls.Range): Range { function toRange(range: ls.Range): Range {
if (!range) { if (!range) {
return; return void 0;
} }
return new Range( return new Range(
range.start.line + 1, range.start.line + 1,
range.start.character + 1, range.start.character + 1,
range.end.line + 1, range.end.line + 1,
range.end.character + 1, range.end.character + 1
); );
} }
function toCompletionItemKind(kind: ls.CompletionItemKind): languages.CompletionItemKind { function toCompletionItemKind(
const mItemKind = languages.CompletionItemKind; kind: number
): monaco.languages.CompletionItemKind {
const mItemKind = monaco.languages.CompletionItemKind;
switch (kind) { switch (kind) {
case ls.CompletionItemKind.Text: case ls.CompletionItemKind.Text:
@ -192,14 +231,59 @@ function toCompletionItemKind(kind: ls.CompletionItemKind): languages.Completion
return mItemKind.File; return mItemKind.File;
case ls.CompletionItemKind.Reference: case ls.CompletionItemKind.Reference:
return mItemKind.Reference; return mItemKind.Reference;
default:
return mItemKind.Property;
} }
return mItemKind.Property;
} }
function toTextEdit(textEdit: ls.TextEdit): editor.ISingleEditOperation { function fromCompletionItemKind(
kind: monaco.languages.CompletionItemKind
): ls.CompletionItemKind {
const mItemKind = monaco.languages.CompletionItemKind;
switch (kind) {
case mItemKind.Text:
return ls.CompletionItemKind.Text;
case mItemKind.Method:
return ls.CompletionItemKind.Method;
case mItemKind.Function:
return ls.CompletionItemKind.Function;
case mItemKind.Constructor:
return ls.CompletionItemKind.Constructor;
case mItemKind.Field:
return ls.CompletionItemKind.Field;
case mItemKind.Variable:
return ls.CompletionItemKind.Variable;
case mItemKind.Class:
return ls.CompletionItemKind.Class;
case mItemKind.Interface:
return ls.CompletionItemKind.Interface;
case mItemKind.Module:
return ls.CompletionItemKind.Module;
case mItemKind.Property:
return ls.CompletionItemKind.Property;
case mItemKind.Unit:
return ls.CompletionItemKind.Unit;
case mItemKind.Value:
return ls.CompletionItemKind.Value;
case mItemKind.Enum:
return ls.CompletionItemKind.Enum;
case mItemKind.Keyword:
return ls.CompletionItemKind.Keyword;
case mItemKind.Snippet:
return ls.CompletionItemKind.Snippet;
case mItemKind.Color:
return ls.CompletionItemKind.Color;
case mItemKind.File:
return ls.CompletionItemKind.File;
case mItemKind.Reference:
return ls.CompletionItemKind.Reference;
}
return ls.CompletionItemKind.Property;
}
function toTextEdit(textEdit: ls.TextEdit): monaco.editor.ISingleEditOperation {
if (!textEdit) { if (!textEdit) {
return; return void 0;
} }
return { return {
range: toRange(textEdit.range), range: toRange(textEdit.range),
@ -207,107 +291,146 @@ function toTextEdit(textEdit: ls.TextEdit): editor.ISingleEditOperation {
}; };
} }
export function createCompletionItemProvider( export class CompletionAdapter
getWorker: WorkerAccessor, implements monaco.languages.CompletionItemProvider {
): languages.CompletionItemProvider { constructor(private _worker: WorkerAccessor) {}
return {
triggerCharacters: [' ', ':'],
async provideCompletionItems(model, position) { public get triggerCharacters(): string[] {
const resource = model.uri; return [' ', ':'];
}
const worker = await getWorker(resource); public provideCompletionItems(
const info = await worker.doComplete(String(resource), fromPosition(position)); model: monaco.editor.IReadOnlyModel,
if (!info) { position: Position,
return; context: monaco.languages.CompletionContext,
} token: CancellationToken
): Thenable<monaco.languages.CompletionList> {
const wordInfo = model.getWordUntilPosition(position);
const resource = model.uri;
const wordInfo = model.getWordUntilPosition(position); return this._worker(resource)
const wordRange = new Range( .then(worker => {
position.lineNumber, return worker.doComplete(resource.toString(), fromPosition(position));
wordInfo.startColumn, })
position.lineNumber, .then(info => {
wordInfo.endColumn, if (!info) {
); return;
}
const items: monaco.languages.CompletionItem[] = info.items.map(
entry => {
const item: monaco.languages.CompletionItem = {
label: entry.label,
insertText: entry.insertText || entry.label,
sortText: entry.sortText,
filterText: entry.filterText,
documentation: entry.documentation,
detail: entry.detail,
kind: toCompletionItemKind(entry.kind),
range: {
startLineNumber: position.lineNumber,
endLineNumber: position.lineNumber,
...wordInfo,
},
};
if (entry.textEdit) {
item.range = toRange(entry.textEdit.range);
item.insertText = entry.textEdit.newText;
}
if (entry.additionalTextEdits) {
item.additionalTextEdits = entry.additionalTextEdits.map(
toTextEdit
);
}
if (entry.insertTextFormat === ls.InsertTextFormat.Snippet) {
item.insertTextRules =
monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet;
}
return item;
}
);
const items = info.items.map((entry) => { return {
const item: languages.CompletionItem = { isIncomplete: info.isIncomplete,
label: entry.label, suggestions: items,
insertText: entry.insertText || entry.label,
sortText: entry.sortText,
filterText: entry.filterText,
documentation: entry.documentation,
detail: entry.detail,
kind: toCompletionItemKind(entry.kind),
range: wordRange,
}; };
if (entry.textEdit) {
item.range = toRange(
'range' in entry.textEdit ? entry.textEdit.range : entry.textEdit.replace,
);
item.insertText = entry.textEdit.newText;
}
if (entry.additionalTextEdits) {
item.additionalTextEdits = entry.additionalTextEdits.map(toTextEdit);
}
if (entry.insertTextFormat === ls.InsertTextFormat.Snippet) {
item.insertTextRules = languages.CompletionItemInsertTextRule.InsertAsSnippet;
}
return item;
}); });
}
return {
incomplete: info.isIncomplete,
suggestions: items,
};
},
};
} }
// --- definition ------ function isMarkupContent(thing: any): thing is ls.MarkupContent {
return (
thing &&
typeof thing === 'object' &&
typeof (thing as ls.MarkupContent).kind === 'string'
);
}
export function createDefinitionProvider(getWorker: WorkerAccessor): languages.DefinitionProvider { function toMarkdownString(
return { entry: ls.MarkupContent | ls.MarkedString
async provideDefinition(model, position) { ): monaco.IMarkdownString {
const resource = model.uri; if (typeof entry === 'string') {
return {
value: entry,
};
}
if (isMarkupContent(entry)) {
if (entry.kind === 'plaintext') {
return {
value: entry.value.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'),
};
}
return {
value: entry.value,
};
}
const worker = await getWorker(resource); return { value: '```' + entry.language + '\n' + entry.value + '\n```\n' };
const definitions = await worker.doDefinition(String(resource), fromPosition(position)); }
return definitions?.map((definition) => ({ function toMarkedStringArray(
originSelectionRange: definition.originSelectionRange, contents: ls.MarkupContent | ls.MarkedString | ls.MarkedString[]
range: toRange(definition.targetRange), ): monaco.IMarkdownString[] {
targetSelectionRange: definition.targetSelectionRange, if (!contents) {
uri: Uri.parse(definition.targetUri), return void 0;
})); }
}, if (Array.isArray(contents)) {
}; return contents.map(toMarkdownString);
}
return [toMarkdownString(contents)];
} }
// --- hover ------ // --- hover ------
export function createHoverProvider(getWorker: WorkerAccessor): languages.HoverProvider { export class HoverAdapter implements monaco.languages.HoverProvider {
return { constructor(private _worker: WorkerAccessor) {}
async provideHover(model, position) {
const resource = model.uri;
const worker = await getWorker(resource); public provideHover(
const info = await worker.doHover(String(resource), fromPosition(position)); model: monaco.editor.IReadOnlyModel,
if (!info) { position: Position,
return; token: CancellationToken
} ): Thenable<monaco.languages.Hover> {
return { const resource = model.uri;
range: toRange(info.range),
contents: [{ value: (info.contents as ls.MarkupContent).value }], return this._worker(resource)
}; .then(worker => {
}, return worker.doHover(resource.toString(), fromPosition(position));
}; })
.then(info => {
if (!info) {
return;
}
return {
range: toRange(info.range),
contents: toMarkedStringArray(info.contents),
} as monaco.languages.Hover;
});
}
} }
// --- document symbols ------ // --- document symbols ------
function toSymbolKind(kind: ls.SymbolKind): languages.SymbolKind { function toSymbolKind(kind: ls.SymbolKind): monaco.languages.SymbolKind {
const mKind = languages.SymbolKind; const mKind = monaco.languages.SymbolKind;
switch (kind) { switch (kind) {
case ls.SymbolKind.File: case ls.SymbolKind.File:
@ -346,43 +469,47 @@ function toSymbolKind(kind: ls.SymbolKind): languages.SymbolKind {
return mKind.Boolean; return mKind.Boolean;
case ls.SymbolKind.Array: case ls.SymbolKind.Array:
return mKind.Array; return mKind.Array;
default: }
return mKind.Function; return mKind.Function;
}
export class DocumentSymbolAdapter
implements monaco.languages.DocumentSymbolProvider {
constructor(private _worker: WorkerAccessor) {}
public provideDocumentSymbols(
model: monaco.editor.IReadOnlyModel,
token: CancellationToken
): Thenable<monaco.languages.DocumentSymbol[]> {
const resource = model.uri;
return this._worker(resource)
.then(worker => worker.findDocumentSymbols(resource.toString()))
.then(items => {
if (!items) {
return;
}
return items.map(item => toDocumentSymbol(item));
});
} }
} }
function toDocumentSymbol(item: ls.DocumentSymbol): languages.DocumentSymbol { function toDocumentSymbol(
item: ls.DocumentSymbol
): monaco.languages.DocumentSymbol {
return { return {
detail: item.detail || '', detail: '',
range: toRange(item.range), range: toRange(item.range),
name: item.name, name: item.name,
kind: toSymbolKind(item.kind), kind: toSymbolKind(item.kind),
selectionRange: toRange(item.selectionRange), selectionRange: toRange(item.selectionRange),
children: item.children.map(toDocumentSymbol), children: item.children.map(child => toDocumentSymbol(child)),
tags: [],
};
}
export function createDocumentSymbolProvider(
getWorker: WorkerAccessor,
): languages.DocumentSymbolProvider {
return {
async provideDocumentSymbols(model) {
const resource = model.uri;
const worker = await getWorker(resource);
const items = await worker.findDocumentSymbols(String(resource));
if (!items) {
return;
}
return items.map(toDocumentSymbol);
},
}; };
} }
function fromFormattingOptions( function fromFormattingOptions(
options: languages.FormattingOptions, options: monaco.languages.FormattingOptions
): CustomFormatterOptions & ls.FormattingOptions { ): ls.FormattingOptions & CustomFormatterOptions {
return { return {
tabSize: options.tabSize, tabSize: options.tabSize,
insertSpaces: options.insertSpaces, insertSpaces: options.insertSpaces,
@ -390,42 +517,115 @@ function fromFormattingOptions(
}; };
} }
export function createDocumentFormattingEditProvider( export class DocumentFormattingEditProvider
getWorker: WorkerAccessor, implements monaco.languages.DocumentFormattingEditProvider {
): languages.DocumentFormattingEditProvider { constructor(private _worker: WorkerAccessor) {}
return {
async provideDocumentFormattingEdits(model, options) {
const resource = model.uri;
const worker = await getWorker(resource); public provideDocumentFormattingEdits(
const edits = await worker.format(String(resource), fromFormattingOptions(options)); model: monaco.editor.IReadOnlyModel,
if (!edits || edits.length === 0) { options: monaco.languages.FormattingOptions,
return; token: CancellationToken
} ): Thenable<monaco.editor.ISingleEditOperation[]> {
return edits.map(toTextEdit); const resource = model.uri;
},
}; return this._worker(resource).then(worker => {
return worker
.format(resource.toString(), null, fromFormattingOptions(options))
.then(edits => {
if (!edits || edits.length === 0) {
return;
}
return edits.map(toTextEdit);
});
});
}
} }
function toLink(link: ls.DocumentLink): languages.ILink { export class DocumentRangeFormattingEditProvider
return { implements monaco.languages.DocumentRangeFormattingEditProvider {
range: toRange(link.range), constructor(private _worker: WorkerAccessor) {}
tooltip: link.tooltip,
url: link.target, public provideDocumentRangeFormattingEdits(
}; model: monaco.editor.IReadOnlyModel,
range: Range,
options: monaco.languages.FormattingOptions,
token: CancellationToken
): Thenable<monaco.editor.ISingleEditOperation[]> {
const resource = model.uri;
return this._worker(resource).then(worker => {
return worker
.format(
resource.toString(),
fromRange(range),
fromFormattingOptions(options)
)
.then(edits => {
if (!edits || edits.length === 0) {
return;
}
return edits.map(toTextEdit);
});
});
}
} }
export function createLinkProvider(getWorker: WorkerAccessor): languages.LinkProvider { // export class DocumentColorAdapter
return { // implements monaco.languages.DocumentColorProvider {
async provideLinks(model) { // constructor(private _worker: WorkerAccessor) {}
const resource = model.uri;
const worker = await getWorker(resource); // public provideDocumentColors(
const links = await worker.findLinks(String(resource)); // model: monaco.editor.IReadOnlyModel,
// token: CancellationToken
// ): Thenable<monaco.languages.IColorInformation[]> {
// const resource = model.uri;
return { // return this._worker(resource)
links: links.map(toLink), // .then(worker => worker.findDocumentColors(resource.toString()))
}; // .then(infos => {
}, // if (!infos) {
}; // return;
} // }
// return infos.map(item => ({
// color: item.color,
// range: toRange(item.range),
// }));
// });
// }
// public provideColorPresentations(
// model: monaco.editor.IReadOnlyModel,
// info: monaco.languages.IColorInformation,
// token: CancellationToken
// ): Thenable<monaco.languages.IColorPresentation[]> {
// const resource = model.uri;
// return this._worker(resource)
// .then(worker =>
// worker.getColorPresentations(
// resource.toString(),
// info.color,
// fromRange(info.range)
// )
// )
// .then(presentations => {
// if (!presentations) {
// return;
// }
// return presentations.map(presentation => {
// const item: monaco.languages.IColorPresentation = {
// label: presentation.label,
// };
// if (presentation.textEdit) {
// item.textEdit = toTextEdit(presentation.textEdit);
// }
// if (presentation.additionalTextEdits) {
// item.additionalTextEdits = presentation.additionalTextEdits.map(
// toTextEdit
// );
// }
// return item;
// });
// });
// }
// }

View file

@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export type ASTNode =
| ObjectASTNode
| PropertyASTNode
| ArrayASTNode
| StringASTNode
| NumberASTNode
| BooleanASTNode
| NullASTNode;
export interface BaseASTNode {
readonly type:
| 'object'
| 'array'
| 'property'
| 'string'
| 'number'
| 'boolean'
| 'null';
readonly parent?: ASTNode;
readonly offset: number;
readonly length: number;
readonly children?: ASTNode[];
readonly value?: string | boolean | number | null;
}
export interface ObjectASTNode extends BaseASTNode {
readonly type: 'object';
readonly properties: PropertyASTNode[];
readonly children: ASTNode[];
}
export interface PropertyASTNode extends BaseASTNode {
readonly type: 'property';
readonly keyNode: StringASTNode;
readonly valueNode?: ASTNode;
readonly colonOffset?: number;
readonly children: ASTNode[];
}
export interface ArrayASTNode extends BaseASTNode {
readonly type: 'array';
readonly items: ASTNode[];
readonly children: ASTNode[];
}
export interface StringASTNode extends BaseASTNode {
readonly type: 'string';
readonly value: string;
}
export interface NumberASTNode extends BaseASTNode {
readonly type: 'number';
readonly value: number;
readonly isInteger: boolean;
}
export interface BooleanASTNode extends BaseASTNode {
readonly type: 'boolean';
readonly value: boolean;
}
export interface NullASTNode extends BaseASTNode {
readonly type: 'null';
readonly value: null;
}

View file

@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* 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 interface JSONSchema {
id?: string;
$schema?: string;
type?: string | string[];
title?: string;
// tslint:disable-next-line: no-any
default?: any;
definitions?: JSONSchemaMap;
description?: string;
properties?: JSONSchemaMap;
patternProperties?: JSONSchemaMap;
// tslint:disable-next-line: no-any
additionalProperties?: any;
minProperties?: number;
maxProperties?: number;
dependencies?: JSONSchemaMap | string[];
// tslint:disable-next-line: no-any
items?: any;
minItems?: number;
maxItems?: number;
uniqueItems?: boolean;
additionalItems?: boolean;
pattern?: string;
minLength?: number;
maxLength?: number;
minimum?: number;
maximum?: number;
exclusiveMinimum?: boolean;
exclusiveMaximum?: boolean;
multipleOf?: number;
required?: string[];
$ref?: string;
anyOf?: JSONSchema[];
allOf?: JSONSchema[];
oneOf?: JSONSchema[];
not?: JSONSchema;
// tslint:disable-next-line: no-any
enum?: any[];
format?: string;
errorMessage?: string; // VSCode extension
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
}
export interface JSONSchemaMap {
[name: string]: JSONSchema;
}

View file

@ -0,0 +1,85 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export type JSONSchemaRef = JSONSchema | boolean;
export interface JSONSchema {
id?: string;
$id?: string;
$schema?: string;
type?: string | string[];
title?: string;
// tslint:disable-next-line: no-any
default?: any;
definitions?: { [name: string]: JSONSchema };
description?: string;
properties?: JSONSchemaMap;
patternProperties?: JSONSchemaMap;
additionalProperties?: boolean | JSONSchemaRef;
minProperties?: number;
maxProperties?: number;
dependencies?: JSONSchemaMap | { [prop: string]: string[] };
items?: JSONSchemaRef | JSONSchemaRef[];
minItems?: number;
maxItems?: number;
uniqueItems?: boolean;
additionalItems?: boolean | JSONSchemaRef;
pattern?: string;
minLength?: number;
maxLength?: number;
minimum?: number;
maximum?: number;
exclusiveMinimum?: boolean | number;
exclusiveMaximum?: boolean | number;
multipleOf?: number;
required?: string[];
$ref?: string;
anyOf?: JSONSchemaRef[];
allOf?: JSONSchemaRef[];
oneOf?: JSONSchemaRef[];
not?: JSONSchemaRef;
// tslint:disable-next-line: no-any
enum?: any[];
format?: string;
// schema draft 06
// tslint:disable-next-line: no-any
const?: any;
contains?: JSONSchemaRef;
propertyNames?: JSONSchemaRef;
// tslint:disable-next-line: no-any
examples?: any[];
// schema draft 07
$comment?: string;
if?: JSONSchemaRef;
then?: JSONSchemaRef;
else?: JSONSchemaRef;
// VSCode extensions
defaultSnippets?: {
label?: string;
description?: string;
markdownDescription?: string;
// tslint:disable-next-line: no-any
body?: any;
bodyText?: string;
}[]; // VSCode extension: body: a object that will be converted to a JSON string. bodyText: text with \t and \n
errorMessage?: string; // VSCode extension
patternErrorMessage?: string; // VSCode extension
deprecationMessage?: string; // VSCode extension
enumDescriptions?: string[]; // VSCode extension
markdownEnumDescriptions?: string[]; // VSCode extension
markdownDescription?: string; // VSCode extension
doNotSuggest?: boolean; // VSCode extension
allowComments?: boolean; // VSCode extension
schemaSequence?: JSONSchema[]; // extension for multiple schemas related to multiple documents in single yaml file
}
export interface JSONSchemaMap {
[name: string]: JSONSchemaRef;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,43 @@
/**
* Parse a boolean according to the specification
*
* Return:
* true if its a true value
* false if its a false value
*/
export function parseYamlBoolean(input: string): boolean {
if (
[
'true',
'True',
'TRUE',
'y',
'Y',
'yes',
'Yes',
'YES',
'on',
'On',
'ON',
].lastIndexOf(input) >= 0
) {
return true;
} else if (
[
'false',
'False',
'FALSE',
'n',
'N',
'no',
'No',
'NO',
'off',
'Off',
'OFF',
].lastIndexOf(input) >= 0
) {
return false;
}
throw `Invalid boolean "${input}"`;
}

View file

@ -0,0 +1,398 @@
/*---------------------------------------------------------------------------------------------
* 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 {
ASTNode,
ErrorCode,
BooleanASTNode,
NullASTNode,
ArrayASTNode,
NumberASTNode,
ObjectASTNode,
PropertyASTNode,
StringASTNode,
JSONDocument,
} from './jsonParser04';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import * as Yaml from '../../yaml-ast-parser-custom-tags/index';
import { Schema, Type } from 'js-yaml';
import {
getLineStartPositions,
getPosition,
} from '../utils/documentPositionCalculator';
import { parseYamlBoolean } from './scalar-type';
import { filterInvalidCustomTags } from '../utils/arrUtils';
export class SingleYAMLDocument extends JSONDocument {
private lines;
public root;
public errors;
public warnings;
constructor(lines: number[]) {
super(null, []);
this.lines = lines;
this.root = null;
this.errors = [];
this.warnings = [];
}
public getSchemas(schema, doc, node) {
const matchingSchemas = [];
doc.validate(schema, matchingSchemas, node.start);
return matchingSchemas;
}
public getNodeFromOffset(offset: number): ASTNode {
return this.getNodeFromOffsetEndInclusive(offset);
}
}
function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
if (!node) {
return;
}
switch (node.kind) {
case Yaml.Kind.MAP: {
const instance = <Yaml.YamlMap>node;
const result = new ObjectASTNode(
parent,
null,
node.startPosition,
node.endPosition
);
for (const mapping of instance.mappings) {
result.addProperty(
<PropertyASTNode>recursivelyBuildAst(result, mapping)
);
}
return result;
}
case Yaml.Kind.MAPPING: {
const instance = <Yaml.YAMLMapping>node;
const key = instance.key;
// Technically, this is an arbitrary node in YAML
// I doubt we would get a better string representation by parsing it
const keyNode = new StringASTNode(
null,
null,
true,
key.startPosition,
key.endPosition
);
keyNode.value = key.value;
const result = new PropertyASTNode(parent, keyNode);
result.end = instance.endPosition;
const valueNode = instance.value
? recursivelyBuildAst(result, instance.value)
: new NullASTNode(
parent,
key.value,
instance.endPosition,
instance.endPosition
);
valueNode.location = key.value;
result.setValue(valueNode);
return result;
}
case Yaml.Kind.SEQ: {
const instance = <Yaml.YAMLSequence>node;
const result = new ArrayASTNode(
parent,
null,
instance.startPosition,
instance.endPosition
);
let count = 0;
for (const item of instance.items) {
if (item === null && count === instance.items.length - 1) {
break;
}
// Be aware of https://github.com/nodeca/js-yaml/issues/321
// Cannot simply work around it here because we need to know if we are in Flow or Block
const itemNode =
item === null
? new NullASTNode(
parent,
null,
instance.endPosition,
instance.endPosition
)
: recursivelyBuildAst(result, item);
itemNode.location = count++;
result.addItem(itemNode);
}
return result;
}
case Yaml.Kind.SCALAR: {
const instance = <Yaml.YAMLScalar>node;
const type = Yaml.determineScalarType(instance);
// The name is set either by the sequence or the mapping case.
const name = null;
const value = instance.value;
//This is a patch for redirecting values with these strings to be boolean nodes because its not supported in the parser.
const possibleBooleanValues = [
'y',
'Y',
'yes',
'Yes',
'YES',
'n',
'N',
'no',
'No',
'NO',
'on',
'On',
'ON',
'off',
'Off',
'OFF',
];
if (
instance.plainScalar &&
possibleBooleanValues.indexOf(value.toString()) !== -1
) {
return new BooleanASTNode(
parent,
name,
parseYamlBoolean(value),
node.startPosition,
node.endPosition
);
}
switch (type) {
case Yaml.ScalarType.null: {
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
);
}
case Yaml.ScalarType.int: {
const result = new NumberASTNode(
parent,
name,
node.startPosition,
node.endPosition
);
result.value = Yaml.parseYamlInteger(value);
result.isInteger = true;
return result;
}
case Yaml.ScalarType.float: {
const result = new NumberASTNode(
parent,
name,
node.startPosition,
node.endPosition
);
result.value = Yaml.parseYamlFloat(value);
result.isInteger = false;
return result;
}
case Yaml.ScalarType.string: {
const result = new StringASTNode(
parent,
name,
false,
node.startPosition,
node.endPosition
);
result.value = node.value;
return result;
}
}
break;
}
case Yaml.Kind.ANCHOR_REF: {
const instance = (<Yaml.YAMLAnchorReference>node).value;
return (
recursivelyBuildAst(parent, instance) ||
new NullASTNode(parent, null, node.startPosition, node.endPosition)
);
}
case Yaml.Kind.INCLUDE_REF: {
const result = new StringASTNode(
parent,
null,
false,
node.startPosition,
node.endPosition
);
result.value = node.value;
return result;
}
}
}
function convertError(e: Yaml.Error) {
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[],
text: string
) {
const _doc = new SingleYAMLDocument(startPositions);
_doc.root = recursivelyBuildAst(null, yamlDoc);
if (!_doc.root) {
// TODO: When this is true, consider not pushing the other errors.
_doc.errors.push({
message: localize(
'Invalid symbol',
'Expected a YAML object, array or literal'
),
code: ErrorCode.Undefined,
location: { start: yamlDoc.startPosition, end: yamlDoc.endPosition },
});
}
const duplicateKeyReason = 'duplicate key';
//Patch ontop of yaml-ast-parser to disable duplicate key message on merge key
const isDuplicateAndNotMergeKey = function(
error: Yaml.Error,
yamlText: string
) {
const errorConverted = convertError(error);
const errorStart = errorConverted.location.start;
const 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 &&
isDuplicateAndNotMergeKey(e, text)) ||
e.isWarning
)
.map(e => convertError(e));
errors.forEach(e => _doc.errors.push(e));
warnings.forEach(e => _doc.warnings.push(e));
return _doc;
}
export class YAMLDocument {
public documents: JSONDocument[];
private errors;
private warnings;
constructor(documents: JSONDocument[]) {
this.documents = documents;
this.errors = [];
this.warnings = [];
}
}
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 = [];
const filteredTags = filterInvalidCustomTags(customTags);
const schemaWithAdditionalTags = Schema.create(
filteredTags.map(tag => {
const typeInfo = tag.split(' ');
return new Type(typeInfo[0], {
kind: (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar',
});
})
);
/**
* Collect the additional tags into a map of string to possible tag types
*/
const tagWithAdditionalItems = new Map<string, string[]>();
filteredTags.forEach(tag => {
const typeInfo = tag.split(' ');
const tagName = typeInfo[0];
const tagType = (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar';
if (tagWithAdditionalItems.has(tagName)) {
tagWithAdditionalItems.set(
tagName,
tagWithAdditionalItems.get(tagName).concat([tagType])
);
} else {
tagWithAdditionalItems.set(tagName, [tagType]);
}
});
tagWithAdditionalItems.forEach((additionalTagKinds, key) => {
const newTagType = new Type(key, {
kind: additionalTagKinds[0] || 'scalar',
});
newTagType.additionalKinds = additionalTagKinds;
schemaWithAdditionalTags.compiledTypeMap[key] = newTagType;
});
const additionalOptions: Yaml.LoadOptions = {
schema: schemaWithAdditionalTags,
};
Yaml.loadAll(text, doc => yamlDocs.push(doc), additionalOptions);
return new YAMLDocument(
yamlDocs.map(doc => createJSONDocument(doc, startPositions, text))
);
}

View file

@ -0,0 +1,389 @@
/*---------------------------------------------------------------------------------------------
* 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 {
JSONDocument,
NullASTNodeImpl,
PropertyASTNodeImpl,
StringASTNodeImpl,
ObjectASTNodeImpl,
NumberASTNodeImpl,
ArrayASTNodeImpl,
BooleanASTNodeImpl,
} from './jsonParser07';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import * as Yaml from '../../yaml-ast-parser-custom-tags/index';
import { Schema, Type } from 'js-yaml';
import { getLineStartPositions } from '../utils/documentPositionCalculator';
import { parseYamlBoolean } from './scalar-type';
import { filterInvalidCustomTags } from '../utils/arrUtils';
import { ASTNode } from '../jsonASTTypes';
import { ErrorCode } from 'vscode-json-languageservice';
export class SingleYAMLDocument extends JSONDocument {
private lines;
public root;
public errors;
public warnings;
public isKubernetes: boolean;
public currentDocIndex: number;
constructor(lines: number[]) {
super(null, []);
this.lines = lines;
this.root = null;
this.errors = [];
this.warnings = [];
}
public getSchemas(schema, doc, node) {
const matchingSchemas = [];
doc.validate(schema, matchingSchemas, node.start);
return matchingSchemas;
}
}
function recursivelyBuildAst(parent: ASTNode, node: Yaml.YAMLNode): ASTNode {
if (!node) {
return;
}
switch (node.kind) {
case Yaml.Kind.MAP: {
const instance = <Yaml.YamlMap>node;
const result = new ObjectASTNodeImpl(
parent,
node.startPosition,
node.endPosition - node.startPosition
);
for (const mapping of instance.mappings) {
result.properties.push(
<PropertyASTNodeImpl>recursivelyBuildAst(result, mapping)
);
}
return result;
}
case Yaml.Kind.MAPPING: {
const instance = <Yaml.YAMLMapping>node;
const key = instance.key;
const result = new PropertyASTNodeImpl(
parent as ObjectASTNodeImpl,
instance.startPosition,
instance.endPosition - instance.startPosition
);
// Technically, this is an arbitrary node in YAML
// I doubt we would get a better string representation by parsing it
const keyNode = new StringASTNodeImpl(
result,
key.startPosition,
key.endPosition - key.startPosition
);
keyNode.value = key.value;
const valueNode = instance.value
? recursivelyBuildAst(result, instance.value)
: new NullASTNodeImpl(parent, instance.endPosition, 0);
//valueNode.location = key.value;
result.keyNode = keyNode;
result.valueNode = valueNode;
return result;
}
case Yaml.Kind.SEQ: {
const instance = <Yaml.YAMLSequence>node;
const result = new ArrayASTNodeImpl(
parent,
instance.startPosition,
instance.endPosition - instance.startPosition
);
const count = 0;
for (const item of instance.items) {
if (item === null && count === instance.items.length - 1) {
break;
}
// Be aware of https://github.com/nodeca/js-yaml/issues/321
// Cannot simply work around it here because we need to know if we are in Flow or Block
const itemNode =
item === null
? new NullASTNodeImpl(parent, instance.endPosition, 0)
: recursivelyBuildAst(result, item);
// itemNode.location = count++;
result.children.push(itemNode);
}
return result;
}
case Yaml.Kind.SCALAR: {
const instance = <Yaml.YAMLScalar>node;
const type = Yaml.determineScalarType(instance);
// The name is set either by the sequence or the mapping case.
const name = null;
const value = instance.value;
//This is a patch for redirecting values with these strings to be boolean nodes because its not supported in the parser.
const possibleBooleanValues = [
'y',
'Y',
'yes',
'Yes',
'YES',
'n',
'N',
'no',
'No',
'NO',
'on',
'On',
'ON',
'off',
'Off',
'OFF',
];
if (
instance.plainScalar &&
possibleBooleanValues.indexOf(value.toString()) !== -1
) {
return new BooleanASTNodeImpl(
parent,
parseYamlBoolean(value),
node.startPosition,
node.endPosition - node.startPosition
);
}
switch (type) {
case Yaml.ScalarType.null: {
return new StringASTNodeImpl(
parent,
instance.startPosition,
instance.endPosition - instance.startPosition
);
}
case Yaml.ScalarType.bool: {
return new BooleanASTNodeImpl(
parent,
Yaml.parseYamlBoolean(value),
node.startPosition,
node.endPosition - node.startPosition
);
}
case Yaml.ScalarType.int: {
const result = new NumberASTNodeImpl(
parent,
node.startPosition,
node.endPosition - node.startPosition
);
result.value = Yaml.parseYamlInteger(value);
result.isInteger = true;
return result;
}
case Yaml.ScalarType.float: {
const result = new NumberASTNodeImpl(
parent,
node.startPosition,
node.endPosition - node.startPosition
);
result.value = Yaml.parseYamlFloat(value);
result.isInteger = false;
return result;
}
case Yaml.ScalarType.string: {
const result = new StringASTNodeImpl(
parent,
node.startPosition,
node.endPosition - node.startPosition
);
result.value = node.value;
return result;
}
}
break;
}
case Yaml.Kind.ANCHOR_REF: {
const instance = (<Yaml.YAMLAnchorReference>node).value;
return (
recursivelyBuildAst(parent, instance) ||
new NullASTNodeImpl(
parent,
node.startPosition,
node.endPosition - node.startPosition
)
);
}
case Yaml.Kind.INCLUDE_REF: {
const result = new StringASTNodeImpl(
parent,
node.startPosition,
node.endPosition - node.startPosition
);
result.value = node.value;
return result;
}
}
}
function convertError(e: Yaml.Error) {
const line = e.mark.line === 0 ? 0 : e.mark.line - 1;
const character =
e.mark.position + e.mark.column === 0
? 0
: e.mark.position + e.mark.column - 1;
return {
message: `${e.reason}`,
range: {
start: {
line,
character,
},
end: {
line,
character,
},
},
severity: 2,
};
}
function createJSONDocument(
yamlDoc: Yaml.YAMLNode,
startPositions: number[],
text: string
) {
const _doc = new SingleYAMLDocument(startPositions);
_doc.root = recursivelyBuildAst(null, yamlDoc);
if (!_doc.root) {
// TODO: When this is true, consider not pushing the other errors.
_doc.errors.push({
message: localize(
'Invalid symbol',
'Expected a YAML object, array or literal'
),
code: ErrorCode.Undefined,
location: { start: yamlDoc.startPosition, end: yamlDoc.endPosition },
});
}
const duplicateKeyReason = 'duplicate key';
//Patch ontop of yaml-ast-parser to disable duplicate key message on merge key
const isDuplicateAndNotMergeKey = function(
error: Yaml.Error,
yamlText: string
) {
const errorConverted = convertError(error);
const errorStart = errorConverted.range.start.character;
const errorEnd = errorConverted.range.end.character;
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 &&
isDuplicateAndNotMergeKey(e, text)) ||
e.isWarning
)
.map(e => convertError(e));
errors.forEach(e => _doc.errors.push(e));
warnings.forEach(e => _doc.warnings.push(e));
return _doc;
}
export class YAMLDocument {
public documents: SingleYAMLDocument[];
private errors;
private warnings;
constructor(documents: SingleYAMLDocument[]) {
this.documents = documents;
this.errors = [];
this.warnings = [];
}
}
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 = [];
const filteredTags = filterInvalidCustomTags(customTags);
const schemaWithAdditionalTags = Schema.create(
filteredTags.map(tag => {
const typeInfo = tag.split(' ');
return new Type(typeInfo[0], {
kind: (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar',
});
})
);
/**
* Collect the additional tags into a map of string to possible tag types
*/
const tagWithAdditionalItems = new Map<string, string[]>();
filteredTags.forEach(tag => {
const typeInfo = tag.split(' ');
const tagName = typeInfo[0];
const tagType = (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar';
if (tagWithAdditionalItems.has(tagName)) {
tagWithAdditionalItems.set(
tagName,
tagWithAdditionalItems.get(tagName).concat([tagType])
);
} else {
tagWithAdditionalItems.set(tagName, [tagType]);
}
});
tagWithAdditionalItems.forEach((additionalTagKinds, key) => {
const newTagType = new Type(key, {
kind: additionalTagKinds[0] || 'scalar',
});
newTagType.additionalKinds = additionalTagKinds;
schemaWithAdditionalTags.compiledTypeMap[key] = newTagType;
});
const additionalOptions: Yaml.LoadOptions = {
schema: schemaWithAdditionalTags,
};
Yaml.loadAll(text, doc => yamlDocs.push(doc), additionalOptions);
return new YAMLDocument(
yamlDocs.map(doc => createJSONDocument(doc, startPositions, text))
);
}

View file

@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* 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 { parse as parseYAML } from '../parser/yamlParser07';
import {
SymbolInformation,
TextDocument,
DocumentSymbol,
} from 'vscode-languageserver-types';
import { YAMLSchemaService } from './yamlSchemaService';
import { JSONDocumentSymbols } from 'vscode-json-languageservice/lib/umd/services/jsonDocumentSymbols';
export class YAMLDocumentSymbols {
private jsonDocumentSymbols;
constructor(schemaService: YAMLSchemaService) {
this.jsonDocumentSymbols = new JSONDocumentSymbols(schemaService);
}
public findDocumentSymbols(document: TextDocument): SymbolInformation[] {
const doc = parseYAML(document.getText());
if (!doc || doc['documents'].length === 0) {
return null;
}
let results = [];
for (const yamlDoc of doc['documents']) {
if (yamlDoc.root) {
results = results.concat(
this.jsonDocumentSymbols.findDocumentSymbols(document, yamlDoc)
);
}
}
return results;
}
public findHierarchicalDocumentSymbols(
document: TextDocument
): DocumentSymbol[] {
const doc = parseYAML(document.getText());
if (!doc || doc['documents'].length === 0) {
return null;
}
let results = [];
for (const yamlDoc of doc['documents']) {
if (yamlDoc.root) {
results = results.concat(
this.jsonDocumentSymbols.findDocumentSymbols2(document, yamlDoc)
);
}
}
return results;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* 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 {
TextDocument,
Range,
Position,
TextEdit,
} from 'vscode-languageserver-types';
import {
CustomFormatterOptions,
LanguageSettings,
} from '../yamlLanguageService';
export class YAMLFormatter {
private formatterEnabled: boolean = true;
public configure(shouldFormat: LanguageSettings) {
if (shouldFormat) {
this.formatterEnabled = shouldFormat.format;
}
}
public format(
document: TextDocument,
options: CustomFormatterOptions
): TextEdit[] {
if (!this.formatterEnabled) {
return [];
}
try {
const prettier = require('prettier/standalone');
const parser = require('prettier/parser-yaml');
const text = document.getText();
const formatted = prettier.format(
text,
Object.assign(options, { parser: 'yaml', plugins: [parser] })
);
return [
TextEdit.replace(
Range.create(Position.create(0, 0), document.positionAt(text.length)),
formatted
),
];
} catch (error) {
return [];
}
}
}

View file

@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* 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 {
PromiseConstructor,
Thenable,
LanguageService,
} from 'vscode-json-languageservice';
import { Hover, TextDocument, Position } from 'vscode-languageserver-types';
import { matchOffsetToDocument2 } from '../utils/arrUtils';
import { LanguageSettings } from '../yamlLanguageService';
import { parse as parseYAML } from '../parser/yamlParser07';
import { YAMLSchemaService } from './yamlSchemaService';
import { JSONHover } from 'vscode-json-languageservice/lib/umd/services/jsonHover';
export class YAMLHover {
private promise: PromiseConstructor;
private shouldHover: boolean;
private jsonHover;
constructor(
schemaService: YAMLSchemaService,
promiseConstructor: PromiseConstructor
) {
this.promise = promiseConstructor || Promise;
this.shouldHover = true;
this.jsonHover = new JSONHover(schemaService, [], Promise);
}
public configure(languageSettings: LanguageSettings) {
if (languageSettings) {
this.shouldHover = languageSettings.hover;
}
}
public doHover(document: TextDocument, position: Position): Thenable<Hover> {
if (!this.shouldHover || !document) {
return this.promise.resolve(void 0);
}
const doc = parseYAML(document.getText());
const offset = document.offsetAt(position);
const currentDoc = matchOffsetToDocument2(offset, doc);
if (currentDoc === null) {
return this.promise.resolve(void 0);
}
const currentDocIndex = doc.documents.indexOf(currentDoc);
currentDoc.currentDocIndex = currentDocIndex;
return this.jsonHover.doHover(document, position, currentDoc);
}
}

View file

@ -0,0 +1,389 @@
/*---------------------------------------------------------------------------------------------
* 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 { JSONSchema, JSONSchemaMap, JSONSchemaRef } from '../jsonSchema07';
import {
SchemaRequestService,
WorkspaceContextService,
PromiseConstructor,
Thenable,
} from '../yamlLanguageService';
import {
UnresolvedSchema,
ResolvedSchema,
JSONSchemaService,
SchemaDependencies,
ISchemaContributions,
} from 'vscode-json-languageservice/lib/umd/services/jsonSchemaService';
import * as nls from 'vscode-nls';
import { convertSimple2RegExpPattern } from '../utils/strings';
const localize = nls.loadMessageBundle();
export declare type CustomSchemaProvider = (uri: string) => Thenable<string>;
export class FilePatternAssociation {
private schemas: string[];
private patternRegExp: RegExp;
constructor(pattern: string) {
try {
this.patternRegExp = new RegExp(
convertSimple2RegExpPattern(pattern) + '$'
);
} catch (e) {
// invalid pattern
this.patternRegExp = null;
}
this.schemas = [];
}
public addSchema(id: string) {
this.schemas.push(id);
}
public matchesPattern(fileName: string): boolean {
return this.patternRegExp && this.patternRegExp.test(fileName);
}
public getSchemas() {
return this.schemas;
}
}
export class YAMLSchemaService extends JSONSchemaService {
private customSchemaProvider: CustomSchemaProvider | undefined;
private filePatternAssociations: FilePatternAssociation[];
private contextService: WorkspaceContextService;
constructor(
requestService: SchemaRequestService,
contextService?: WorkspaceContextService,
promiseConstructor?: PromiseConstructor
) {
super(requestService, contextService, promiseConstructor);
this.customSchemaProvider = undefined;
}
registerCustomSchemaProvider(customSchemaProvider: CustomSchemaProvider) {
this.customSchemaProvider = customSchemaProvider;
}
//tslint:disable
public resolveSchemaContent(
schemaToResolve: UnresolvedSchema,
schemaURL: string,
dependencies: SchemaDependencies
): Thenable<ResolvedSchema> {
let resolveErrors: string[] = schemaToResolve.errors.slice(0);
let schema = schemaToResolve.schema;
let contextService = this.contextService;
let findSection = (schema: JSONSchema, path: string): any => {
if (!path) {
return schema;
}
let current: any = schema;
if (path[0] === '/') {
path = path.substr(1);
}
path.split('/').some(part => {
current = current[part];
return !current;
});
return current;
};
let merge = (
target: JSONSchema,
sourceRoot: JSONSchema,
sourceURI: string,
path: string
): void => {
let section = findSection(sourceRoot, path);
if (section) {
for (let key in section) {
if (section.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
target[key] = section[key];
}
}
} else {
resolveErrors.push(
localize(
'json.schema.invalidref',
"$ref '{0}' in '{1}' can not be resolved.",
path,
sourceURI
)
);
}
};
let resolveExternalLink = (
node: JSONSchema,
uri: string,
linkPath: string,
parentSchemaURL: string,
parentSchemaDependencies: SchemaDependencies
): Thenable<any> => {
if (contextService && !/^\w+:\/\/.*/.test(uri)) {
uri = contextService.resolveRelativePath(uri, parentSchemaURL);
}
uri = this.normalizeId(uri);
const referencedHandle = this.getOrAddSchemaHandle(uri);
return referencedHandle.getUnresolvedSchema().then(unresolvedSchema => {
parentSchemaDependencies[uri] = true;
if (unresolvedSchema.errors.length) {
let loc = linkPath ? uri + '#' + linkPath : uri;
resolveErrors.push(
localize(
'json.schema.problemloadingref',
"Problems loading reference '{0}': {1}",
loc,
unresolvedSchema.errors[0]
)
);
}
merge(node, unresolvedSchema.schema, uri, linkPath);
return resolveRefs(
node,
unresolvedSchema.schema,
uri,
referencedHandle.dependencies
);
});
};
let resolveRefs = (
node: JSONSchema,
parentSchema: JSONSchema,
parentSchemaURL: string,
parentSchemaDependencies: SchemaDependencies
): Thenable<any> => {
if (!node || typeof node !== 'object') {
return Promise.resolve(null);
}
let toWalk: JSONSchema[] = [node];
let seen: JSONSchema[] = [];
let openPromises: Thenable<any>[] = [];
let collectEntries = (...entries: JSONSchemaRef[]) => {
for (let entry of entries) {
if (typeof entry === 'object') {
toWalk.push(entry);
}
}
};
let collectMapEntries = (...maps: JSONSchemaMap[]) => {
for (let map of maps) {
if (typeof map === 'object') {
for (let key in map) {
let entry = map[key];
if (typeof entry === 'object') {
toWalk.push(entry);
}
}
}
}
};
let collectArrayEntries = (...arrays: JSONSchemaRef[][]) => {
for (let array of arrays) {
if (Array.isArray(array)) {
for (let entry of array) {
if (typeof entry === 'object') {
toWalk.push(entry);
}
}
}
}
};
let handleRef = (next: JSONSchema) => {
let seenRefs = [];
while (next.$ref) {
const ref = next.$ref;
let segments = ref.split('#', 2);
delete next.$ref;
if (segments[0].length > 0) {
openPromises.push(
resolveExternalLink(
next,
segments[0],
segments[1],
parentSchemaURL,
parentSchemaDependencies
)
);
return;
} else {
if (seenRefs.indexOf(ref) === -1) {
merge(next, parentSchema, parentSchemaURL, segments[1]); // can set next.$ref again, use seenRefs to avoid circle
seenRefs.push(ref);
}
}
}
collectEntries(
<JSONSchema>next.items,
<JSONSchema>next.additionalProperties,
next.not,
next.contains,
next.propertyNames,
next.if,
next.then,
next.else
);
collectMapEntries(
next.definitions,
next.properties,
next.patternProperties,
<JSONSchemaMap>next.dependencies
);
collectArrayEntries(
next.anyOf,
next.allOf,
next.oneOf,
<JSONSchema[]>next.items,
next.schemaSequence
);
};
while (toWalk.length) {
let next = toWalk.pop();
if (seen.indexOf(next) >= 0) {
continue;
}
seen.push(next);
handleRef(next);
}
return Promise.all(openPromises);
};
return resolveRefs(schema, schema, schemaURL, dependencies).then(
_ => new ResolvedSchema(schema, resolveErrors)
);
}
//tslint:enable
public getSchemaForResource(
resource: string,
doc = undefined
): Thenable<ResolvedSchema> {
const resolveSchema = () => {
const seen: { [schemaId: string]: boolean } = Object.create(null);
const schemas: string[] = [];
for (const entry of this.filePatternAssociations) {
if (entry.matchesPattern(resource)) {
for (const schemaId of entry.getSchemas()) {
if (!seen[schemaId]) {
schemas.push(schemaId);
seen[schemaId] = true;
}
}
}
}
if (schemas.length > 0) {
return super
.createCombinedSchema(resource, schemas)
.getResolvedSchema()
.then(schema => {
if (
schema.schema &&
schema.schema.schemaSequence &&
schema.schema.schemaSequence[doc.currentDocIndex]
) {
return new ResolvedSchema(
schema.schema.schemaSequence[doc.currentDocIndex]
);
}
return schema;
});
}
return Promise.resolve(null);
};
if (this.customSchemaProvider) {
return this.customSchemaProvider(resource)
.then(schemaUri => {
if (!schemaUri) {
return resolveSchema();
}
return this.loadSchema(schemaUri).then(unsolvedSchema =>
this.resolveSchemaContent(unsolvedSchema, schemaUri, []).then(
schema => {
if (
schema.schema &&
schema.schema.schemaSequence &&
schema.schema.schemaSequence[doc.currentDocIndex]
) {
return new ResolvedSchema(
schema.schema.schemaSequence[doc.currentDocIndex]
);
}
return schema;
}
)
);
})
.then(
schema => schema,
err => resolveSchema()
);
} else {
return resolveSchema();
}
}
/**
* Everything below here is needed because we're importing from vscode-json-languageservice umd and we need
* to provide a wrapper around the javascript methods we are calling since they have no type
*/
normalizeId(id: string) {
return super.normalizeId(id);
}
getOrAddSchemaHandle(id: string, unresolvedSchemaContent?: JSONSchema) {
return super.getOrAddSchemaHandle(id, unresolvedSchemaContent);
}
// tslint:disable-next-line: no-any
loadSchema(schemaUri: string): Thenable<any> {
return super.loadSchema(schemaUri);
}
registerExternalSchema(
uri: string,
filePatterns?: string[],
unresolvedSchema?: JSONSchema
) {
return super.registerExternalSchema(uri, filePatterns, unresolvedSchema);
}
clearExternalSchemas(): void {
super.clearExternalSchemas();
}
setSchemaContributions(schemaContributions: ISchemaContributions): void {
super.setSchemaContributions(schemaContributions);
}
getRegisteredSchemaIds(filter?: (scheme: any) => boolean): string[] {
return super.getRegisteredSchemaIds(filter);
}
getResolvedSchema(schemaId: string): Thenable<ResolvedSchema> {
return super.getResolvedSchema(schemaId);
}
onResourceChange(uri: string): boolean {
return super.onResourceChange(uri);
}
}

View file

@ -0,0 +1,94 @@
/*---------------------------------------------------------------------------------------------
* 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 { Diagnostic, TextDocument } from 'vscode-languageserver-types';
import { PromiseConstructor, LanguageSettings } from '../yamlLanguageService';
import { parse as parseYAML, YAMLDocument } from '../parser/yamlParser07';
import { SingleYAMLDocument } from '../parser/yamlParser04';
import { YAMLSchemaService } from './yamlSchemaService';
import { JSONValidation } from 'vscode-json-languageservice/lib/umd/services/jsonValidation';
export class YAMLValidation {
private promise: PromiseConstructor;
private validationEnabled: boolean;
private customTags: String[];
private jsonValidation;
private MATCHES_MULTIPLE =
'Matches multiple schemas when only one must validate.';
public constructor(
schemaService: YAMLSchemaService,
promiseConstructor: PromiseConstructor
) {
this.promise = promiseConstructor || Promise;
this.validationEnabled = true;
this.jsonValidation = new JSONValidation(schemaService, this.promise);
}
public configure(settings: LanguageSettings) {
if (settings) {
this.validationEnabled = settings.validate;
this.customTags = settings.customTags;
}
}
public async doValidation(
textDocument: TextDocument,
isKubernetes: boolean = false
): Promise<Diagnostic[]> {
if (!this.validationEnabled) {
return this.promise.resolve([]);
}
const yamlDocument: YAMLDocument = parseYAML(
textDocument.getText(),
this.customTags
);
const validationResult: Diagnostic[] = [];
let index = 0;
for (const currentYAMLDoc of yamlDocument.documents) {
currentYAMLDoc.isKubernetes = isKubernetes;
currentYAMLDoc.currentDocIndex = index;
const validation = await this.jsonValidation.doValidation(
textDocument,
currentYAMLDoc
);
const syd = (currentYAMLDoc as unknown) as SingleYAMLDocument;
if (syd.errors.length > 0) {
validationResult.push(...syd.errors);
}
validationResult.push(...validation);
index++;
}
const foundSignatures = new Set();
const duplicateMessagesRemoved = [];
for (const err of validationResult as Diagnostic[]) {
/**
* A patch ontop of the validation that removes the
* 'Matches many schemas' error for kubernetes
* for a better user experience.
*/
if (isKubernetes && err.message === this.MATCHES_MULTIPLE) {
continue;
}
const errSig =
err.range.start.line +
' ' +
err.range.start.character +
' ' +
err.message;
if (!foundSignatures.has(errSig)) {
duplicateMessagesRemoved.push(err);
foundSignatures.add(errSig);
}
}
return duplicateMessagesRemoved;
}
}

View file

@ -0,0 +1,107 @@
/*---------------------------------------------------------------------------------------------
* 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/yamlParser04';
export function removeDuplicates(arr, prop) {
const new_arr = [];
const lookup = {};
for (const i in arr) {
lookup[arr[i][prop]] = arr[i];
}
for (const i in lookup) {
new_arr.push(lookup[i]);
}
return new_arr;
}
export function getLineOffsets(textDocString: String): number[] {
const lineOffsets: number[] = [];
const text = textDocString;
let isLineStart = true;
for (let i = 0; i < text.length; i++) {
if (isLineStart) {
lineOffsets.push(i);
isLineStart = false;
}
const 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) {
const nonDuplicateSet = new Set();
const nonDuplicateArr = [];
for (const obj in objArray) {
const currObj = objArray[obj];
const stringifiedObj = JSON.stringify(currObj);
if (!nonDuplicateSet.has(stringifiedObj)) {
nonDuplicateArr.push(currObj);
nonDuplicateSet.add(stringifiedObj);
}
}
return nonDuplicateArr;
}
export function matchOffsetToDocument(offset: number, jsonDocuments) {
for (const jsonDoc in jsonDocuments.documents) {
const currJsonDoc: SingleYAMLDocument = jsonDocuments.documents[jsonDoc];
if (
currJsonDoc.root &&
currJsonDoc.root.end >= offset &&
currJsonDoc.root.start <= offset
) {
return currJsonDoc;
}
}
// TODO: Fix this so that it returns the correct document
return jsonDocuments.documents[0];
}
export function matchOffsetToDocument2(offset: number, jsonDocuments) {
for (const jsonDoc of jsonDocuments.documents) {
if (
jsonDoc.root &&
jsonDoc.root.offset <= offset &&
jsonDoc.root.length + jsonDoc.root.offset >= offset
) {
return jsonDoc;
}
}
// TODO: Fix this so that it returns the correct document
return null;
}
export function filterInvalidCustomTags(customTags: String[]): String[] {
const validCustomTags = ['mapping', 'scalar', 'sequence'];
return customTags.filter(tag => {
if (typeof tag === 'string') {
const typeInfo = tag.split(' ');
const type = (typeInfo[1] && typeInfo[1].toLowerCase()) || 'scalar';
// We need to check if map is a type because map will throw an error within the yaml-ast-parser
if (type === 'map') {
return false;
}
return validCustomTags.indexOf(type) !== -1;
}
return false;
});
}

View file

@ -0,0 +1,66 @@
/*---------------------------------------------------------------------------------------------
* 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) {
return -pt - 1;
}
export function binarySearch(array: number[], sought: number) {
let lower = 0;
let upper = array.length - 1;
while (lower <= upper) {
const idx = Math.floor((lower + upper) / 2);
const value = array[idx];
if (value === sought) {
return idx;
}
if (lower === upper) {
const insertionPoint = value < sought ? idx + 1 : idx;
return insertionPointReturnValue(insertionPoint);
}
if (sought > value) {
lower = idx + 1;
} else if (sought < value) {
upper = idx - 1;
}
}
}
export function getLineStartPositions(text: string) {
const lineStartPositions = [0];
for (let i = 0; i < text.length; i++) {
const c = text[i];
if (c === '\r') {
// Check for Windows encoding, otherwise we are old Mac
if (i + 1 < text.length && text[i + 1] === '\n') {
i++;
}
lineStartPositions.push(i + 1);
} else if (c === '\n') {
lineStartPositions.push(i + 1);
}
}
return lineStartPositions;
}
export function getPosition(pos: number, lineStartPositions: number[]) {
let line = binarySearch(lineStartPositions, pos);
if (line < 0) {
const insertionPoint = -1 * line - 1;
line = insertionPoint - 1;
}
return { line, column: pos - lineStartPositions[line] };
}

View file

@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Red Hat, Inc. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class ErrorHandler {
private errorResultsList;
private textDocument;
constructor(textDocument) {
this.errorResultsList = [];
this.textDocument = textDocument;
}
public addErrorResult(errorNode, errorMessage, errorType) {
this.errorResultsList.push({
severity: errorType,
range: {
start: this.textDocument.positionAt(errorNode.startPosition),
end: this.textDocument.positionAt(errorNode.endPosition),
},
message: errorMessage,
});
}
public getErrorResultsList() {
return this.errorResultsList;
}
}

View file

@ -0,0 +1,53 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// tslint:disable-next-line: no-any
export function stringifyObject(
obj: any,
indent: string,
stringifyLiteral: (val: any) => string
): string {
if (obj !== null && typeof obj === 'object') {
const newIndent = indent + '\t';
if (Array.isArray(obj)) {
if (obj.length === 0) {
return '[]';
}
let result = '[\n';
for (let i = 0; i < obj.length; i++) {
result +=
newIndent + stringifyObject(obj[i], newIndent, stringifyLiteral);
if (i < obj.length - 1) {
result += ',';
}
result += '\n';
}
result += indent + ']';
return result;
} else {
const keys = Object.keys(obj);
if (keys.length === 0) {
return '{}';
}
let result = '{\n';
for (let i = 0; i < keys.length; i++) {
const key = keys[i];
result +=
newIndent +
JSON.stringify(key) +
': ' +
stringifyObject(obj[key], newIndent, stringifyLiteral);
if (i < keys.length - 1) {
result += ',';
}
result += '\n';
}
result += indent + '}';
return result;
}
}
return stringifyLiteral(obj);
}

View file

@ -0,0 +1,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
// tslint:disable-next-line: no-any
export function equals(one: any, other: any): boolean {
if (one === other) {
return true;
}
if (
one === null ||
one === undefined ||
other === null ||
other === undefined
) {
return false;
}
if (typeof one !== typeof other) {
return false;
}
if (typeof one !== 'object') {
return false;
}
if (Array.isArray(one) !== Array.isArray(other)) {
return false;
}
let i: number, key: string;
if (Array.isArray(one)) {
if (one.length !== other.length) {
return false;
}
for (i = 0; i < one.length; i++) {
if (!equals(one[i], other[i])) {
return false;
}
}
} else {
const oneKeys: string[] = [];
for (key in one) {
oneKeys.push(key);
}
oneKeys.sort();
const otherKeys: string[] = [];
for (key in other) {
otherKeys.push(key);
}
otherKeys.sort();
if (!equals(oneKeys, otherKeys)) {
return false;
}
for (i = 0; i < oneKeys.length; i++) {
if (!equals(one[oneKeys[i]], other[oneKeys[i]])) {
return false;
}
}
}
return true;
}
// tslint:disable-next-line: no-any
export function isNumber(val: any): val is number {
return typeof val === 'number';
}
// tslint:disable-next-line: no-any
export function isDefined(val: any): val is object {
return typeof val !== 'undefined';
}
// tslint:disable-next-line: no-any
export function isBoolean(val: any): val is boolean {
return typeof val === 'boolean';
}
// tslint:disable-next-line: no-any
export function isString(val: any): val is string {
return typeof val === 'string';
}

View file

@ -0,0 +1,38 @@
import { WorkspaceFolder } from 'vscode-languageserver';
import { join, normalize } from 'path';
import { URI } from 'vscode-uri';
export const isRelativePath = (path: string): boolean => {
const relativePathRegex = /^(((\.\.?)|([\w-\. ]+))(\/|\\\\?))*[\w-\. ]*\.[\w-]+$/i;
return relativePathRegex.test(path);
};
export const relativeToAbsolutePath = (
workspaceFolders: WorkspaceFolder[],
workspaceRoot: URI,
uri: string
): string => {
// Iterate through all of the workspace root folders
for (const folder of workspaceFolders) {
// If the requested schema URI specifies a workspace root folder
// Convert it into an absolute path with the appropriate root folder path
if (uri.startsWith(folder.name)) {
const pathToFolder = URI.parse(folder.uri).fsPath;
const withoutFolderPrefix = uri.split(folder.name);
withoutFolderPrefix.shift();
return URI.file(
join(pathToFolder, withoutFolderPrefix.join())
).toString();
}
}
// If a root folder was not specified, resolve the relative URI
// Against the location of the workspace file instead
if (workspaceRoot) {
return URI.file(join(workspaceRoot.fsPath, uri)).toString();
}
// Fallback in case nothing could be applied
return normalize(uri);
};

View file

@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* 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 startsWith(haystack: string, needle: string): boolean {
if (haystack.length < needle.length) {
return false;
}
for (let i = 0; i < needle.length; i++) {
if (haystack[i] !== needle[i]) {
return false;
}
}
return true;
}
/**
* Determines if haystack ends with needle.
*/
export function endsWith(haystack: string, needle: string): boolean {
const diff = haystack.length - needle.length;
if (diff > 0) {
return haystack.lastIndexOf(needle) === diff;
} else if (diff === 0) {
return haystack === needle;
} else {
return false;
}
}
export function convertSimple2RegExp(pattern: string): RegExp {
const 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);
}
export function convertSimple2RegExpPattern(pattern: string): string {
return pattern
.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&')
.replace(/[\*]/g, '.*');
}

View file

@ -0,0 +1,210 @@
/*---------------------------------------------------------------------------------------------
* 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 {
YAMLSchemaService,
CustomSchemaProvider,
} from './services/yamlSchemaService';
import {
TextDocument,
Position,
CompletionList,
Diagnostic,
Hover,
SymbolInformation,
DocumentSymbol,
CompletionItem,
TextEdit,
} from 'vscode-languageserver-types';
import { JSONSchema } from './jsonSchema04';
import { YAMLDocumentSymbols } from './services/documentSymbols';
import { YAMLCompletion } from './services/yamlCompletion';
import { YAMLHover } from './services/yamlHover';
import { YAMLValidation } from './services/yamlValidation';
import { YAMLFormatter } from './services/yamlFormatter';
import {
LanguageService as JSONLanguageService,
getLanguageService as getJSONLanguageService,
JSONWorkerContribution,
} from 'vscode-json-languageservice';
export interface LanguageSettings {
validate?: boolean; //Setting for whether we want to validate the schema
hover?: boolean; //Setting for whether we want to have hover results
completion?: boolean; //Setting for whether we want to have completion results
format?: boolean; //Setting for whether we want to have the formatter or not
isKubernetes?: boolean; //If true then its validating against kubernetes
// tslint:disable-next-line: no-any
schemas?: any[]; //List of schemas,
customTags?: Array<String>; //Array of Custom Tags
}
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.
*/
// tslint:disable-next-line: no-any
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.
*/
// tslint:disable-next-line: no-any
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.
*/
// tslint:disable-next-line: no-any
then<TResult>(
onfulfilled?: (value: R) => TResult | Thenable<TResult>,
onrejected?: (reason: any) => TResult | Thenable<TResult>
): Thenable<TResult>;
// tslint:disable-next-line: no-any
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 CustomFormatterOptions {
singleQuote?: boolean;
bracketSpacing?: boolean;
proseWrap?: string;
printWidth?: number;
enable?: boolean;
}
export interface LanguageService {
configure(settings: LanguageSettings): void;
registerCustomSchemaProvider(schemaProvider: CustomSchemaProvider): void;
doComplete(
document: TextDocument,
position: Position,
isKubernetes: boolean
): Thenable<CompletionList>;
doValidation(
document: TextDocument,
isKubernetes: boolean
): Thenable<Diagnostic[]>;
doHover(document: TextDocument, position: Position): Thenable<Hover | null>;
findDocumentSymbols(document: TextDocument): SymbolInformation[];
findDocumentSymbols2(document: TextDocument): DocumentSymbol[];
doResolve(completionItem): Thenable<CompletionItem>;
resetSchema(uri: string): boolean;
doFormat(document: TextDocument, options: CustomFormatterOptions): TextEdit[];
}
export function getLanguageService(
schemaRequestService: SchemaRequestService,
workspaceContext: WorkspaceContextService,
contributions: JSONWorkerContribution[],
promiseConstructor?: PromiseConstructor
): LanguageService {
const promise = promiseConstructor || Promise;
const schemaService = new YAMLSchemaService(
schemaRequestService,
workspaceContext
);
const completer = new YAMLCompletion(schemaService, contributions, promise);
const hover = new YAMLHover(schemaService, promise);
const yamlDocumentSymbols = new YAMLDocumentSymbols(schemaService);
const yamlValidation = new YAMLValidation(schemaService, promise);
const formatter = new YAMLFormatter();
return {
configure: settings => {
schemaService.clearExternalSchemas();
if (settings.schemas) {
settings.schemas.forEach(settings => {
schemaService.registerExternalSchema(
settings.uri,
settings.fileMatch,
settings.schema
);
});
}
yamlValidation.configure(settings);
hover.configure(settings);
const customTagsSetting =
settings && settings['customTags'] ? settings['customTags'] : [];
completer.configure(settings, customTagsSetting);
formatter.configure(settings);
},
registerCustomSchemaProvider: (schemaProvider: CustomSchemaProvider) => {
schemaService.registerCustomSchemaProvider(schemaProvider);
},
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
),
findDocumentSymbols2: yamlDocumentSymbols.findHierarchicalDocumentSymbols.bind(
yamlDocumentSymbols
),
resetSchema: (uri: string) => schemaService.onResourceChange(uri),
doFormat: formatter.format.bind(formatter),
};
}

View file

@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* 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 { setupMode } from './yamlMode';
import Emitter = monaco.Emitter;
import IEvent = monaco.IEvent;
declare var require: <T>(
moduleId: [string],
callback: (module: T) => void
) => void;
// --- YAML configuration and defaults ---------
export class LanguageServiceDefaultsImpl
implements monaco.languages.yaml.LanguageServiceDefaults {
private _onDidChange = new Emitter<
monaco.languages.yaml.LanguageServiceDefaults
>();
private _diagnosticsOptions: monaco.languages.yaml.DiagnosticsOptions;
private _languageId: string;
constructor(
languageId: string,
diagnosticsOptions: monaco.languages.yaml.DiagnosticsOptions
) {
this._languageId = languageId;
this.setDiagnosticsOptions(diagnosticsOptions);
}
get onDidChange(): IEvent<monaco.languages.yaml.LanguageServiceDefaults> {
return this._onDidChange.event;
}
get languageId(): string {
return this._languageId;
}
get diagnosticsOptions(): monaco.languages.yaml.DiagnosticsOptions {
return this._diagnosticsOptions;
}
public setDiagnosticsOptions(
options: monaco.languages.yaml.DiagnosticsOptions
): void {
this._diagnosticsOptions = options || Object.create(null);
this._onDidChange.fire(this);
}
}
const diagnosticDefault: monaco.languages.yaml.DiagnosticsOptions = {
validate: true,
schemas: [],
enableSchemaRequest: false,
};
const yamlDefaults = new LanguageServiceDefaultsImpl('yaml', diagnosticDefault);
// Export API
function createAPI(): typeof monaco.languages.yaml {
return {
yamlDefaults,
};
}
monaco.languages.yaml = createAPI();
// --- Registration to monaco editor ---
monaco.languages.register({
id: 'yaml',
extensions: ['.yaml', '.yml'],
aliases: ['YAML', 'yaml', 'YML', 'yml'],
mimetypes: ['application/x-yaml'],
});
monaco.languages.onLanguage('yaml', () => {
setupMode(yamlDefaults);
});

54
src/monaco.d.ts vendored Normal file
View file

@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare namespace 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?: Array<{
/**
* 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;
}>;
/**
* If set, the schema service would load schema content on-demand with 'fetch' if available
*/
readonly enableSchemaRequest?: boolean;
/**
* If specified, this prefix will be added to all on demand schema requests
*/
readonly prefix?: string;
/**
* Whether or not kubernetes yaml is supported
*/
readonly isKubernetes?: boolean;
readonly format?: boolean;
}
export interface LanguageServiceDefaults {
readonly onDidChange: IEvent<LanguageServiceDefaults>;
readonly diagnosticsOptions: DiagnosticsOptions;
setDiagnosticsOptions(options: DiagnosticsOptions): void;
}
export const yamlDefaults: LanguageServiceDefaults;
}

37
src/requestTypes.ts Normal file
View file

@ -0,0 +1,37 @@
import { NotificationType, RequestType } from 'vscode-languageserver';
export namespace SchemaAssociationNotification {
export const type: NotificationType<{}, {}> = new NotificationType(
'json/schemaAssociations'
);
}
export namespace DynamicCustomSchemaRequestRegistration {
export const type: NotificationType<{}, {}> = new NotificationType(
'yaml/registerCustomSchemaRequest'
);
}
export namespace VSCodeContentRequest {
export const type: RequestType<{}, {}, {}, {}> = new RequestType(
'vscode/content'
);
}
export namespace CustomSchemaContentRequest {
export const type: RequestType<{}, {}, {}, {}> = new RequestType(
'custom/schema/content'
);
}
export namespace CustomSchemaRequest {
export const type: RequestType<{}, {}, {}, {}> = new RequestType(
'custom/schema/request'
);
}
export namespace ColorSymbolRequest {
export const type: RequestType<{}, {}, {}, {}> = new RequestType(
'json/colorSymbols'
);
}

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

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

18
src/tsconfig.json Normal file
View file

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

5
src/typings/refs.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../node_modules/monaco-editor-core/monaco.d.ts'/>

View file

@ -1,67 +1,96 @@
import { editor, languages } from 'monaco-editor/esm/vs/editor/editor.api.js'; /*---------------------------------------------------------------------------------------------
* 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 { WorkerAccessor } from './languageFeatures'; import { LanguageServiceDefaultsImpl } from './monaco.contribution';
import { YAMLWorker } from './yamlWorker'; import { YAMLWorker } from './yamlWorker';
// 2min import IDisposable = monaco.IDisposable;
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000; import Uri = monaco.Uri;
export function createWorkerManager( const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000; // 2min
defaults: languages.yaml.LanguageServiceDefaults,
): WorkerAccessor {
let worker: editor.MonacoWebWorker<YAMLWorker>;
let client: Promise<YAMLWorker>;
let lastUsedTime = 0;
const stopWorker = (): void => { export class WorkerManager {
if (worker) { private _defaults: LanguageServiceDefaultsImpl;
worker.dispose(); private _idleCheckInterval: NodeJS.Timer;
worker = undefined; private _lastUsedTime: number;
private _configChangeListener: IDisposable;
private _worker: monaco.editor.MonacoWebWorker<YAMLWorker>;
private _client: Promise<YAMLWorker>;
constructor(defaults: LanguageServiceDefaultsImpl) {
this._defaults = defaults;
this._worker = null;
this._idleCheckInterval = setInterval(() => this._checkIfIdle(), 30 * 1000);
this._lastUsedTime = 0;
this._configChangeListener = this._defaults.onDidChange(() =>
this._stopWorker()
);
}
public dispose(): void {
clearInterval(this._idleCheckInterval);
this._configChangeListener.dispose();
this._stopWorker();
}
public getLanguageServiceWorker(...resources: Uri[]): Promise<YAMLWorker> {
let _client: YAMLWorker;
return this._getClient()
.then(client => {
_client = client;
})
.then(_ => {
return this._worker.withSyncedResources(resources);
})
.then(_ => _client);
}
private _stopWorker(): void {
if (this._worker) {
this._worker.dispose();
this._worker = null;
} }
client = undefined; this._client = null;
}; }
setInterval(() => { private _checkIfIdle(): void {
if (!worker) { if (!this._worker) {
return; return;
} }
const timePassedSinceLastUsed = Date.now() - lastUsedTime; const timePassedSinceLastUsed = Date.now() - this._lastUsedTime;
if (timePassedSinceLastUsed > STOP_WHEN_IDLE_FOR) { if (timePassedSinceLastUsed > STOP_WHEN_IDLE_FOR) {
stopWorker(); this._stopWorker();
} }
}, 30 * 1000); }
// This is necessary to have updated language options take effect (e.g. schema changes) private _getClient(): Promise<YAMLWorker> {
defaults.onDidChange(() => stopWorker()); this._lastUsedTime = Date.now();
const getClient = (): Promise<YAMLWorker> => { if (!this._client) {
lastUsedTime = Date.now(); this._worker = monaco.editor.createWebWorker<YAMLWorker>({
// module that exports the create() method and returns a `YAMLWorker` instance
if (!client) {
worker = editor.createWebWorker<YAMLWorker>({
// Module that exports the create() method and returns a `YAMLWorker` instance
moduleId: 'vs/language/yaml/yamlWorker', moduleId: 'vs/language/yaml/yamlWorker',
label: defaults.languageId, label: this._defaults.languageId,
// Passed in to the create() method // passed in to the create() method
createData: { createData: {
languageSettings: defaults.diagnosticsOptions, languageSettings: this._defaults.diagnosticsOptions,
enableSchemaRequest: defaults.diagnosticsOptions.enableSchemaRequest, languageId: this._defaults.languageId,
isKubernetes: defaults.diagnosticsOptions.isKubernetes, enableSchemaRequest: this._defaults.diagnosticsOptions
customTags: defaults.diagnosticsOptions.customTags, .enableSchemaRequest,
prefix: this._defaults.diagnosticsOptions.prefix,
isKubernetes: this._defaults.diagnosticsOptions.isKubernetes,
}, },
}); });
client = worker.getProxy(); this._client = this._worker.getProxy();
} }
return client; return this._client;
}; }
return async (...resources) => {
const client = await getClient();
await worker.withSyncedResources(resources);
return client;
};
} }

View file

@ -0,0 +1,2 @@
/*.js
/*.js.map

View file

@ -0,0 +1,48 @@
'use strict';
export function isNothing(subject) {
return typeof subject === 'undefined' || null === subject;
}
export function isObject(subject) {
return typeof subject === 'object' && null !== subject;
}
export function toArray(sequence) {
if (Array.isArray(sequence)) {
return sequence;
} else if (isNothing(sequence)) {
return [];
}
return [sequence];
}
export function extend(target, source) {
var index, length, key, sourceKeys;
if (source) {
sourceKeys = Object.keys(source);
for (index = 0, length = sourceKeys.length; index < length; index += 1) {
key = sourceKeys[index];
target[key] = source[key];
}
}
return target;
}
export function repeat(string, count) {
var result = '',
cycle;
for (cycle = 0; cycle < count; cycle += 1) {
result += string;
}
return result;
}
export function isNegativeZero(number) {
return 0 === number && Number.NEGATIVE_INFINITY === 1 / number;
}

View file

@ -0,0 +1,898 @@
'use strict';
declare function require(n: string): any;
/*eslint-disable no-use-before-define*/
var common = require('./common');
var YAMLException = require('./exception');
var DEFAULT_FULL_SCHEMA = require('./schema/default_full');
var DEFAULT_SAFE_SCHEMA = require('./schema/default_safe');
var _toString = Object.prototype.toString;
var _hasOwnProperty = Object.prototype.hasOwnProperty;
var CHAR_TAB = 0x09; /* Tab */
var CHAR_LINE_FEED = 0x0a; /* LF */
var CHAR_CARRIAGE_RETURN = 0x0d; /* CR */
var CHAR_SPACE = 0x20; /* Space */
var CHAR_EXCLAMATION = 0x21; /* ! */
var CHAR_DOUBLE_QUOTE = 0x22; /* " */
var CHAR_SHARP = 0x23; /* # */
var CHAR_PERCENT = 0x25; /* % */
var CHAR_AMPERSAND = 0x26; /* & */
var CHAR_SINGLE_QUOTE = 0x27; /* ' */
var CHAR_ASTERISK = 0x2a; /* * */
var CHAR_COMMA = 0x2c; /* , */
var CHAR_MINUS = 0x2d; /* - */
var CHAR_COLON = 0x3a; /* : */
var CHAR_GREATER_THAN = 0x3e; /* > */
var CHAR_QUESTION = 0x3f; /* ? */
var CHAR_COMMERCIAL_AT = 0x40; /* @ */
var CHAR_LEFT_SQUARE_BRACKET = 0x5b; /* [ */
var CHAR_RIGHT_SQUARE_BRACKET = 0x5d; /* ] */
var CHAR_GRAVE_ACCENT = 0x60; /* ` */
var CHAR_LEFT_CURLY_BRACKET = 0x7b; /* { */
var CHAR_VERTICAL_LINE = 0x7c; /* | */
var CHAR_RIGHT_CURLY_BRACKET = 0x7d; /* } */
var ESCAPE_SEQUENCES = {};
ESCAPE_SEQUENCES[0x00] = '\\0';
ESCAPE_SEQUENCES[0x07] = '\\a';
ESCAPE_SEQUENCES[0x08] = '\\b';
ESCAPE_SEQUENCES[0x09] = '\\t';
ESCAPE_SEQUENCES[0x0a] = '\\n';
ESCAPE_SEQUENCES[0x0b] = '\\v';
ESCAPE_SEQUENCES[0x0c] = '\\f';
ESCAPE_SEQUENCES[0x0d] = '\\r';
ESCAPE_SEQUENCES[0x1b] = '\\e';
ESCAPE_SEQUENCES[0x22] = '\\"';
ESCAPE_SEQUENCES[0x5c] = '\\\\';
ESCAPE_SEQUENCES[0x85] = '\\N';
ESCAPE_SEQUENCES[0xa0] = '\\_';
ESCAPE_SEQUENCES[0x2028] = '\\L';
ESCAPE_SEQUENCES[0x2029] = '\\P';
var DEPRECATED_BOOLEANS_SYNTAX = [
'y',
'Y',
'yes',
'Yes',
'YES',
'on',
'On',
'ON',
'n',
'N',
'no',
'No',
'NO',
'off',
'Off',
'OFF',
];
function compileStyleMap(schema, map) {
var result, keys, index, length, tag, style, type;
if (null === map) {
return {};
}
result = {};
keys = Object.keys(map);
for (index = 0, length = keys.length; index < length; index += 1) {
tag = keys[index];
style = String(map[tag]);
if ('!!' === tag.slice(0, 2)) {
tag = 'tag:yaml.org,2002:' + tag.slice(2);
}
type = schema.compiledTypeMap[tag];
if (type && _hasOwnProperty.call(type.styleAliases, style)) {
style = type.styleAliases[style];
}
result[tag] = style;
}
return result;
}
function encodeHex(character) {
var string, handle, length;
string = character.toString(16).toUpperCase();
if (character <= 0xff) {
handle = 'x';
length = 2;
} else if (character <= 0xffff) {
handle = 'u';
length = 4;
} else if (character <= 0xffffffff) {
handle = 'U';
length = 8;
} else {
throw new YAMLException(
'code point within a string may not be greater than 0xFFFFFFFF'
);
}
return '\\' + handle + common.repeat('0', length - string.length) + string;
}
function State(options) {
this.schema = options['schema'] || DEFAULT_FULL_SCHEMA;
this.indent = Math.max(1, options['indent'] || 2);
this.skipInvalid = options['skipInvalid'] || false;
this.flowLevel = common.isNothing(options['flowLevel'])
? -1
: options['flowLevel'];
this.styleMap = compileStyleMap(this.schema, options['styles'] || null);
this.implicitTypes = this.schema.compiledImplicit;
this.explicitTypes = this.schema.compiledExplicit;
this.tag = null;
this.result = '';
this.duplicates = [];
this.usedDuplicates = null;
}
function indentString(string: string, spaces) {
var ind = common.repeat(' ', spaces),
position = 0,
next = -1,
result = '',
line,
length = string.length;
while (position < length) {
next = string.indexOf('\n', position);
if (next === -1) {
line = string.slice(position);
position = length;
} else {
line = string.slice(position, next + 1);
position = next + 1;
}
if (line.length && line !== '\n') {
result += ind;
}
result += line;
}
return result;
}
function generateNextLine(state, level) {
return '\n' + common.repeat(' ', state.indent * level);
}
function testImplicitResolving(state, str) {
var index, length, type;
for (
index = 0, length = state.implicitTypes.length;
index < length;
index += 1
) {
type = state.implicitTypes[index];
if (type.resolve(str)) {
return true;
}
}
return false;
}
function StringBuilder(source) {
this.source = source;
this.result = '';
this.checkpoint = 0;
}
StringBuilder.prototype.takeUpTo = function(position) {
var er;
if (position < this.checkpoint) {
er = new Error('position should be > checkpoint');
er.position = position;
er.checkpoint = this.checkpoint;
throw er;
}
this.result += this.source.slice(this.checkpoint, position);
this.checkpoint = position;
return this;
};
StringBuilder.prototype.escapeChar = function() {
var character, esc;
character = this.source.charCodeAt(this.checkpoint);
esc = ESCAPE_SEQUENCES[character] || encodeHex(character);
this.result += esc;
this.checkpoint += 1;
return this;
};
StringBuilder.prototype.finish = function() {
if (this.source.length > this.checkpoint) {
this.takeUpTo(this.source.length);
}
};
function writeScalar(state, object, level) {
var simple,
first,
spaceWrap,
folded,
literal,
single,
double,
sawLineFeed,
linePosition,
longestLine,
indent,
max,
character,
position,
escapeSeq,
hexEsc,
previous,
lineLength,
modifier,
trailingLineBreaks,
result;
if (0 === object.length) {
state.dump = "''";
return;
}
if (object.indexOf('!include') == 0) {
state.dump = '' + object; //FIXME
return;
}
if (object.indexOf('!$$$novalue') == 0) {
state.dump = ''; //FIXME
return;
}
if (-1 !== DEPRECATED_BOOLEANS_SYNTAX.indexOf(object)) {
state.dump = "'" + object + "'";
return;
}
simple = true;
first = object.length ? object.charCodeAt(0) : 0;
spaceWrap =
CHAR_SPACE === first || CHAR_SPACE === object.charCodeAt(object.length - 1);
// Simplified check for restricted first characters
// http://www.yaml.org/spec/1.2/spec.html#ns-plain-first%28c%29
if (
CHAR_MINUS === first ||
CHAR_QUESTION === first ||
CHAR_COMMERCIAL_AT === first ||
CHAR_GRAVE_ACCENT === first
) {
simple = false;
}
// can only use > and | if not wrapped in spaces.
if (spaceWrap) {
simple = false;
folded = false;
literal = false;
} else {
folded = true;
literal = true;
}
single = true;
double = new StringBuilder(object);
sawLineFeed = false;
linePosition = 0;
longestLine = 0;
indent = state.indent * level;
max = 80;
if (indent < 40) {
max -= indent;
} else {
max = 40;
}
for (position = 0; position < object.length; position++) {
character = object.charCodeAt(position);
if (simple) {
// Characters that can never appear in the simple scalar
if (!simpleChar(character)) {
simple = false;
} else {
// Still simple. If we make it all the way through like
// this, then we can just dump the string as-is.
continue;
}
}
if (single && character === CHAR_SINGLE_QUOTE) {
single = false;
}
escapeSeq = ESCAPE_SEQUENCES[character];
hexEsc = needsHexEscape(character);
if (!escapeSeq && !hexEsc) {
continue;
}
if (
character !== CHAR_LINE_FEED &&
character !== CHAR_DOUBLE_QUOTE &&
character !== CHAR_SINGLE_QUOTE
) {
folded = false;
literal = false;
} else if (character === CHAR_LINE_FEED) {
sawLineFeed = true;
single = false;
if (position > 0) {
previous = object.charCodeAt(position - 1);
if (previous === CHAR_SPACE) {
literal = false;
folded = false;
}
}
if (folded) {
lineLength = position - linePosition;
linePosition = position;
if (lineLength > longestLine) {
longestLine = lineLength;
}
}
}
if (character !== CHAR_DOUBLE_QUOTE) {
single = false;
}
double.takeUpTo(position);
double.escapeChar();
}
if (simple && testImplicitResolving(state, object)) {
simple = false;
}
modifier = '';
if (folded || literal) {
trailingLineBreaks = 0;
if (object.charCodeAt(object.length - 1) === CHAR_LINE_FEED) {
trailingLineBreaks += 1;
if (object.charCodeAt(object.length - 2) === CHAR_LINE_FEED) {
trailingLineBreaks += 1;
}
}
if (trailingLineBreaks === 0) {
modifier = '-';
} else if (trailingLineBreaks === 2) {
modifier = '+';
}
}
if (literal && longestLine < max) {
folded = false;
}
// If it's literally one line, then don't bother with the literal.
// We may still want to do a fold, though, if it's a super long line.
if (!sawLineFeed) {
literal = false;
}
if (simple) {
state.dump = object;
} else if (single) {
state.dump = "'" + object + "'";
} else if (folded) {
result = fold(object, max);
state.dump = '>' + modifier + '\n' + indentString(result, indent);
} else if (literal) {
if (!modifier) {
object = object.replace(/\n$/, '');
}
state.dump = '|' + modifier + '\n' + indentString(object, indent);
} else if (double) {
double.finish();
state.dump = '"' + double.result + '"';
} else {
throw new Error('Failed to dump scalar value');
}
return;
}
// The `trailing` var is a regexp match of any trailing `\n` characters.
//
// There are three cases we care about:
//
// 1. One trailing `\n` on the string. Just use `|` or `>`.
// This is the assumed default. (trailing = null)
// 2. No trailing `\n` on the string. Use `|-` or `>-` to "chomp" the end.
// 3. More than one trailing `\n` on the string. Use `|+` or `>+`.
//
// In the case of `>+`, these line breaks are *not* doubled (like the line
// breaks within the string), so it's important to only end with the exact
// same number as we started.
function fold(object, max) {
var result = '',
position = 0,
length = object.length,
trailing = /\n+$/.exec(object),
newLine;
if (trailing) {
length = trailing.index + 1;
}
while (position < length) {
newLine = object.indexOf('\n', position);
if (newLine > length || newLine === -1) {
if (result) {
result += '\n\n';
}
result += foldLine(object.slice(position, length), max);
position = length;
} else {
if (result) {
result += '\n\n';
}
result += foldLine(object.slice(position, newLine), max);
position = newLine + 1;
}
}
if (trailing && trailing[0] !== '\n') {
result += trailing[0];
}
return result;
}
function foldLine(line, max) {
if (line === '') {
return line;
}
var foldRe = /[^\s] [^\s]/g,
result = '',
prevMatch = 0,
foldStart = 0,
match = foldRe.exec(line),
index,
foldEnd,
folded;
while (match) {
index = match.index;
// when we cross the max len, if the previous match would've
// been ok, use that one, and carry on. If there was no previous
// match on this fold section, then just have a long line.
if (index - foldStart > max) {
if (prevMatch !== foldStart) {
foldEnd = prevMatch;
} else {
foldEnd = index;
}
if (result) {
result += '\n';
}
folded = line.slice(foldStart, foldEnd);
result += folded;
foldStart = foldEnd + 1;
}
prevMatch = index + 1;
match = foldRe.exec(line);
}
if (result) {
result += '\n';
}
// if we end up with one last word at the end, then the last bit might
// be slightly bigger than we wanted, because we exited out of the loop.
if (foldStart !== prevMatch && line.length - foldStart > max) {
result +=
line.slice(foldStart, prevMatch) + '\n' + line.slice(prevMatch + 1);
} else {
result += line.slice(foldStart);
}
return result;
}
// Returns true if character can be found in a simple scalar
function simpleChar(character) {
return (
CHAR_TAB !== character &&
CHAR_LINE_FEED !== character &&
CHAR_CARRIAGE_RETURN !== character &&
CHAR_COMMA !== character &&
CHAR_LEFT_SQUARE_BRACKET !== character &&
CHAR_RIGHT_SQUARE_BRACKET !== character &&
CHAR_LEFT_CURLY_BRACKET !== character &&
CHAR_RIGHT_CURLY_BRACKET !== character &&
CHAR_SHARP !== character &&
CHAR_AMPERSAND !== character &&
CHAR_ASTERISK !== character &&
CHAR_EXCLAMATION !== character &&
CHAR_VERTICAL_LINE !== character &&
CHAR_GREATER_THAN !== character &&
CHAR_SINGLE_QUOTE !== character &&
CHAR_DOUBLE_QUOTE !== character &&
CHAR_PERCENT !== character &&
CHAR_COLON !== character &&
!ESCAPE_SEQUENCES[character] &&
!needsHexEscape(character)
);
}
// Returns true if the character code needs to be escaped.
function needsHexEscape(character) {
return !(
(0x00020 <= character && character <= 0x00007e) ||
0x00085 === character ||
(0x000a0 <= character && character <= 0x00d7ff) ||
(0x0e000 <= character && character <= 0x00fffd) ||
(0x10000 <= character && character <= 0x10ffff)
);
}
function writeFlowSequence(state, level, object) {
var _result = '',
_tag = state.tag,
index,
length;
for (index = 0, length = object.length; index < length; index += 1) {
// Write only valid elements.
if (writeNode(state, level, object[index], false, false)) {
if (0 !== index) {
_result += ', ';
}
_result += state.dump;
}
}
state.tag = _tag;
state.dump = '[' + _result + ']';
}
function writeBlockSequence(state, level, object, compact) {
var _result = '',
_tag = state.tag,
index,
length;
for (index = 0, length = object.length; index < length; index += 1) {
// Write only valid elements.
if (writeNode(state, level + 1, object[index], true, true)) {
if (!compact || 0 !== index) {
_result += generateNextLine(state, level);
}
_result += '- ' + state.dump;
}
}
state.tag = _tag;
state.dump = _result || '[]'; // Empty sequence if no valid values.
}
function writeFlowMapping(state, level, object) {
var _result = '',
_tag = state.tag,
objectKeyList = Object.keys(object),
index,
length,
objectKey,
objectValue,
pairBuffer;
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
pairBuffer = '';
if (0 !== index) {
pairBuffer += ', ';
}
objectKey = objectKeyList[index];
objectValue = object[objectKey];
if (!writeNode(state, level, objectKey, false, false)) {
continue; // Skip this pair because of invalid key;
}
if (state.dump.length > 1024) {
pairBuffer += '? ';
}
pairBuffer += state.dump + ': ';
if (!writeNode(state, level, objectValue, false, false)) {
continue; // Skip this pair because of invalid value.
}
pairBuffer += state.dump;
// Both key and value are valid.
_result += pairBuffer;
}
state.tag = _tag;
state.dump = '{' + _result + '}';
}
function writeBlockMapping(state, level, object, compact) {
var _result = '',
_tag = state.tag,
objectKeyList = Object.keys(object),
index,
length,
objectKey,
objectValue,
explicitPair,
pairBuffer;
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
pairBuffer = '';
if (!compact || 0 !== index) {
pairBuffer += generateNextLine(state, level);
}
objectKey = objectKeyList[index];
objectValue = object[objectKey];
if (!writeNode(state, level + 1, objectKey, true, true)) {
continue; // Skip this pair because of invalid key.
}
explicitPair =
(null !== state.tag && '?' !== state.tag) ||
(state.dump && state.dump.length > 1024);
if (explicitPair) {
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
pairBuffer += '?';
} else {
pairBuffer += '? ';
}
}
pairBuffer += state.dump;
if (explicitPair) {
pairBuffer += generateNextLine(state, level);
}
if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {
continue; // Skip this pair because of invalid value.
}
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
pairBuffer += ':';
} else {
pairBuffer += ': ';
}
pairBuffer += state.dump;
// Both key and value are valid.
_result += pairBuffer;
}
state.tag = _tag;
state.dump = _result || '{}'; // Empty mapping if no valid pairs.
}
function detectType(state, object, explicit) {
var _result, typeList, index, length, type, style;
typeList = explicit ? state.explicitTypes : state.implicitTypes;
for (index = 0, length = typeList.length; index < length; index += 1) {
type = typeList[index];
if (
(type.instanceOf || type.predicate) &&
(!type.instanceOf ||
('object' === typeof object && object instanceof type.instanceOf)) &&
(!type.predicate || type.predicate(object))
) {
state.tag = explicit ? type.tag : '?';
if (type.represent) {
style = state.styleMap[type.tag] || type.defaultStyle;
if ('[object Function]' === _toString.call(type.represent)) {
_result = type.represent(object, style);
} else if (_hasOwnProperty.call(type.represent, style)) {
_result = type.represent[style](object, style);
} else {
throw new YAMLException(
'!<' + type.tag + '> tag resolver accepts not "' + style + '" style'
);
}
state.dump = _result;
}
return true;
}
}
return false;
}
// Serializes `object` and writes it to global `result`.
// Returns true on success, or false on invalid object.
//
function writeNode(state, level, object, block, compact) {
state.tag = null;
state.dump = object;
if (!detectType(state, object, false)) {
detectType(state, object, true);
}
var type = _toString.call(state.dump);
if (block) {
block = 0 > state.flowLevel || state.flowLevel > level;
}
if (
(null !== state.tag && '?' !== state.tag) ||
(2 !== state.indent && level > 0)
) {
compact = false;
}
var objectOrArray = '[object Object]' === type || '[object Array]' === type,
duplicateIndex,
duplicate;
if (objectOrArray) {
duplicateIndex = state.duplicates.indexOf(object);
duplicate = duplicateIndex !== -1;
}
if (duplicate && state.usedDuplicates[duplicateIndex]) {
state.dump = '*ref_' + duplicateIndex;
} else {
if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {
state.usedDuplicates[duplicateIndex] = true;
}
if ('[object Object]' === type) {
if (block && 0 !== Object.keys(state.dump).length) {
writeBlockMapping(state, level, state.dump, compact);
if (duplicate) {
state.dump =
'&ref_' + duplicateIndex + (0 === level ? '\n' : '') + state.dump;
}
} else {
writeFlowMapping(state, level, state.dump);
if (duplicate) {
state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
}
}
} else if ('[object Array]' === type) {
if (block && 0 !== state.dump.length) {
writeBlockSequence(state, level, state.dump, compact);
if (duplicate) {
state.dump =
'&ref_' + duplicateIndex + (0 === level ? '\n' : '') + state.dump;
}
} else {
writeFlowSequence(state, level, state.dump);
if (duplicate) {
state.dump = '&ref_' + duplicateIndex + ' ' + state.dump;
}
}
} else if ('[object String]' === type) {
if ('?' !== state.tag) {
writeScalar(state, state.dump, level);
}
} else {
if (state.skipInvalid) {
return false;
}
throw new YAMLException('unacceptable kind of an object to dump ' + type);
}
if (null !== state.tag && '?' !== state.tag) {
state.dump = '!<' + state.tag + '> ' + state.dump;
}
}
return true;
}
function getDuplicateReferences(object, state) {
var objects = [],
duplicatesIndexes = [],
index,
length;
inspectNode(object, objects, duplicatesIndexes);
for (
index = 0, length = duplicatesIndexes.length;
index < length;
index += 1
) {
state.duplicates.push(objects[duplicatesIndexes[index]]);
}
state.usedDuplicates = new Array(length);
}
function inspectNode(object, objects, duplicatesIndexes) {
var type = _toString.call(object),
objectKeyList,
index,
length;
if (null !== object && 'object' === typeof object) {
index = objects.indexOf(object);
if (-1 !== index) {
if (-1 === duplicatesIndexes.indexOf(index)) {
duplicatesIndexes.push(index);
}
} else {
objects.push(object);
if (Array.isArray(object)) {
for (index = 0, length = object.length; index < length; index += 1) {
inspectNode(object[index], objects, duplicatesIndexes);
}
} else {
objectKeyList = Object.keys(object);
for (
index = 0, length = objectKeyList.length;
index < length;
index += 1
) {
inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);
}
}
}
}
}
export function dump(input, options) {
options = options || {};
var state = new State(options);
getDuplicateReferences(input, state);
if (writeNode(state, 0, input, true, true)) {
return state.dump + '\n';
}
return '';
}
export function safeDump(input, options) {
return dump(input, common.extend({ schema: DEFAULT_SAFE_SCHEMA }, options));
}

View file

@ -0,0 +1,52 @@
import Mark from './mark';
'use strict';
class YAMLException {
message: string;
reason: string;
name: string;
mark: Mark;
isWarning: boolean;
private static CLASS_IDENTIFIER = 'yaml-ast-parser.YAMLException';
public static isInstance(instance: any): instance is YAMLException {
if (
instance != null &&
instance.getClassIdentifier &&
typeof instance.getClassIdentifier == 'function'
) {
for (let currentIdentifier of instance.getClassIdentifier()) {
if (currentIdentifier == YAMLException.CLASS_IDENTIFIER) return true;
}
}
return false;
}
public getClassIdentifier(): string[] {
var superIdentifiers = [];
return superIdentifiers.concat(YAMLException.CLASS_IDENTIFIER);
}
constructor(reason: string, mark: Mark = null, isWarning = false) {
this.name = 'YAMLException';
this.reason = reason;
this.mark = mark;
this.message = this.toString(false);
this.isWarning = isWarning;
}
toString(compact: boolean = false) {
var result;
result = 'JS-YAML: ' + (this.reason || '(unknown reason)');
if (!compact && this.mark) {
result += ' ' + this.mark.toString();
}
return result;
}
}
export default YAMLException;

View file

@ -0,0 +1,21 @@
/**
* Created by kor on 06/05/15.
*/
export { load, loadAll, safeLoad, safeLoadAll, LoadOptions } from './loader';
export { dump, safeDump } from './dumper';
import Mark from './mark';
import YAMLException from './exception';
export * from './yamlAST';
export type Error = YAMLException;
function deprecated(name) {
return function() {
throw new Error('Function ' + name + ' is deprecated and cannot be used.');
};
}
export * from './scalarInference';

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,92 @@
'use strict';
import * as common from './common';
class Mark {
constructor(
public name: string,
public buffer: string,
public position: number,
public line: number,
public column: number
) {}
filePath: string;
toLineEnd: boolean;
getSnippet(indent: number = 0, maxLength: number = 75) {
var head, start, tail, end, snippet;
if (!this.buffer) {
return null;
}
indent = indent || 4;
maxLength = maxLength || 75;
head = '';
start = this.position;
while (
start > 0 &&
-1 === '\x00\r\n\x85\u2028\u2029'.indexOf(this.buffer.charAt(start - 1))
) {
start -= 1;
if (this.position - start > maxLength / 2 - 1) {
head = ' ... ';
start += 5;
break;
}
}
tail = '';
end = this.position;
while (
end < this.buffer.length &&
-1 === '\x00\r\n\x85\u2028\u2029'.indexOf(this.buffer.charAt(end))
) {
end += 1;
if (end - this.position > maxLength / 2 - 1) {
tail = ' ... ';
end -= 5;
break;
}
}
snippet = this.buffer.slice(start, end);
return (
common.repeat(' ', indent) +
head +
snippet +
tail +
'\n' +
common.repeat(' ', indent + this.position - start + head.length) +
'^'
);
}
toString(compact: boolean = true) {
var snippet,
where = '';
if (this.name) {
where += 'in "' + this.name + '" ';
}
where += 'at line ' + (this.line + 1) + ', column ' + (this.column + 1);
if (!compact) {
snippet = this.getSnippet();
if (snippet) {
where += ':\n' + snippet;
}
}
return where;
}
}
export default Mark;

Some files were not shown because too many files have changed in this diff Show more