Compare commits

..

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

38 changed files with 22416 additions and 3527 deletions

View file

@ -1,19 +1,24 @@
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-unpublished-import: off
node/no-unsupported-features/es-syntax: off node/no-unsupported-features/es-syntax: off
node/no-unsupported-features/node-builtins: off node/no-unsupported-features/node-builtins: off

View file

@ -16,16 +16,6 @@ jobs:
- run: npm ci - run: npm ci
- run: npx eslint . - 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:
@ -52,15 +42,15 @@ jobs:
with: { node-version: 16 } with: { node-version: 16 }
- run: npm ci - run: npm ci
- run: npx tsc - run: npx tsc
# release:
# runs-on: ubuntu-latest release:
# needs: [eslint, pack, prettier, tsc] runs-on: ubuntu-latest
# if: startsWith(github.ref, 'refs/tags/') needs: [eslint, pack, prettier, tsc]
# steps: if: startsWith(github.ref, 'refs/tags/')
# - uses: actions/checkout@v2 steps:
# - 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: npm publish - run: npm publish
# env: env:
# NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

4
.gitignore vendored
View file

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

1
.npmrc
View file

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

View file

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

View file

@ -1,15 +1,5 @@
# Contributing # 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. To get started with contributing, clone the repository and install its dependencies.
```sh ```sh
@ -18,19 +8,16 @@ cd monaco-yaml
npm ci npm ci
``` ```
## Building
To build the repository, run: To build the repository, run:
```sh ```sh
npm run prepack npm prepack
``` ```
## Running
To test it, run one of the To test it, run one of the
[examples](https://github.com/remcohaszing/monaco-yaml/tree/main/examples). [examples](https://github.com/remcohaszing/monaco-yaml/tree/main/examples).
```sh ```sh
npm --workspace demo start cd examples/webpack-worker-loader
npm start
``` ```

101
README.md
View file

@ -16,7 +16,6 @@ files:
- Document Symbols - Document Symbols
- Automatically load remote schema files (by enabling DiagnosticsOptions.enableSchemaRequest) - Automatically load remote schema files (by enabling DiagnosticsOptions.enableSchemaRequest)
- Links from JSON references. - 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/remcohaszing/monaco-yaml/blob/main/index.d.ts) for the API that the plugin
@ -32,7 +31,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,43 +85,7 @@ 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 web worker.
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 ## Examples
@ -133,66 +96,6 @@ A running example: ![demo-image](test-demo.png)
Some usage examples can be found in the Some usage examples can be found in the
[examples](https://github.com/remcohaszing/monaco-yaml/tree/main/examples) directory. [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 ## Contributing
Please see our [contributing guidelines](CONTRIBUTING.md) Please see our [contributing guidelines](CONTRIBUTING.md)

View file

@ -1,13 +1,18 @@
const { promises: fs } = require('fs');
const { join } = require('path');
const { build } = require('esbuild'); const { build } = require('esbuild');
const { dependencies, peerDependencies } = require('./package.json'); const { dependencies, peerDependencies } = require('./package.json');
fs.rm(join(__dirname, 'lib'), { force: true, recursive: true })
.then(() =>
build({ build({
entryPoints: ['src/index.ts', 'src/yaml.worker.ts'], entryPoints: ['src/monaco.contribution.ts', 'src/yaml.worker.ts'],
bundle: true, bundle: true,
external: Object.keys({ ...dependencies, ...peerDependencies }), external: Object.keys({ ...dependencies, ...peerDependencies }),
logLevel: 'info', logLevel: 'info',
outdir: '.', outdir: 'lib/esm',
sourcemap: true, sourcemap: true,
format: 'esm', format: 'esm',
target: ['es2019'], target: ['es2019'],
@ -15,15 +20,22 @@ build({
{ {
name: 'alias', name: 'alias',
setup({ onResolve }) { setup({ onResolve }) {
// The file monaco-yaml/lib/esm/schemaSelectionHandlers.js imports code from the language // The yaml language service only imports re-exports of vscode-languageserver-types from
// server part that we dont want. // vscode-languageserver.
onResolve({ filter: /\/schemaSelectionHandlers$/ }, () => ({ onResolve({ filter: /^vscode-languageserver-textdocument$/ }, () => ({
path: require.resolve('./src/fillers/schemaSelectionHandlers.ts'), path: 'vscode-languageserver-textdocument/lib/esm/main.js',
external: true,
})); }));
// The yaml language service only imports re-exports of vscode-languageserver-types from // The yaml language service only imports re-exports of vscode-languageserver-types from
// vscode-languageserver. // vscode-languageserver.
onResolve({ filter: /^vscode-languageserver(\/node|-protocol)?$/ }, () => ({ onResolve({ filter: /^vscode-languageserver$/ }, () => ({
path: 'vscode-languageserver-types', path: 'vscode-languageserver-types/lib/esm/main.js',
external: true,
}));
// The yaml language service only imports re-exports of vscode-languageserver-types from
// vscode-languageserver.
onResolve({ filter: /^vscode-languageserver-types$/ }, () => ({
path: 'vscode-languageserver-types/lib/esm/main.js',
external: true, external: true,
})); }));
// The yaml language service uses path. We can stub it using path-browserify. // The yaml language service uses path. We can stub it using path-browserify.
@ -33,8 +45,8 @@ build({
})); }));
// The main prettier entry point contains all of Prettier. // The main prettier entry point contains all of Prettier.
// The standalone bundle is smaller and works fine for us. // The standalone bundle is smaller and works fine for us.
onResolve({ filter: /^prettier/ }, ({ path }) => ({ onResolve({ filter: /^prettier$/ }, () => ({
path: path === 'prettier' ? 'prettier/standalone.js' : `${path}.js`, path: 'prettier/standalone',
external: true, external: true,
})); }));
// This tiny filler implementation serves all our needs. // This tiny filler implementation serves all our needs.
@ -51,8 +63,9 @@ build({
}, },
}, },
], ],
}).catch((error) => { }),
// eslint-disable-next-line no-console )
.catch((error) => {
console.error(error); console.error(error);
process.exit(1); process.exit(1);
}); });

