Compare commits

..

No commits in common. "main" and "v3.0.0" have entirely different histories.
main ... v3.0.0

56 changed files with 27923 additions and 5013 deletions

View file

@ -1,19 +1,22 @@
extends: extends: remcohaszing
- remcohaszing
- remcohaszing/typechecking
rules: rules:
class-methods-use-this: off
max-classes-per-file: off
no-console: off
no-restricted-globals: off no-restricted-globals: off
no-underscore-dangle: off
no-useless-constructor: off
'@typescript-eslint/no-misused-promises': off '@typescript-eslint/naming-convention': off
'@typescript-eslint/no-parameter-properties': off
'@typescript-eslint/no-shadow': off '@typescript-eslint/no-shadow': off
'@typescript-eslint/no-unnecessary-condition': off '@typescript-eslint/prefer-optional-chain': off
import/extensions: off
import/no-extraneous-dependencies: off import/no-extraneous-dependencies: off
import/no-unresolved: off import/no-unresolved: off
import/no-webpack-loader-syntax: off
jsdoc/require-jsdoc: off jsdoc/require-jsdoc: off
node/no-extraneous-import: off node/no-extraneous-import: off
node/no-unsupported-features/es-syntax: off node/no-unpublished-import: off
node/no-unsupported-features/node-builtins: off

View file

@ -3,29 +3,10 @@ name: ci
on: on:
pull_request: pull_request:
push: push:
branches: [main] branches: [master]
tags: ['*'] tags: ['*']
jobs: 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: pack:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -35,32 +16,11 @@ jobs:
- run: npm ci - run: npm ci
- run: npm pack - run: npm pack
prettier: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-node@v2 - uses: actions/setup-node@v2
with: { node-version: 16 } with: { node-version: 16 }
- run: npm ci - run: npm ci
- run: npx prettier --check . - run: npm run lint
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 }}

5
.gitignore vendored
View file

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

1
.npmrc
View file

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

1
.nvmrc
View file

@ -1 +0,0 @@
16

View file

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

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
```

147
README.md
View file

@ -1,26 +1,19 @@
# 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)
[![npm version](https://img.shields.io/npm/v/monaco-yaml)](https://www.npmjs.com/package/monaco-yaml)
[![prettier code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io)
[![demo](https://img.shields.io/badge/demo-monaco--yaml.js.org-61ffcf.svg)](https://monaco-yaml.js.org)
[![netlify status](https://api.netlify.com/api/v1/badges/20b08937-99d0-4882-b9a3-d5f09ddd29b7/deploy-status)](https://app.netlify.com/sites/monaco-yaml/deploys)
YAML language plugin for the Monaco Editor. It provides the following features when editing YAML YAML language plugin for the Monaco Editor. It provides the following features when editing YAML
files: files:
- Code completion, based on JSON schemas or by looking at similar objects in the same file - Code completion, based on JSON schemas or by looking at similar objects in the same file
- Hovers, based on JSON schemas - Hovers, based on JSON schemas
- Validation: Syntax errors and schema validation - Validation: Syntax errors and schema validation
- Formatting using Prettier - Formatting
- Document Symbols - Document Symbols
- Syntax highlighting
- Automatically load remote schema files (by enabling DiagnosticsOptions.enableSchemaRequest) - 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 Schemas can also be provided by configuration. See
[here](https://github.com/remcohaszing/monaco-yaml/blob/main/index.d.ts) for the API that the plugin [here](https://github.com/Microsoft/monaco-json/blob/master/index.d.ts) for the API that the JSON
offers to configure the YAML language support. plugin offers to configure the JSON language support.
## Installation ## Installation
@ -32,7 +25,7 @@ npm install monaco-yaml
Import `monaco-yaml` and configure it before an editor instance is created. Import `monaco-yaml` and configure it before an editor instance is created.
```typescript ```ts
import { editor, Uri } from 'monaco-editor'; import { editor, Uri } from 'monaco-editor';
import { setDiagnosticsOptions } from 'monaco-yaml'; import { setDiagnosticsOptions } from 'monaco-yaml';
@ -86,134 +79,22 @@ editor.create(document.createElement('editor'), {
}); });
``` ```
Also make sure to register the web worker. When using Webpack 5, this looks like the code below. Also make sure to register the service worker. See the
Other bundlers may use a different syntax, but the idea is the same. Languages you dont used can be [examples](https://github.com/pengx17/monaco-yaml/tree/master/examples) directory for full fledged
omitted. examples.
```js ## Development
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 - `git clone https://github.com/pengx17/monaco-yaml`
- `cd monaco-yaml`
A demo is available on [monaco-yaml.js.org](https://monaco-yaml.js.org). - `npm ci`
A running example: ![demo-image](test-demo.png) 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
Originally [@kpdecker](https://github.com/kpdecker) forked this repository from - https://github.com/redhat-developer/yaml-language-server
[`monaco-json`](https://github.com/microsoft/monaco-json) by
[@microsoft](https://github.com/microsoft) and rewrote it to work with
[`yaml-language-server`](https://github.com/redhat-developer/yaml-language-server) instead. Later
the repository maintenance was taken over by [@pengx17](https://github.com/pengx17). Eventually the
repository was tranferred to the account of [@remcohaszing](https://github.com/remcohaszing), who is
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/remcohaszing/monaco-yaml/blob/main/LICENSE.md) [MIT](https://github.com/pengx17/monaco-yaml/blob/master/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

@ -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,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"
}
}

View file

@ -0,0 +1,9 @@
# Usage example with Webpack and worker-loader
To run:
```
npm start
```
The demo will open in your browser.

View file

@ -0,0 +1,21 @@
{
"name": "webpack-worker-loader-example",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "webpack serve --open --mode development",
"build": "webpack --mode production"
},
"devDependencies": {
"css-loader": "^5.2.7",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.1",
"monaco-editor": "^0.26.1",
"monaco-yaml": "file:../..",
"style-loader": "^2.0.0",
"webpack": "^4.46.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2",
"worker-loader": "^3.0.8"
}
}

View file

@ -0,0 +1,4 @@
#editor {
width: 800px;
height: 600px;
}

View file

@ -5,7 +5,6 @@
<title>Monaco YAML</title> <title>Monaco YAML</title>
</head> </head>
<body> <body>
<div id="editor" style="width: 800px; height: 600px;"></div> <div id="editor"></div>
<script type="module" src="/index.js"></script>
</body> </body>
</html> </html>

View file

@ -0,0 +1,69 @@
import './index.css';
// 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';
import { editor } from 'monaco-editor/esm/vs/editor/editor.api';
import { setDiagnosticsOptions } from 'monaco-yaml';
// 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/lib/esm/yaml.worker';
window.MonacoEnvironment = {
getWorker(workerId, label) {
if (label === 'yaml') {
return new YamlWorker();
}
return new EditorWorker();
},
};
setDiagnosticsOptions({
validate: true,
enableSchemaRequest: true,
hover: true,
completion: true,
schemas: [
{
// Id of the first schema
uri: 'http://myserver/foo-schema.json',
// Associate with our model
fileMatch: ['*'],
schema: {
// Id of the first schema
id: 'http://myserver/foo-schema.json',
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: {
// Id of the first schema
id: 'http://myserver/bar-schema.json',
type: 'object',
properties: {
q1: {
enum: ['x1', 'x2'],
},
},
},
},
],
});
editor.create(document.getElementById('editor'), {
value: 'p1: ',
language: 'yaml',
});

View file

@ -0,0 +1,17 @@
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.ttf$/,
loader: 'file-loader',
},
],
},
plugins: [new HtmlWebPackPlugin()],
};