View file

@ -2,30 +2,10 @@
This demo is deployed to [monaco-yaml.js.org](https://monaco-yaml.js.org). It shows how 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 `monaco-editor` and `monaco-yaml` can be used with
[Webpack 5](https://webpack.js.org/concepts/entry-points). [Webpack 5](https://webpack.js.org/concepts/entry-points). 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 ```sh
git clone https://github.com/remcohaszing/monaco-yaml npm start
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. The demo will open in your browser.

View file

@ -7,18 +7,17 @@
"build": "webpack --mode production" "build": "webpack --mode production"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^6.0.0", "@fortawesome/fontawesome-free": "^5.15.4",
"@schemastore/schema-catalog": "^0.0.5", "css-loader": "^6.2.0",
"css-loader": "^6.0.0", "css-minimizer-webpack-plugin": "^3.0.2",
"css-minimizer-webpack-plugin": "^3.0.0", "html-webpack-plugin": "^5.3.2",
"html-webpack-plugin": "^5.0.0", "mini-css-extract-plugin": "^2.2.0",
"mini-css-extract-plugin": "^2.0.0", "monaco-editor": "^0.27.0",
"monaco-editor": "^0.31.0",
"monaco-yaml": "file:../..", "monaco-yaml": "file:../..",
"ts-loader": "^9.0.0", "ts-loader": "^9.2.5",
"typescript": "^4.0.0", "typescript": "^4.3.5",
"webpack": "^5.0.0", "webpack": "^5.48.0",
"webpack-cli": "^4.0.0", "webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.0.0" "webpack-dev-server": "^3.11.2"
} }
} }

View file

@ -59,14 +59,6 @@ nav {
margin: 1.5rem; 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 { #breadcrumbs {
border-bottom: 1px solid var(--shadow-color); border-bottom: 1px solid var(--shadow-color);
color: var(--foreground-color); color: var(--foreground-color);

View file

@ -24,11 +24,6 @@
</div> </div>
</nav> </nav>
<div class="editor-wrapper"> <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="breadcrumbs"></div>
<div id="editor"></div> <div id="editor"></div>
<div id="problems"></div> <div id="problems"></div>

View file

@ -1,6 +1,5 @@
import './index.css'; import './index.css';
import { JSONSchemaForSchemaStoreOrgCatalogFiles } from '@schemastore/schema-catalog';
import { CancellationToken } from 'monaco-editor/esm/vs/base/common/cancellation'; import { CancellationToken } from 'monaco-editor/esm/vs/base/common/cancellation';
import { getDocumentSymbols } from 'monaco-editor/esm/vs/editor/contrib/documentSymbols/documentSymbols'; import { getDocumentSymbols } from 'monaco-editor/esm/vs/editor/contrib/documentSymbols/documentSymbols';
import { import {
@ -9,16 +8,13 @@ import {
languages, languages,
Position, Position,
Range, Range,
Uri,
} from 'monaco-editor/esm/vs/editor/editor.api'; } from 'monaco-editor/esm/vs/editor/editor.api';
import { SchemasSettings, setDiagnosticsOptions } from 'monaco-yaml'; import { setDiagnosticsOptions } from 'monaco-yaml';
// NOTE: This will give you all editor featues. If you would prefer to limit to only the editor // 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) // 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 'monaco-editor';
import defaultSchemaUri from './schema.json';
declare global { declare global {
interface Window { interface Window {
MonacoEnvironment: Environment; MonacoEnvironment: Environment;
@ -31,20 +27,70 @@ window.MonacoEnvironment = {
case 'editorWorkerService': case 'editorWorkerService':
return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url)); return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url));
case 'yaml': case 'yaml':
return new Worker(new URL('monaco-yaml/yaml.worker', import.meta.url)); return new Worker(new URL('monaco-yaml/lib/esm/yaml.worker', import.meta.url));
default: default:
throw new Error(`Unknown label ${label}`); throw new Error(`Unknown label ${label}`);
} }
}, },
}; };
const defaultSchema: SchemasSettings = {
uri: defaultSchemaUri,
fileMatch: ['monaco-yaml.yaml'],
};
setDiagnosticsOptions({ setDiagnosticsOptions({
schemas: [defaultSchema], validate: true,
enableSchemaRequest: true,
format: true,
hover: true,
completion: true,
schemas: [
{
// Id of the first schema
uri: 'https://example.com/example-schema.json',
// Associate with our model
fileMatch: ['*'],
schema: {
// Id of the first schema
id: 'https://example.com/example-schema.json',
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: 'https://example.com/example-schema.json',
},
array: {
description: 'It also works in arrays',
items: {
$ref: 'https://example.com/example-schema.json',
},
},
},
},
},
],
}); });
const value = ` const value = `
@ -64,10 +110,6 @@ markdown: hover me to get a markdown based description 😮
enum: enum:
# Unused anchors will be reported
unused anchor: &unused anchor
# Of course numbers are supported! # Of course numbers are supported!
number: 12 number: 12
@ -99,14 +141,6 @@ pointer:
$ref: '#/array' $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. 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.
@ -118,53 +152,11 @@ formatting: Formatting is supported too! Under the hood this is powered by
const ed = editor.create(document.getElementById('editor'), { const ed = editor.create(document.getElementById('editor'), {
automaticLayout: true, automaticLayout: true,
model: editor.createModel(value, 'yaml', Uri.parse('monaco-yaml.yaml')), value,
language: 'yaml',
theme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'vs-dark' : 'vs-light', 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( function* iterateSymbols(
symbols: languages.DocumentSymbol[], symbols: languages.DocumentSymbol[],
position: Position, position: Position,
@ -188,7 +180,6 @@ ed.onDidChangeCursorPosition(async (event) => {
breadcrumb.setAttribute('role', 'button'); breadcrumb.setAttribute('role', 'button');
breadcrumb.classList.add('breadcrumb'); breadcrumb.classList.add('breadcrumb');
breadcrumb.textContent = symbol.name; breadcrumb.textContent = symbol.name;
breadcrumb.title = symbol.detail;
if (symbol.kind === languages.SymbolKind.Array) { if (symbol.kind === languages.SymbolKind.Array) {
breadcrumb.classList.add('array'); breadcrumb.classList.add('array');
} else if (symbol.kind === languages.SymbolKind.Module) { } else if (symbol.kind === languages.SymbolKind.Module) {

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

@ -6,9 +6,12 @@ module.exports = {
output: { output: {
filename: '[contenthash].js', filename: '[contenthash].js',
}, },
devtool: 'source-map',
resolve: { resolve: {
extensions: ['.mjs', '.js', '.ts'], extensions: ['.mjs', '.js', '.ts'],
fallback: {
// Yaml-ast-parser-custom-tags imports buffer. This can be omitted safely.
buffer: false,
},
}, },
module: { module: {
rules: [ rules: [
@ -19,11 +22,7 @@ module.exports = {
{ {
// Monaco editor uses .ttf icons. // Monaco editor uses .ttf icons.
test: /\.(svg|ttf)$/, test: /\.(svg|ttf)$/,
type: 'asset/resource', type: 'asset',
},
{
test: /schema\.json$/,
type: 'asset/resource',
}, },
{ {
test: /\.ts$/, test: /\.ts$/,

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

100
index.d.ts vendored
View file

@ -1,96 +1,58 @@
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 {

24602
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,17 +1,18 @@
{ {
"name": "monaco-yaml", "name": "monaco-yaml",
"version": "4.0.0-alpha.1", "version": "3.1.0",
"description": "YAML plugin for the Monaco Editor", "description": "YAML plugin for the Monaco Editor",
"homepage": "https://monaco-yaml.js.org", "homepage": "https://monaco-yaml.js.org",
"scripts": { "scripts": {
"prepack": "node build.js", "prepack": "node build.js",
"prepare": "husky install" "prepare": "husky install"
}, },
"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/*"
@ -37,29 +38,31 @@
"yaml" "yaml"
], ],
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.0", "@types/json-schema": "^7.0.9",
"jsonc-parser": "^3.0.0", "js-yaml": "^4.1.0",
"path-browserify": "^1.0.0", "path-browserify": "^1.0.1",
"prettier": "2.0.5", "prettier": "2.0.5",
"vscode-languageserver-textdocument": "^1.0.0", "vscode-languageserver-textdocument": "^1.0.1",
"vscode-languageserver-types": "^3.0.0", "vscode-languageserver-types": "^3.16.0",
"yaml": "2.0.0-10" "yaml-language-server-parser": "^0.1.2"
}, },
"peerDependencies": { "peerDependencies": {
"monaco-editor": ">=0.30" "monaco-editor": ">=0.22"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^4.29.0",
"esbuild": "^0.14.0", "@typescript-eslint/parser": "^4.29.0",
"eslint": "^7.0.0", "esbuild": "^0.12.20",
"eslint-config-remcohaszing": "^3.0.0", "eslint": "^7.32.0",
"husky": "^7.0.0", "eslint-config-remcohaszing": "^3.5.0",
"lint-staged": "^12.0.0", "husky": "^7.0.1",
"monaco-editor": "^0.31.0", "lint-staged": "^11.1.1",
"type-fest": "^2.0.0", "monaco-editor": "^0.27.0",
"typescript": "^4.0.0", "type-fest": "^2.1.0",
"yaml-language-server": "^1.0.0" "typescript": "^4.3.5",
"yaml-language-server": "^0.22.0"
}, },
"resolutions": {},
"lint-staged": { "lint-staged": {
"*.{css,json,md,html,yaml}": [ "*.{css,json,md,html,yaml}": [
"prettier --write" "prettier --write"

View file

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

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,17 +1,16 @@
import { import {
editor, editor,
IDisposable, IDisposable,
IMarkdownString,
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/lib/esm/languageservice/yamlLanguageService';
import { languageId } from './constants';
import { YAMLWorker } from './yamlWorker'; import { YAMLWorker } from './yamlWorker';
export type WorkerAccessor = (...more: Uri[]) => PromiseLike<YAMLWorker>; export type WorkerAccessor = (...more: Uri[]) => PromiseLike<YAMLWorker>;
@ -33,17 +32,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,84 +42,96 @@ 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 function createDiagnosticsAdapter(
languageId: string,
getWorker: WorkerAccessor, getWorker: WorkerAccessor,
defaults: languages.yaml.LanguageServiceDefaults, defaults: languages.yaml.LanguageServiceDefaults,
): void { ): IDisposable {
const listeners = new Map<string, IDisposable>(); let disposables: IDisposable[] = [];
const listeners: Record<string, IDisposable> = Object.create(null);
const resetSchema = async (resource: Uri): Promise<void> => { const resetSchema = async (resource: Uri): Promise<void> => {
const worker = await getWorker(); const worker = await getWorker();
worker.resetSchema(String(resource)); worker.resetSchema(String(resource));
}; };
const doValidate = async (resource: Uri): Promise<void> => { const doValidate = async (resource: Uri, languageId: string): Promise<void> => {
const worker = await getWorker(resource); const worker = await getWorker(resource);
const diagnostics = await worker.doValidation(String(resource)); const diagnostics = await worker.doValidation(String(resource));
const markers = diagnostics.map(toDiagnostics); const markers = diagnostics.map((d) => toDiagnostics(resource, d));
const model = editor.getModel(resource); const model = editor.getModel(resource);
// Return value from getModel can be null if model not found if (model.getModeId() === languageId) {
// (e.g. if user navigates away from editor)
if (model && model.getLanguageId() === languageId) {
editor.setModelMarkers(model, languageId, markers); editor.setModelMarkers(model, languageId, markers);
} }
}; };
const onModelAdd = (model: editor.IModel): void => { const onModelAdd = (model: editor.IModel): void => {
if (model.getLanguageId() !== languageId) { const modeId = model.getModeId();
if (modeId !== languageId) {
return; return;
} }
let handle: ReturnType<typeof setTimeout>; let handle: ReturnType<typeof setTimeout>;
listeners.set( listeners[String(toString)] = model.onDidChangeContent(() => {
String(model.uri),
model.onDidChangeContent(() => {
clearTimeout(handle); clearTimeout(handle);
handle = setTimeout(() => doValidate(model.uri), 500); handle = setTimeout(() => doValidate(model.uri, modeId), 500);
}), });
);
doValidate(model.uri); doValidate(model.uri, modeId);
}; };
const onModelRemoved = (model: editor.IModel): void => { const onModelRemoved = (model: editor.IModel): void => {
editor.setModelMarkers(model, languageId, []); editor.setModelMarkers(model, languageId, []);
const uriStr = String(model.uri); const uriStr = String(model.uri);
const listener = listeners.get(uriStr); const listener = listeners[uriStr];
if (listener) { if (listener) {
listener.dispose(); listener.dispose();
listeners.delete(uriStr); delete listeners[uriStr];
} }
}; };
editor.onDidCreateModel(onModelAdd); disposables.push(
editor.onDidCreateModel(onModelAdd),
editor.onWillDisposeModel((model) => { editor.onWillDisposeModel((model) => {
onModelRemoved(model); onModelRemoved(model);
resetSchema(model.uri); 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); 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() === languageId) {
onModelRemoved(model); onModelRemoved(model);
onModelAdd(model); onModelAdd(model);
} }
}
}); });
}),
for (const model of editor.getModels()) { {
onModelAdd(model); dispose: () => {
editor.getModels().forEach(onModelRemoved);
for (const disposable of Object.values(listeners)) {
disposable.dispose();
} }
},
},
);
editor.getModels().forEach(onModelAdd);
return {
dispose() {
disposables.forEach((d) => d && d.dispose());
disposables = [];
},
};
} }
// --- completion ------ // --- completion ------
@ -152,7 +155,7 @@ function toRange(range: ls.Range): Range {
); );
} }
function toCompletionItemKind(kind: ls.CompletionItemKind): languages.CompletionItemKind { function toCompletionItemKind(kind: languages.CompletionItemKind): languages.CompletionItemKind {
const mItemKind = languages.CompletionItemKind; const mItemKind = languages.CompletionItemKind;
switch (kind) { switch (kind) {
@ -264,25 +267,41 @@ export function createCompletionItemProvider(
}; };
} }
// --- 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 ------
@ -298,7 +317,7 @@ export function createHoverProvider(getWorker: WorkerAccessor): languages.HoverP
} }
return { return {
range: toRange(info.range), range: toRange(info.range),
contents: [{ value: (info.contents as ls.MarkupContent).value }], contents: toMarkedStringArray(info.contents),
}; };
}, },
}; };
@ -353,12 +372,12 @@ 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: [],
}; };
} }
@ -375,7 +394,7 @@ export function createDocumentSymbolProvider(
if (!items) { if (!items) {
return; return;
} }
return items.map(toDocumentSymbol); return items.map((item) => toDocumentSymbol(item));
}, },
}; };
} }