View file

@ -0,0 +1,9 @@
# Demo: Weback + Monaco + Monaco YAML
To run:
```
npm start
```
The demo will open in your browser.

View file

@ -0,0 +1,20 @@
{
"name": "webpack-example",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "webpack serve --open --mode development",
"build": "webpack --mode production"
},
"devDependencies": {
"css-loader": "^5.2.7",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^4.5.1",
"monaco-editor": "^0.26.1",
"monaco-yaml": "file:../..",
"style-loader": "2.0.0",
"webpack": "^4.46.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^3.11.2"
}
}

View file

@ -0,0 +1,4 @@
#editor {
width: 800px;
height: 600px;
}

View file

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

View file

@ -1,37 +1,35 @@
import { editor, Uri } from 'monaco-editor'; import './index.css';
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import { editor } from 'monaco-editor/esm/vs/editor/editor.api';
import { setDiagnosticsOptions } from 'monaco-yaml'; import { setDiagnosticsOptions } from 'monaco-yaml';
import YamlWorker from 'monaco-yaml/yaml.worker?worker';
// 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 = { window.MonacoEnvironment = {
getWorker(moduleId, label) { getWorkerUrl(moduleId, label) {
switch (label) { if (label === 'yaml') {
case 'editorWorkerService': return './yaml.worker.bundle.js';
return new EditorWorker();
case 'yaml':
return new YamlWorker();
default:
throw new Error(`Unknown label ${label}`);
} }
return './editor.worker.bundle.js';
}, },
}; };
// The uri is used for the schema file match.
const modelUri = Uri.parse('a://b/foo.yaml');
setDiagnosticsOptions({ setDiagnosticsOptions({
validate: true,
enableSchemaRequest: true, enableSchemaRequest: true,
hover: true, hover: true,
completion: true, completion: true,
validate: true,
format: true,
schemas: [ schemas: [
{ {
// Id of the first schema // Id of the first schema
uri: 'http://myserver/foo-schema.json', uri: 'http://myserver/foo-schema.json',
// Associate with our model // Associate with our model
fileMatch: [String(modelUri)], fileMatch: ['*'],
schema: { schema: {
// Id of the first schema
id: 'http://myserver/foo-schema.json',
type: 'object', type: 'object',
properties: { properties: {
p1: { p1: {
@ -48,6 +46,8 @@ setDiagnosticsOptions({
// Id of the first schema // Id of the first schema
uri: 'http://myserver/bar-schema.json', uri: 'http://myserver/bar-schema.json',
schema: { schema: {
// Id of the first schema
id: 'http://myserver/bar-schema.json',
type: 'object', type: 'object',
properties: { properties: {
q1: { q1: {
@ -59,9 +59,7 @@ setDiagnosticsOptions({
], ],
}); });
const value = 'p1: \np2: \n';
editor.create(document.getElementById('editor'), { editor.create(document.getElementById('editor'), {
automaticLayout: true, value: 'p1: ',
model: editor.createModel(value, 'yaml', modelUri), language: 'yaml',
}); });

View file

@ -0,0 +1,25 @@
const HtmlWebPackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js',
'yaml.worker': 'monaco-yaml/lib/esm/yaml.worker.js',
},
output: {
filename: '[name].bundle.js',
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.ttf$/,
loader: 'file-loader',
},
],
},
plugins: [new HtmlWebPackPlugin()],
};

101
index.d.ts vendored
View file

@ -1,101 +1,62 @@
import { JSONSchema4, JSONSchema6, JSONSchema7 } from 'json-schema'; import { JSONSchema4, JSONSchema6, JSONSchema7 } from 'json-schema';
import { IEvent, languages } from 'monaco-editor/esm/vs/editor/editor.api'; 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' { declare module 'monaco-editor/esm/vs/editor/editor.api' {
namespace languages.yaml { namespace languages.yaml {
export interface DiagnosticsOptions { export interface DiagnosticsOptions {
/** /**
* If set, enable schema based autocompletion. * If set, enable schema based autocompletion.
*
* @default true
*/ */
readonly completion?: boolean; 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. * If set, enable hover typs based the JSON schema.
*
* @default true
*/ */
readonly hover?: boolean; 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 * If set, the validator will be enabled and perform syntax validation as well as schema
* based validation. * based validation.
*
* @default true
*/ */
readonly validate?: boolean; readonly validate?: boolean;
/** /**
* The YAML version to use for parsing. * A list of known schemas and/or associations of schemas to file names.
*
* @default '1.2'
*/ */
readonly yamlVersion?: '1.1' | '1.2'; readonly schemas?: {
/**
* The URI of the schema, which is also the identifier of the schema.
*/
readonly uri: string;
/**
* A list of file names that are associated to the schema. The '*' wildcard can be used.
* For example '*.schema.json', 'package.json'
*/
readonly fileMatch?: string[];
/**
* The schema for the given URI.
*/
readonly schema?: JSONSchema4 | JSONSchema6 | JSONSchema7;
}[];
/**
* 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 { export interface LanguageServiceDefaults {
readonly onDidChange: IEvent<LanguageServiceDefaults>; readonly onDidChange: IEvent<LanguageServiceDefaults>;
readonly languageId: string;
readonly diagnosticsOptions: DiagnosticsOptions; readonly diagnosticsOptions: DiagnosticsOptions;
setDiagnosticsOptions: (options: DiagnosticsOptions) => void; setDiagnosticsOptions: (options: DiagnosticsOptions) => void;
} }

View file

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

30386
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,64 +1,59 @@
{ {
"name": "monaco-yaml", "name": "monaco-yaml",
"version": "4.0.0-alpha.1", "version": "3.0.0",
"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 && tsc",
"bundle": "rimraf ./lib && node ./scripts/bundle-esm",
"prepack": "npm run compile && npm run bundle",
"prepare": "husky install",
"lint": "eslint . && prettier --check ."
}, },
"main": "./lib/esm/monaco.contribution.js",
"module": "./lib/esm/monaco.contribution.js",
"typings": "./index.d.ts", "typings": "./index.d.ts",
"files": [ "files": [
"index.js", "lib",
"index.d.ts", "index.d.ts"
"yaml.worker.js"
], ],
"workspaces": [ "workspaces": [
"examples/*" "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"
}, },
"keywords": [
"editor",
"frontend",
"front-end",
"monaco",
"monaco-editor",
"yaml"
],
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.0", "@types/json-schema": "^7.0.8",
"jsonc-parser": "^3.0.0", "js-yaml": "^3.14.1",
"path-browserify": "^1.0.0", "yaml-ast-parser-custom-tags": "^0.0.43"
"prettier": "2.0.5",
"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.22"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^4.28.3",
"esbuild": "^0.14.0", "@typescript-eslint/parser": "^4.28.3",
"eslint": "^7.0.0", "eslint": "^7.30.0",
"eslint-config-remcohaszing": "^3.0.0", "eslint-config-remcohaszing": "^3.4.0",
"husky": "^7.0.0", "husky": "^7.0.1",
"lint-staged": "^12.0.0", "lint-staged": "^10.5.4",
"monaco-editor": "^0.31.0", "monaco-editor": "^0.26.1",
"type-fest": "^2.0.0", "monaco-plugin-helpers": "^1.0.3",
"typescript": "^4.0.0", "prettier": "2.0.5",
"yaml-language-server": "^1.0.0" "rimraf": "^3.0.2",
"typescript": "^4.3.5",
"yaml-language-server": "^0.11.1"
}, },
"lint-staged": { "lint-staged": {
"*.{css,json,md,html,yaml}": [ "*.{css,json,md,html,yaml}": [

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

@ -0,0 +1,58 @@
const { join } = require('path');
const helpers = require('monaco-plugin-helpers');
const REPO_ROOT = 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': join(REPO_ROOT, 'out/esm/fillers/vscode-nls.js'),
'vscode-json-languageservice/lib/umd/services/jsonValidation': join(
REPO_ROOT,
'node_modules/vscode-json-languageservice/lib/esm/services/jsonValidation.js',
),
'vscode-json-languageservice': join(
REPO_ROOT,
'node_modules/vscode-json-languageservice/lib/esm/jsonLanguageService.js',
),
'vscode-json-languageservice/lib/umd/services/jsonHover': join(
REPO_ROOT,
'node_modules/vscode-json-languageservice/lib/esm/services/jsonHover.js',
),
'vscode-json-languageservice/lib/umd/services/jsonDocumentSymbols': join(
REPO_ROOT,
'node_modules/vscode-json-languageservice/lib/esm/services/jsonDocumentSymbols.js',
),
'vscode-json-languageservice/lib/umd/services/jsonSchemaService': join(
REPO_ROOT,
'node_modules/vscode-json-languageservice/lib/esm/services/jsonSchemaService.js',
),
'vscode-json-languageservice/lib/umd/services/jsonCompletion': join(
REPO_ROOT,
'node_modules/vscode-json-languageservice/lib/esm/services/jsonCompletion.js',
),
'vscode-json-languageservice/lib/umd/services/jsonDefinition': join(
REPO_ROOT,
'node_modules/vscode-json-languageservice/lib/esm/services/jsonDefinition.js',
),
'yaml-language-server': join(
REPO_ROOT,
'node_modules/yaml-language-server/lib/esm/languageservice/yamlLanguageService.js',
),
prettier: join(REPO_ROOT, 'node_modules/prettier/standalone.js'),
'prettier/parser-yaml': join(REPO_ROOT, 'node_modules/prettier/parser-yaml.js'),
},
resolveSkip: ['monaco-editor', 'monaco-editor-core', 'js-yaml', 'yaml-ast-parser-custom-tags'],
destinationFolderSimplification: {
// eslint-disable-next-line camelcase
node_modules: '_deps',
'jsonc-parser/lib/esm': 'jsonc-parser',
'vscode-languageserver-types/lib/esm': 'vscode-languageserver-types',
'vscode-uri/lib/esm': 'vscode-uri',
},
});

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

@ -16,7 +16,7 @@ export type LoadFunc = (file?: string) => LocalizeFunc;
function format(message: string, args: string[]): string { function format(message: string, args: string[]): string {
return args.length === 0 return args.length === 0
? message ? message
: message.replace(/{(\d+)}/g, (match, rest: number[]) => { : message.replace(/{(\d+)}/g, (match, rest) => {
const [index] = rest; const [index] = rest;
return typeof args[index] === 'undefined' ? match : args[index]; return typeof args[index] === 'undefined' ? match : args[index];
}); });

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,24 +1,25 @@
import { import {
editor, editor,
IDisposable, IDisposable,
IMarkdownString,
IRange,
languages, languages,
MarkerSeverity, MarkerSeverity,
MarkerTag,
Position, Position,
Range, Range,
Uri, Uri,
} from 'monaco-editor/esm/vs/editor/editor.api.js'; } from 'monaco-editor/esm/vs/editor/editor.api';
import * as ls from 'vscode-languageserver-types'; import * as ls from 'vscode-languageserver-types';
import { CustomFormatterOptions } from 'yaml-language-server/lib/esm/languageservice/yamlLanguageService.js'; import { CustomFormatterOptions } from 'yaml-language-server';
import { languageId } from './constants'; import { LanguageServiceDefaultsImpl } from './monaco.contribution';
import { YAMLWorker } from './yamlWorker'; import { YAMLWorker } from './yamlWorker';
export type WorkerAccessor = (...more: Uri[]) => PromiseLike<YAMLWorker>; export type WorkerAccessor = (...more: Uri[]) => PromiseLike<YAMLWorker>;
// --- diagnostics --- --- // --- diagnostics --- ---
function toSeverity(lsSeverity: ls.DiagnosticSeverity): MarkerSeverity { function toSeverity(lsSeverity: number): MarkerSeverity {
switch (lsSeverity) { switch (lsSeverity) {
case ls.DiagnosticSeverity.Error: case ls.DiagnosticSeverity.Error:
return MarkerSeverity.Error; return MarkerSeverity.Error;
@ -33,17 +34,9 @@ function toSeverity(lsSeverity: ls.DiagnosticSeverity): MarkerSeverity {
} }
} }
function toMarkerDataTag(tag: ls.DiagnosticTag): MarkerTag { function toDiagnostics(resource: Uri, diag: ls.Diagnostic): editor.IMarkerData {
switch (tag) { const code = typeof diag.code === 'number' ? String(diag.code) : (diag.code as string);
case ls.DiagnosticTag.Deprecated:
return MarkerTag.Deprecated;
case ls.DiagnosticTag.Unnecessary:
return MarkerTag.Unnecessary;
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,83 +44,102 @@ 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( export class DiagnosticsAdapter {
getWorker: WorkerAccessor, private _disposables: IDisposable[] = [];
defaults: languages.yaml.LanguageServiceDefaults, private _listener: Record<string, IDisposable> = Object.create(null);
): 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);
}
};
constructor(
private _languageId: string,
private _worker: WorkerAccessor,
defaults: LanguageServiceDefaultsImpl,
) {
const onModelAdd = (model: editor.IModel): void => { const onModelAdd = (model: editor.IModel): void => {
if (model.getLanguageId() !== languageId) { const modeId = model.getModeId();
if (modeId !== this._languageId) {
return; return;
} }
let handle: ReturnType<typeof setTimeout>; let handle: number;
listeners.set( this._listener[String(toString)] = model.onDidChangeContent(() => {
String(model.uri),
model.onDidChangeContent(() => {
clearTimeout(handle); clearTimeout(handle);
handle = setTimeout(() => doValidate(model.uri), 500); handle = setTimeout(() => this._doValidate(model.uri, modeId), 500);
}), });
);
doValidate(model.uri); this._doValidate(model.uri, modeId);
}; };
const onModelRemoved = (model: editor.IModel): void => { const onModelRemoved = (model: editor.IModel): void => {
editor.setModelMarkers(model, languageId, []); editor.setModelMarkers(model, this._languageId, []);
const uriStr = String(model.uri); const uriStr = String(model.uri);
const listener = listeners.get(uriStr); const listener = this._listener[uriStr];
if (listener) { if (listener) {
listener.dispose(); listener.dispose();
listeners.delete(uriStr); delete this._listener[uriStr];
} }
}; };
editor.onDidCreateModel(onModelAdd); this._disposables.push(
editor.onDidCreateModel(onModelAdd),
editor.onWillDisposeModel((model) => { editor.onWillDisposeModel((model) => {
onModelRemoved(model); onModelRemoved(model);
resetSchema(model.uri); this._resetSchema(model.uri);
}); }),
editor.onDidChangeModelLanguage((event) => { editor.onDidChangeModelLanguage((event) => {
onModelRemoved(event.model); onModelRemoved(event.model);
onModelAdd(event.model); onModelAdd(event.model);
resetSchema(event.model.uri); this._resetSchema(event.model.uri);
}); }),
defaults.onDidChange(() => { defaults.onDidChange(() => {
for (const model of editor.getModels()) { editor.getModels().forEach((model) => {
if (model.getLanguageId() === languageId) { if (model.getModeId() === this._languageId) {
onModelRemoved(model); onModelRemoved(model);
onModelAdd(model); onModelAdd(model);
} }
}
}); });
}),
{
dispose: () => {
editor.getModels().forEach(onModelRemoved);
for (const disposable of Object.values(this._listener)) {
disposable.dispose();
}
},
},
);
for (const model of editor.getModels()) { editor.getModels().forEach(onModelAdd);
onModelAdd(model); }
dispose(): void {
this._disposables.forEach((d) => d && d.dispose());
this._disposables = [];
}
private _resetSchema(resource: Uri): void {
this._worker().then((worker) => {
worker.resetSchema(String(resource));
});
}
private _doValidate(resource: Uri, languageId: string): void {
this._worker(resource)
.then((worker) =>
worker.doValidation(String(resource)).then((diagnostics) => {
const markers = diagnostics.map((d) => toDiagnostics(resource, d));
const model = editor.getModel(resource);
if (model.getModeId() === languageId) {
editor.setModelMarkers(model, languageId, markers);
}
}),
)
.then(undefined, (err) => {
console.error(err);
});
} }
} }
@ -140,6 +152,18 @@ function fromPosition(position: Position): ls.Position {
return { character: position.column - 1, line: position.lineNumber - 1 }; return { character: position.column - 1, line: position.lineNumber - 1 };
} }
function fromRange(range: IRange): ls.Range {
if (!range) {
return;
}
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;
@ -152,7 +176,7 @@ function toRange(range: ls.Range): Range {
); );
} }
function toCompletionItemKind(kind: ls.CompletionItemKind): languages.CompletionItemKind { function toCompletionItemKind(kind: number): languages.CompletionItemKind {
const mItemKind = languages.CompletionItemKind; const mItemKind = languages.CompletionItemKind;
switch (kind) { switch (kind) {
@ -207,17 +231,20 @@ function toTextEdit(textEdit: ls.TextEdit): editor.ISingleEditOperation {
}; };
} }
export function createCompletionItemProvider( export class CompletionAdapter implements languages.CompletionItemProvider {
getWorker: WorkerAccessor, triggetCharacters = [' ', ':'];
): languages.CompletionItemProvider {
return {
triggerCharacters: [' ', ':'],
async provideCompletionItems(model, position) { constructor(private _worker: WorkerAccessor) {}
provideCompletionItems(
model: editor.IReadOnlyModel,
position: Position,
): PromiseLike<languages.CompletionList> {
const resource = model.uri; const resource = model.uri;
const worker = await getWorker(resource); return this._worker(resource)
const info = await worker.doComplete(String(resource), fromPosition(position)); .then((worker) => worker.doComplete(String(resource), fromPosition(position)))
.then((info) => {
if (!info) { if (!info) {
return; return;
} }
@ -230,7 +257,7 @@ export function createCompletionItemProvider(
wordInfo.endColumn, wordInfo.endColumn,
); );
const items = info.items.map((entry) => { const items: languages.CompletionItem[] = info.items.map((entry) => {
const item: languages.CompletionItem = { const item: languages.CompletionItem = {
label: entry.label, label: entry.label,
insertText: entry.insertText || entry.label, insertText: entry.insertText || entry.label,
@ -257,51 +284,69 @@ export function createCompletionItemProvider(
}); });
return { return {
incomplete: info.isIncomplete, isIncomplete: info.isIncomplete,
suggestions: items, suggestions: items,
}; };
}, });
}; }
} }
// --- definition ------ function isMarkupContent(thing: unknown): 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(entry: ls.MarkedString | ls.MarkupContent): IMarkdownString {
if (typeof entry === 'string') {
return { return {
async provideDefinition(model, position) { value: entry,
const resource = model.uri;
const worker = await getWorker(resource);
const definitions = await worker.doDefinition(String(resource), fromPosition(position));
return definitions?.map((definition) => ({
originSelectionRange: definition.originSelectionRange,
range: toRange(definition.targetRange),
targetSelectionRange: definition.targetSelectionRange,
uri: Uri.parse(definition.targetUri),
}));
},
}; };
}
if (isMarkupContent(entry)) {
if (entry.kind === 'plaintext') {
return {
value: entry.value.replace(/[!#()*+.[\\\]_`{}-]/g, '\\$&'),
};
}
return {
value: entry.value,
};
}
return { value: `\`\`\`${entry.language}\n${entry.value}\n\`\`\`\n` };
}
function toMarkedStringArray(
contents: ls.MarkedString | ls.MarkedString[] | ls.MarkupContent,
): IMarkdownString[] {
if (!contents) {
return;
}
if (Array.isArray(contents)) {
return contents.map(toMarkdownString);
}
return [toMarkdownString(contents)];
} }
// --- hover ------ // --- hover ------
export function createHoverProvider(getWorker: WorkerAccessor): languages.HoverProvider { export class HoverAdapter implements languages.HoverProvider {
return { constructor(private _worker: WorkerAccessor) {}
async provideHover(model, position) {
provideHover(model: editor.IReadOnlyModel, position: Position): PromiseLike<languages.Hover> {
const resource = model.uri; const resource = model.uri;
const worker = await getWorker(resource); return this._worker(resource)
const info = await worker.doHover(String(resource), fromPosition(position)); .then((worker) => worker.doHover(String(resource), fromPosition(position)))
.then((info) => {
if (!info) { if (!info) {
return; return;
} }
return { return {
range: toRange(info.range), range: toRange(info.range),
contents: [{ value: (info.contents as ls.MarkupContent).value }], contents: toMarkedStringArray(info.contents),
}; } as languages.Hover;
}, });
}; }
} }
// --- document symbols ------ // --- document symbols ------
@ -353,31 +398,31 @@ function toSymbolKind(kind: ls.SymbolKind): languages.SymbolKind {
function toDocumentSymbol(item: ls.DocumentSymbol): languages.DocumentSymbol { function toDocumentSymbol(item: ls.DocumentSymbol): 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: [], tags: [],
}; };
} }
export function createDocumentSymbolProvider( export class DocumentSymbolAdapter implements languages.DocumentSymbolProvider {
getWorker: WorkerAccessor, constructor(private _worker: WorkerAccessor) {}
): languages.DocumentSymbolProvider {
return { provideDocumentSymbols(model: editor.IReadOnlyModel): PromiseLike<languages.DocumentSymbol[]> {
async provideDocumentSymbols(model) {
const resource = model.uri; const resource = model.uri;
const worker = await getWorker(resource); return this._worker(resource)
const items = await worker.findDocumentSymbols(String(resource)); .then((worker) => worker.findDocumentSymbols(String(resource)))
.then((items) => {
if (!items) { if (!items) {
return; return;
} }
return items.map(toDocumentSymbol); return items.map((item) => toDocumentSymbol(item));
}, });
}; }
} }
function fromFormattingOptions( function fromFormattingOptions(
@ -390,42 +435,46 @@ function fromFormattingOptions(
}; };
} }
export function createDocumentFormattingEditProvider( export class DocumentFormattingEditProvider implements languages.DocumentFormattingEditProvider {
getWorker: WorkerAccessor, constructor(private _worker: WorkerAccessor) {}
): languages.DocumentFormattingEditProvider {
return { provideDocumentFormattingEdits(
async provideDocumentFormattingEdits(model, options) { model: editor.IReadOnlyModel,
options: languages.FormattingOptions,
): PromiseLike<editor.ISingleEditOperation[]> {
const resource = model.uri; const resource = model.uri;
const worker = await getWorker(resource); return this._worker(resource).then((worker) =>
const edits = await worker.format(String(resource), fromFormattingOptions(options)); worker.format(String(resource), null, fromFormattingOptions(options)).then((edits) => {
if (!edits || edits.length === 0) { if (!edits || edits.length === 0) {
return; return;
} }
return edits.map(toTextEdit); return edits.map(toTextEdit);
}, }),
}; );
}
} }
function toLink(link: ls.DocumentLink): languages.ILink { export class DocumentRangeFormattingEditProvider
return { implements languages.DocumentRangeFormattingEditProvider {
range: toRange(link.range), constructor(private _worker: WorkerAccessor) {}
tooltip: link.tooltip,
url: link.target,
};
}
export function createLinkProvider(getWorker: WorkerAccessor): languages.LinkProvider { provideDocumentRangeFormattingEdits(
return { model: editor.IReadOnlyModel,
async provideLinks(model) { range: Range,
options: languages.FormattingOptions,
): PromiseLike<editor.ISingleEditOperation[]> {
const resource = model.uri; const resource = model.uri;
const worker = await getWorker(resource); return this._worker(resource).then((worker) =>
const links = await worker.findLinks(String(resource)); worker
.format(String(resource), fromRange(range), fromFormattingOptions(options))
return { .then((edits) => {
links: links.map(toLink), if (!edits || edits.length === 0) {
}; return;
}, }
}; return edits.map(toTextEdit);
}),
);
}
} }

View file

@ -0,0 +1,71 @@
import { Emitter, IEvent, languages } from 'monaco-editor/esm/vs/editor/editor.api';
import { setupMode } from './yamlMode';
// --- YAML configuration and defaults ---------
export class LanguageServiceDefaultsImpl implements languages.yaml.LanguageServiceDefaults {
private _onDidChange = new Emitter<languages.yaml.LanguageServiceDefaults>();
private _diagnosticsOptions: languages.yaml.DiagnosticsOptions;
private _languageId: string;
constructor(languageId: string, diagnosticsOptions: languages.yaml.DiagnosticsOptions) {
this._languageId = languageId;
this.setDiagnosticsOptions(diagnosticsOptions);
}
get onDidChange(): IEvent<languages.yaml.LanguageServiceDefaults> {
return this._onDidChange.event;
}
get languageId(): string {
return this._languageId;
}
get diagnosticsOptions(): languages.yaml.DiagnosticsOptions {
return this._diagnosticsOptions;
}
setDiagnosticsOptions(options: languages.yaml.DiagnosticsOptions): void {
this._diagnosticsOptions = options || Object.create(null);
this._onDidChange.fire(this);
}
}
const diagnosticDefault: languages.yaml.DiagnosticsOptions = {
validate: true,
schemas: [],
enableSchemaRequest: false,
};
const yamlDefaults = new LanguageServiceDefaultsImpl('yaml', diagnosticDefault);
// Export API
function createAPI(): typeof languages.yaml {
return {
yamlDefaults,
};
}
languages.yaml = createAPI();
// --- Registration to monaco editor ---
languages.register({
id: 'yaml',
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,67 +1,85 @@
import { editor, languages } from 'monaco-editor/esm/vs/editor/editor.api.js'; import { editor, IDisposable, Uri } from 'monaco-editor/esm/vs/editor/editor.api';
import { WorkerAccessor } from './languageFeatures'; import { LanguageServiceDefaultsImpl } from './monaco.contribution';
import { YAMLWorker } from './yamlWorker'; import { YAMLWorker } from './yamlWorker';
// 2min // 2min
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000; const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000;
export function createWorkerManager( export class WorkerManager {
defaults: languages.yaml.LanguageServiceDefaults, private _defaults: LanguageServiceDefaultsImpl;
): WorkerAccessor { private _idleCheckInterval: number;
let worker: editor.MonacoWebWorker<YAMLWorker>; private _lastUsedTime: number;
let client: Promise<YAMLWorker>; private _configChangeListener: IDisposable;
let lastUsedTime = 0;
const stopWorker = (): void => { private _worker: editor.MonacoWebWorker<YAMLWorker>;
if (worker) { private _client: Promise<YAMLWorker>;
worker.dispose();
worker = undefined; 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());
} }
client = undefined;
};
setInterval(() => { dispose(): void {
if (!worker) { clearInterval(this._idleCheckInterval);
this._configChangeListener.dispose();
this._stopWorker();
}
getLanguageServiceWorker(...resources: Uri[]): Promise<YAMLWorker> {
let _client: YAMLWorker;
return this._getClient()
.then((client) => {
_client = client;
})
.then(() => this._worker.withSyncedResources(resources))
.then(() => _client);
}
private _stopWorker(): void {
if (this._worker) {
this._worker.dispose();
this._worker = null;
}
this._client = null;
}
private _checkIfIdle(): void {
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 = editor.createWebWorker<YAMLWorker>({
if (!client) {
worker = editor.createWebWorker<YAMLWorker>({
// Module that exports the create() method and returns a `YAMLWorker` instance // Module that exports the create() method and returns a `YAMLWorker` instance
moduleId: 'vs/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.enableSchemaRequest,
customTags: defaults.diagnosticsOptions.customTags, 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

@ -1,7 +1,8 @@
import { initialize } from 'monaco-editor/esm/vs/editor/editor.worker.js'; import * as worker from 'monaco-editor/esm/vs/editor/editor.worker';
import { createYAMLWorker, ICreateData } from './yamlWorker'; import { YAMLWorker } from './yamlWorker';
self.onmessage = () => { self.onmessage = () => {
initialize((ctx, createData: ICreateData) => Object.create(createYAMLWorker(ctx, createData))); // Ignore the first message
worker.initialize((ctx, createData) => new YAMLWorker(ctx, createData));
}; };

View file

@ -1,16 +1,9 @@
import { languages } from 'monaco-editor/esm/vs/editor/editor.api.js'; import { IDisposable, languages, Uri } from 'monaco-editor/esm/vs/editor/editor.api';
import { languageId } from './constants'; import * as languageFeatures from './languageFeatures';
import { import { LanguageServiceDefaultsImpl } from './monaco.contribution';
createCompletionItemProvider, import { WorkerManager } from './workerManager';
createDefinitionProvider, import { YAMLWorker } from './yamlWorker';
createDiagnosticsAdapter,
createDocumentFormattingEditProvider,
createDocumentSymbolProvider,
createHoverProvider,
createLinkProvider,
} from './languageFeatures';
import { createWorkerManager } from './workerManager';
const richEditConfiguration: languages.LanguageConfiguration = { const richEditConfiguration: languages.LanguageConfiguration = {
comments: { comments: {
@ -44,18 +37,44 @@ const richEditConfiguration: languages.LanguageConfiguration = {
], ],
}; };
export function setupMode(defaults: languages.yaml.LanguageServiceDefaults): void { export function setupMode(defaults: LanguageServiceDefaultsImpl): void {
const worker = createWorkerManager(defaults); const disposables: IDisposable[] = [];
languages.registerCompletionItemProvider(languageId, createCompletionItemProvider(worker)); const client = new WorkerManager(defaults);
languages.registerHoverProvider(languageId, createHoverProvider(worker)); disposables.push(client);
languages.registerDefinitionProvider(languageId, createDefinitionProvider(worker));
languages.registerDocumentSymbolProvider(languageId, createDocumentSymbolProvider(worker)); const worker: languageFeatures.WorkerAccessor = (...uris: Uri[]): Promise<YAMLWorker> =>
client.getLanguageServiceWorker(...uris);
const { languageId } = defaults;
disposables.push(
languages.registerCompletionItemProvider(
languageId,
new languageFeatures.CompletionAdapter(worker),
),
languages.registerHoverProvider(languageId, new languageFeatures.HoverAdapter(worker)),
languages.registerDocumentSymbolProvider(
languageId,
new languageFeatures.DocumentSymbolAdapter(worker),
),
languages.registerDocumentFormattingEditProvider( languages.registerDocumentFormattingEditProvider(
languageId, languageId,
createDocumentFormattingEditProvider(worker), new languageFeatures.DocumentFormattingEditProvider(worker),
),
languages.registerDocumentRangeFormattingEditProvider(
languageId,
new languageFeatures.DocumentRangeFormattingEditProvider(worker),
),
new languageFeatures.DiagnosticsAdapter(languageId, worker, defaults),
languages.setLanguageConfiguration(languageId, richEditConfiguration),
); );
languages.registerLinkProvider(languageId, createLinkProvider(worker));
createDiagnosticsAdapter(worker, defaults); // Color adapter should be necessary most of the time:
languages.setLanguageConfiguration(languageId, richEditConfiguration); // disposables.push(
// languages.registerColorProvider(
// languageId,
// new languageFeatures.DocumentColorAdapter(worker)
// )
// );
} }

View file

@ -1,111 +1,101 @@
import { worker } from 'monaco-editor/esm/vs/editor/editor.api.js'; import { worker } from 'monaco-editor/esm/vs/editor/editor.api';
import { Promisable } from 'type-fest';
import { TextDocument } from 'vscode-languageserver-textdocument';
import * as ls from 'vscode-languageserver-types'; import * as ls from 'vscode-languageserver-types';
import { import * as yamlService from 'yaml-language-server';
CustomFormatterOptions,
getLanguageService,
LanguageSettings,
} from 'yaml-language-server/lib/esm/languageservice/yamlLanguageService.js';
import { languageId } from './constants'; let defaultSchemaRequestService: (url: string) => PromiseLike<string>;
async function schemaRequestService(uri: string): Promise<string> { if (typeof fetch !== 'undefined') {
const response = await fetch(uri); defaultSchemaRequestService = (url) => fetch(url).then((response) => response.text());
if (response.ok) {
return response.text();
}
throw new Error(`Schema request failed for ${uri}`);
} }
export interface YAMLWorker { export class YAMLWorker {
doValidation: (uri: string) => Promisable<ls.Diagnostic[]>; private _ctx: worker.IWorkerContext;
private _languageService: yamlService.LanguageService;
private _languageSettings: yamlService.LanguageSettings;
private _languageId: string;
private _isKubernetes: boolean;
doComplete: (uri: string, position: ls.Position) => Promisable<ls.CompletionList>; constructor(ctx: worker.IWorkerContext, createData: ICreateData) {
const prefix = createData.prefix || '';
doDefinition: (uri: string, position: ls.Position) => Promisable<ls.LocationLink[]>; const service = (url: string): PromiseLike<string> =>
defaultSchemaRequestService(`${prefix}${url}`);
doHover: (uri: string, position: ls.Position) => Promisable<ls.Hover>; this._ctx = ctx;
this._languageSettings = createData.languageSettings;
format: (uri: string, options: CustomFormatterOptions) => Promisable<ls.TextEdit[]>; this._languageId = createData.languageId;
this._languageService = yamlService.getLanguageService(
resetSchema: (uri: string) => Promisable<boolean>; createData.enableSchemaRequest && service,
findDocumentSymbols: (uri: string) => Promisable<ls.DocumentSymbol[]>;
findLinks: (uri: string) => Promisable<ls.DocumentLink[]>;
}
export function createYAMLWorker(
ctx: worker.IWorkerContext,
{ enableSchemaRequest, languageSettings }: ICreateData,
): YAMLWorker {
const languageService = getLanguageService(
enableSchemaRequest ? schemaRequestService : null,
null,
null,
null,
null, null,
[],
); );
languageService.configure(languageSettings); this._isKubernetes = createData.isKubernetes || false;
this._languageService.configure({
...this._languageSettings,
hover: true,
isKubernetes: this._isKubernetes,
});
}
const getTextDocument = (uri: string): TextDocument => { doValidation(uri: string): PromiseLike<ls.Diagnostic[]> {
const models = ctx.getMirrorModels(); const document = this._getTextDocument(uri);
if (document) {
return this._languageService.doValidation(document, this._isKubernetes);
}
return Promise.resolve([]);
}
doComplete(uri: string, position: ls.Position): PromiseLike<ls.CompletionList> {
const document = this._getTextDocument(uri);
return this._languageService.doComplete(document, position, this._isKubernetes);
}
doResolve(item: ls.CompletionItem): PromiseLike<ls.CompletionItem> {
return this._languageService.doResolve(item);
}
doHover(uri: string, position: ls.Position): PromiseLike<ls.Hover> {
const document = this._getTextDocument(uri);
return this._languageService.doHover(document, position);
}
format(
uri: string,
range: ls.Range,
options: yamlService.CustomFormatterOptions,
): PromiseLike<ls.TextEdit[]> {
const document = this._getTextDocument(uri);
const textEdits = this._languageService.doFormat(document, options);
return Promise.resolve(textEdits);
}
resetSchema(uri: string): PromiseLike<boolean> {
return Promise.resolve(this._languageService.resetSchema(uri));
}
findDocumentSymbols(uri: string): PromiseLike<ls.DocumentSymbol[]> {
const document = this._getTextDocument(uri);
const symbols = this._languageService.findDocumentSymbols2(document);
return Promise.resolve(symbols);
}
private _getTextDocument(uri: string): ls.TextDocument {
const models = this._ctx.getMirrorModels();
for (const model of models) { for (const model of models) {
if (String(model.uri) === uri) { if (String(model.uri) === uri) {
return TextDocument.create(uri, languageId, model.version, model.getValue()); return ls.TextDocument.create(uri, this._languageId, model.version, model.getValue());
} }
} }
return null; return null;
};
return {
doValidation(uri) {
const document = getTextDocument(uri);
if (document) {
return languageService.doValidation(document, languageSettings.isKubernetes);
} }
return [];
},
doComplete(uri, position) {
const document = getTextDocument(uri);
return languageService.doComplete(document, position, languageSettings.isKubernetes);
},
doDefinition(uri, position) {
const document = getTextDocument(uri);
return languageService.doDefinition(document, { position, textDocument: { uri } });
},
doHover(uri, position) {
const document = getTextDocument(uri);
return languageService.doHover(document, position);
},
format(uri, options) {
const document = getTextDocument(uri);
return languageService.doFormat(document, options);
},
resetSchema(uri) {
return languageService.resetSchema(uri);
},
findDocumentSymbols(uri) {
const document = getTextDocument(uri);
return languageService.findDocumentSymbols2(document, {});
},
findLinks(uri) {
const document = getTextDocument(uri);
return languageService.findLinks(document);
},
};
} }
export interface ICreateData { export interface ICreateData {
languageSettings: LanguageSettings; languageId: string;
languageSettings: yamlService.LanguageSettings;
enableSchemaRequest: boolean; enableSchemaRequest: boolean;
prefix?: string;
isKubernetes?: boolean; isKubernetes?: boolean;
} }
export function create(ctx: worker.IWorkerContext, createData: ICreateData): YAMLWorker {
return new YAMLWorker(ctx, createData);
}

View file

@ -1,15 +1,17 @@
{ {
"compilerOptions": { "compilerOptions": {
"alwaysStrict": true,
"declaration": true, "declaration": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"downlevelIteration": true, "downlevelIteration": true,
"lib": ["dom", "dom.iterable", "es2017"],
"module": "esnext", "module": "esnext",
"moduleResolution": "node", "moduleResolution": "node",
"noEmit": true, "lib": ["dom", "es2017"],
"outDir": "./out/esm",
"skipLibCheck": true, "skipLibCheck": true,
"sourceMap": true, "sourceMap": true,
"target": "es6", "target": "es6",
"types": [] "types": []
} },
"exclude": ["node_modules", "out", "lib", "test"]
} }