View file

@ -1,23 +1,11 @@
import { Emitter, languages } from 'monaco-editor/esm/vs/editor/editor.api.js'; import { Emitter, languages } from 'monaco-editor/esm/vs/editor/editor.api';
import { languageId } from './constants';
import { setupMode } from './yamlMode'; import { setupMode } from './yamlMode';
// --- YAML configuration and defaults --------- // --- 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( export function createLanguageServiceDefaults(
languageId: string,
initialDiagnosticsOptions: languages.yaml.DiagnosticsOptions, initialDiagnosticsOptions: languages.yaml.DiagnosticsOptions,
): languages.yaml.LanguageServiceDefaults { ): languages.yaml.LanguageServiceDefaults {
const onDidChange = new Emitter<languages.yaml.LanguageServiceDefaults>(); const onDidChange = new Emitter<languages.yaml.LanguageServiceDefaults>();
@ -37,7 +25,7 @@ export function createLanguageServiceDefaults(
}, },
setDiagnosticsOptions(options) { setDiagnosticsOptions(options) {
diagnosticsOptions = { ...diagnosticDefault, ...options }; diagnosticsOptions = options || {};
onDidChange.fire(languageServiceDefaults); onDidChange.fire(languageServiceDefaults);
}, },
}; };
@ -45,7 +33,13 @@ export function createLanguageServiceDefaults(
return languageServiceDefaults; return languageServiceDefaults;
} }
const yamlDefaults = createLanguageServiceDefaults(diagnosticDefault); const diagnosticDefault: languages.yaml.DiagnosticsOptions = {
validate: true,
schemas: [],
enableSchemaRequest: false,
};
const yamlDefaults = createLanguageServiceDefaults('yaml', diagnosticDefault);
// Export API // Export API
function createAPI(): typeof languages.yaml { function createAPI(): typeof languages.yaml {
@ -58,7 +52,7 @@ languages.yaml = createAPI();
// --- Registration to monaco editor --- // --- Registration to monaco editor ---
languages.register({ languages.register({
id: languageId, id: 'yaml',
extensions: ['.yaml', '.yml'], extensions: ['.yaml', '.yml'],
aliases: ['YAML', 'yaml', 'YML', 'yml'], aliases: ['YAML', 'yaml', 'YML', 'yml'],
mimetypes: ['application/x-yaml'], mimetypes: ['application/x-yaml'],

View file

@ -1,14 +1,17 @@
import { editor, languages } from 'monaco-editor/esm/vs/editor/editor.api.js'; import { editor, IDisposable, languages, Uri } from 'monaco-editor/esm/vs/editor/editor.api';
import { WorkerAccessor } from './languageFeatures';
import { YAMLWorker } from './yamlWorker'; import { YAMLWorker } from './yamlWorker';
export interface WorkerManager extends IDisposable {
getLanguageServiceWorker: (...resources: Uri[]) => Promise<YAMLWorker>;
}
// 2min // 2min
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000; const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000;
export function createWorkerManager( export function createWorkerManager(
defaults: languages.yaml.LanguageServiceDefaults, defaults: languages.yaml.LanguageServiceDefaults,
): WorkerAccessor { ): WorkerManager {
let worker: editor.MonacoWebWorker<YAMLWorker>; let worker: editor.MonacoWebWorker<YAMLWorker>;
let client: Promise<YAMLWorker>; let client: Promise<YAMLWorker>;
let lastUsedTime = 0; let lastUsedTime = 0;
@ -16,12 +19,12 @@ export function createWorkerManager(
const stopWorker = (): void => { const stopWorker = (): void => {
if (worker) { if (worker) {
worker.dispose(); worker.dispose();
worker = undefined; worker = null;
} }
client = undefined; client = null;
}; };
setInterval(() => { const idleCheckInterval = setInterval(() => {
if (!worker) { if (!worker) {
return; return;
} }
@ -31,8 +34,7 @@ export function createWorkerManager(
} }
}, 30 * 1000); }, 30 * 1000);
// This is necessary to have updated language options take effect (e.g. schema changes) const configChangeListener = defaults.onDidChange(() => stopWorker());
defaults.onDidChange(() => stopWorker());
const getClient = (): Promise<YAMLWorker> => { const getClient = (): Promise<YAMLWorker> => {
lastUsedTime = Date.now(); lastUsedTime = Date.now();
@ -47,9 +49,10 @@ export function createWorkerManager(
// Passed in to the create() method // Passed in to the create() method
createData: { createData: {
languageSettings: defaults.diagnosticsOptions, languageSettings: defaults.diagnosticsOptions,
languageId: defaults.languageId,
enableSchemaRequest: defaults.diagnosticsOptions.enableSchemaRequest, enableSchemaRequest: defaults.diagnosticsOptions.enableSchemaRequest,
prefix: defaults.diagnosticsOptions.prefix,
isKubernetes: defaults.diagnosticsOptions.isKubernetes, isKubernetes: defaults.diagnosticsOptions.isKubernetes,
customTags: defaults.diagnosticsOptions.customTags,
}, },
}); });
@ -59,9 +62,17 @@ export function createWorkerManager(
return client; return client;
}; };
return async (...resources) => { return {
dispose() {
clearInterval(idleCheckInterval);
configChangeListener.dispose();
stopWorker();
},
async getLanguageServiceWorker(...resources) {
const client = await getClient(); const client = await getClient();
await worker.withSyncedResources(resources); await worker.withSyncedResources(resources);
return client; return client;
},
}; };
} }

View file

@ -1,7 +1,7 @@
import { initialize } from 'monaco-editor/esm/vs/editor/editor.worker.js'; import { initialize } from 'monaco-editor/esm/vs/editor/editor.worker';
import { createYAMLWorker, ICreateData } from './yamlWorker'; import { createYAMLWorker } from './yamlWorker';
self.onmessage = () => { self.onmessage = () => {
initialize((ctx, createData: ICreateData) => Object.create(createYAMLWorker(ctx, createData))); initialize((ctx, createData) => Object.create(createYAMLWorker(ctx, createData)));
}; };

View file

@ -1,16 +1,16 @@
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 { import {
createCompletionItemProvider, createCompletionItemProvider,
createDefinitionProvider,
createDiagnosticsAdapter, createDiagnosticsAdapter,
createDocumentFormattingEditProvider, createDocumentFormattingEditProvider,
createDocumentSymbolProvider, createDocumentSymbolProvider,
createHoverProvider, createHoverProvider,
createLinkProvider, createLinkProvider,
WorkerAccessor,
} from './languageFeatures'; } from './languageFeatures';
import { createWorkerManager } from './workerManager'; import { createWorkerManager } from './workerManager';
import { YAMLWorker } from './yamlWorker';
const richEditConfiguration: languages.LanguageConfiguration = { const richEditConfiguration: languages.LanguageConfiguration = {
comments: { comments: {
@ -45,17 +45,26 @@ const richEditConfiguration: languages.LanguageConfiguration = {
}; };
export function setupMode(defaults: languages.yaml.LanguageServiceDefaults): void { export function setupMode(defaults: languages.yaml.LanguageServiceDefaults): void {
const worker = createWorkerManager(defaults); const disposables: IDisposable[] = [];
languages.registerCompletionItemProvider(languageId, createCompletionItemProvider(worker)); const client = createWorkerManager(defaults);
languages.registerHoverProvider(languageId, createHoverProvider(worker)); disposables.push(client);
languages.registerDefinitionProvider(languageId, createDefinitionProvider(worker));
languages.registerDocumentSymbolProvider(languageId, createDocumentSymbolProvider(worker)); const worker: WorkerAccessor = (...uris: Uri[]): Promise<YAMLWorker> =>
client.getLanguageServiceWorker(...uris);
const { languageId } = defaults;
disposables.push(
languages.registerCompletionItemProvider(languageId, createCompletionItemProvider(worker)),
languages.registerHoverProvider(languageId, createHoverProvider(worker)),
languages.registerDocumentSymbolProvider(languageId, createDocumentSymbolProvider(worker)),
languages.registerDocumentFormattingEditProvider( languages.registerDocumentFormattingEditProvider(
languageId, languageId,
createDocumentFormattingEditProvider(worker), createDocumentFormattingEditProvider(worker),
),
languages.registerLinkProvider(languageId, createLinkProvider(worker)),
createDiagnosticsAdapter(languageId, worker, defaults),
languages.setLanguageConfiguration(languageId, richEditConfiguration),
); );
languages.registerLinkProvider(languageId, createLinkProvider(worker));
createDiagnosticsAdapter(worker, defaults);
languages.setLanguageConfiguration(languageId, richEditConfiguration);
} }

View file

@ -1,4 +1,4 @@
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 { Promisable } from 'type-fest';
import { TextDocument } from 'vscode-languageserver-textdocument'; import { TextDocument } from 'vscode-languageserver-textdocument';
import * as ls from 'vscode-languageserver-types'; import * as ls from 'vscode-languageserver-types';
@ -6,16 +6,12 @@ import {
CustomFormatterOptions, CustomFormatterOptions,
getLanguageService, getLanguageService,
LanguageSettings, LanguageSettings,
} from 'yaml-language-server/lib/esm/languageservice/yamlLanguageService.js'; } from 'yaml-language-server/lib/esm/languageservice/yamlLanguageService';
import { languageId } from './constants'; let defaultSchemaRequestService: (url: string) => Promise<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 interface YAMLWorker {
@ -23,8 +19,6 @@ export interface YAMLWorker {
doComplete: (uri: string, position: ls.Position) => Promisable<ls.CompletionList>; doComplete: (uri: string, position: ls.Position) => Promisable<ls.CompletionList>;
doDefinition: (uri: string, position: ls.Position) => Promisable<ls.LocationLink[]>;
doHover: (uri: string, position: ls.Position) => Promisable<ls.Hover>; doHover: (uri: string, position: ls.Position) => Promisable<ls.Hover>;
format: (uri: string, options: CustomFormatterOptions) => Promisable<ls.TextEdit[]>; format: (uri: string, options: CustomFormatterOptions) => Promisable<ls.TextEdit[]>;
@ -38,16 +32,21 @@ export interface YAMLWorker {
export function createYAMLWorker( export function createYAMLWorker(
ctx: worker.IWorkerContext, ctx: worker.IWorkerContext,
{ enableSchemaRequest, languageSettings }: ICreateData, {
enableSchemaRequest,
isKubernetes = false,
languageId,
languageSettings,
prefix = '',
}: ICreateData,
): YAMLWorker { ): YAMLWorker {
const languageService = getLanguageService( const service = (url: string): Promise<string> => defaultSchemaRequestService(`${prefix}${url}`);
enableSchemaRequest ? schemaRequestService : null, const languageService = getLanguageService(enableSchemaRequest && service, null, null, null);
null, languageService.configure({
null, ...languageSettings,
null, hover: true,
null, isKubernetes,
); });
languageService.configure(languageSettings);
const getTextDocument = (uri: string): TextDocument => { const getTextDocument = (uri: string): TextDocument => {
const models = ctx.getMirrorModels(); const models = ctx.getMirrorModels();
@ -63,19 +62,14 @@ export function createYAMLWorker(
doValidation(uri) { doValidation(uri) {
const document = getTextDocument(uri); const document = getTextDocument(uri);
if (document) { if (document) {
return languageService.doValidation(document, languageSettings.isKubernetes); return languageService.doValidation(document, isKubernetes);
} }
return []; return [];
}, },
doComplete(uri, position) { doComplete(uri, position) {
const document = getTextDocument(uri); const document = getTextDocument(uri);
return languageService.doComplete(document, position, languageSettings.isKubernetes); return languageService.doComplete(document, position, isKubernetes);
},
doDefinition(uri, position) {
const document = getTextDocument(uri);
return languageService.doDefinition(document, { position, textDocument: { uri } });
}, },
doHover(uri, position) { doHover(uri, position) {
@ -99,13 +93,15 @@ export function createYAMLWorker(
findLinks(uri) { findLinks(uri) {
const document = getTextDocument(uri); const document = getTextDocument(uri);
return languageService.findLinks(document); return Promise.resolve(languageService.findLinks(document));
}, },
}; };
} }
export interface ICreateData { export interface ICreateData {
languageId: string;
languageSettings: LanguageSettings; languageSettings: LanguageSettings;
enableSchemaRequest: boolean; enableSchemaRequest: boolean;
prefix?: string;
isKubernetes?: boolean; isKubernetes?: boolean;
} }