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:
- remcohaszing
- remcohaszing/typechecking
extends: remcohaszing
rules:
class-methods-use-this: off
max-classes-per-file: off
no-console: 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-unnecessary-condition': off
'@typescript-eslint/prefer-optional-chain': off
import/extensions: off
import/no-extraneous-dependencies: off
import/no-unresolved: off
import/no-webpack-loader-syntax: off
jsdoc/require-jsdoc: off
node/no-extraneous-import: off
node/no-unsupported-features/es-syntax: off
node/no-unsupported-features/node-builtins: off
node/no-unpublished-import: off

View file

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

5
.gitignore vendored
View file

@ -1,7 +1,6 @@
dist/
node_modules/
/index.js
/yaml.worker.js
/out/
/lib/
*.log
*.map
*.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/
/index.js
/yaml.worker.js
/dist/
/lib/
/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
[![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
files:
- Code completion, based on JSON schemas or by looking at similar objects in the same file
- Hovers, based on JSON schemas
- Validation: Syntax errors and schema validation
- Formatting using Prettier
- Formatting
- Document Symbols
- Syntax highlighting
- 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
[here](https://github.com/remcohaszing/monaco-yaml/blob/main/index.d.ts) for the API that the plugin
offers to configure the YAML language support.
[here](https://github.com/Microsoft/monaco-json/blob/master/index.d.ts) for the API that the JSON
plugin offers to configure the JSON language support.
## Installation
@ -32,7 +25,7 @@ npm install monaco-yaml
Import `monaco-yaml` and configure it before an editor instance is created.
```typescript
```ts
import { editor, Uri } from 'monaco-editor';
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.
Other bundlers may use a different syntax, but the idea is the same. Languages you dont used can be
omitted.
Also make sure to register the service worker. See the
[examples](https://github.com/pengx17/monaco-yaml/tree/master/examples) directory for full fledged
examples.
```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}`);
}
},
};
```
## Development
## Examples
A demo is available on [monaco-yaml.js.org](https://monaco-yaml.js.org).
- `git clone https://github.com/pengx17/monaco-yaml`
- `cd monaco-yaml`
- `npm ci`
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
Originally [@kpdecker](https://github.com/kpdecker) forked this repository from
[`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`.
- https://github.com/redhat-developer/yaml-language-server
## 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>
</head>
<body>
<div id="editor" style="width: 800px; height: 600px;"></div>
<script type="module" src="/index.js"></script>
<div id="editor"></div>
</body>
</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 EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import './index.css';
import { editor } from 'monaco-editor/esm/vs/editor/editor.api';
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 = {
getWorker(moduleId, label) {
switch (label) {
case 'editorWorkerService':
return new EditorWorker();
case 'yaml':
return new YamlWorker();
default:
throw new Error(`Unknown label ${label}`);
getWorkerUrl(moduleId, label) {
if (label === 'yaml') {
return './yaml.worker.bundle.js';
}
return './editor.worker.bundle.js';
},
};
// The uri is used for the schema file match.
const modelUri = Uri.parse('a://b/foo.yaml');
setDiagnosticsOptions({
validate: true,
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)],
fileMatch: ['*'],
schema: {
// Id of the first schema
id: 'http://myserver/foo-schema.json',
type: 'object',
properties: {
p1: {
@ -48,6 +46,8 @@ setDiagnosticsOptions({
// 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: {
@ -59,9 +59,7 @@ setDiagnosticsOptions({
],
});
const value = 'p1: \np2: \n';
editor.create(document.getElementById('editor'), {
automaticLayout: true,
model: editor.createModel(value, 'yaml', modelUri),
value: 'p1: ',
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 { IEvent, languages } from 'monaco-editor/esm/vs/editor/editor.api';
export interface SchemasSettings {
/**
* A `Uri` file match which will trigger the schema validation. This may be a glob or an exact
* path.
*
* @example '.gitlab-ci.yml'
* @example 'file://**\/.github/actions/*.yaml'
*/
fileMatch: string[];
/**
* The JSON schema which will be used for validation. If not specified, it will be downloaded from
* `uri`.
*/
schema?: JSONSchema4 | JSONSchema6 | JSONSchema7;
/**
* The source URI of the JSON schema. The JSON schema will be downloaded from here if no schema
* was supplied. It will also be displayed as the source in hover tooltips.
*/
uri: string;
}
declare module 'monaco-editor/esm/vs/editor/editor.api' {
namespace languages.yaml {
export interface DiagnosticsOptions {
/**
* If set, enable schema based autocompletion.
*
* @default true
*/
readonly completion?: boolean;
/**
* A list of custom tags.
*
* @default []
*/
readonly customTags?: string[];
/**
* If set, the schema service would load schema content on-demand with 'fetch' if available
*
* @default false
*/
readonly enableSchemaRequest?: boolean;
/**
* If true, formatting using Prettier is enabled. Setting this to `false` does **not** exclude
* Prettier from the bundle.
*
* @default true
*/
readonly format?: boolean;
/**
* If set, enable hover typs based the JSON schema.
*
* @default true
*/
readonly hover?: boolean;
/**
* If true, a different diffing algorithm is used to generate error messages.
*
* @default false
*/
readonly isKubernetes?: boolean;
/**
* A list of known schemas and/or associations of schemas to file names.
*
* @default []
*/
readonly schemas?: SchemasSettings[];
/**
* If set, the validator will be enabled and perform syntax validation as well as schema
* based validation.
*
* @default true
*/
readonly validate?: boolean;
/**
* The YAML version to use for parsing.
*
* @default '1.2'
* A list of known schemas and/or associations of schemas to file names.
*/
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 {
readonly onDidChange: IEvent<LanguageServiceDefaults>;
readonly languageId: string;
readonly diagnosticsOptions: DiagnosticsOptions;
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'

30398
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,64 +1,59 @@
{
"name": "monaco-yaml",
"version": "4.0.0-alpha.1",
"version": "3.0.0",
"description": "YAML plugin for the Monaco Editor",
"homepage": "https://monaco-yaml.js.org",
"scripts": {
"prepack": "node build.js",
"prepare": "husky install"
"watch": "tsc -p ./src --watch",
"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",
"files": [
"index.js",
"index.d.ts",
"yaml.worker.js"
"lib",
"index.d.ts"
],
"workspaces": [
"examples/*"
],
"author": "Kevin Decker <kpdecker@gmail.com> (http://incaseofstairs.com)",
"maintainers": [
"Remco Haszing <remcohaszing@gmail.com> (https://github.com/remcohaszing)"
"kpdecker",
"pengx17"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/remcohaszing/monaco-yaml"
"url": "https://github.com/pengx17/monaco-yaml"
},
"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": {
"@types/json-schema": "^7.0.0",
"jsonc-parser": "^3.0.0",
"path-browserify": "^1.0.0",
"prettier": "2.0.5",
"vscode-languageserver-textdocument": "^1.0.0",
"vscode-languageserver-types": "^3.0.0",
"yaml": "2.0.0-10"
"@types/json-schema": "^7.0.8",
"js-yaml": "^3.14.1",
"yaml-ast-parser-custom-tags": "^0.0.43"
},
"peerDependencies": {
"monaco-editor": ">=0.30"
"monaco-editor": ">=0.22"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.0.0",
"esbuild": "^0.14.0",
"eslint": "^7.0.0",
"eslint-config-remcohaszing": "^3.0.0",
"husky": "^7.0.0",
"lint-staged": "^12.0.0",
"monaco-editor": "^0.31.0",
"type-fest": "^2.0.0",
"typescript": "^4.0.0",
"yaml-language-server": "^1.0.0"
"@typescript-eslint/eslint-plugin": "^4.28.3",
"@typescript-eslint/parser": "^4.28.3",
"eslint": "^7.30.0",
"eslint-config-remcohaszing": "^3.4.0",
"husky": "^7.0.1",
"lint-staged": "^10.5.4",
"monaco-editor": "^0.26.1",
"monaco-plugin-helpers": "^1.0.3",
"prettier": "2.0.5",
"rimraf": "^3.0.2",
"typescript": "^4.3.5",
"yaml-language-server": "^0.11.1"
},
"lint-staged": {
"*.{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 {
return args.length === 0
? message
: message.replace(/{(\d+)}/g, (match, rest: number[]) => {
: message.replace(/{(\d+)}/g, (match, rest) => {
const [index] = rest;
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 {
editor,
IDisposable,
IMarkdownString,
IRange,
languages,
MarkerSeverity,
MarkerTag,
Position,
Range,
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 { 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';
export type WorkerAccessor = (...more: Uri[]) => PromiseLike<YAMLWorker>;
// --- diagnostics --- ---
function toSeverity(lsSeverity: ls.DiagnosticSeverity): MarkerSeverity {
function toSeverity(lsSeverity: number): MarkerSeverity {
switch (lsSeverity) {
case ls.DiagnosticSeverity.Error:
return MarkerSeverity.Error;
@ -33,17 +34,9 @@ function toSeverity(lsSeverity: ls.DiagnosticSeverity): MarkerSeverity {
}
}
function toMarkerDataTag(tag: ls.DiagnosticTag): MarkerTag {
switch (tag) {
case ls.DiagnosticTag.Deprecated:
return MarkerTag.Deprecated;
case ls.DiagnosticTag.Unnecessary:
return MarkerTag.Unnecessary;
default:
}
}
function toDiagnostics(resource: Uri, diag: ls.Diagnostic): editor.IMarkerData {
const code = typeof diag.code === 'number' ? String(diag.code) : (diag.code as string);
function toDiagnostics(diag: ls.Diagnostic): editor.IMarkerData {
return {
severity: toSeverity(diag.severity),
startLineNumber: diag.range.start.line + 1,
@ -51,83 +44,102 @@ function toDiagnostics(diag: ls.Diagnostic): editor.IMarkerData {
endLineNumber: diag.range.end.line + 1,
endColumn: diag.range.end.character + 1,
message: diag.message,
code: String(diag.code),
code,
source: diag.source,
tags: diag.tags?.map(toMarkerDataTag),
};
}
export function createDiagnosticsAdapter(
getWorker: WorkerAccessor,
defaults: languages.yaml.LanguageServiceDefaults,
): void {
const listeners = new Map<string, IDisposable>();
export class DiagnosticsAdapter {
private _disposables: IDisposable[] = [];
private _listener: Record<string, IDisposable> = Object.create(null);
const resetSchema = async (resource: Uri): Promise<void> => {
const worker = await getWorker();
worker.resetSchema(String(resource));
};
constructor(
private _languageId: string,
private _worker: WorkerAccessor,
defaults: LanguageServiceDefaultsImpl,
) {
const onModelAdd = (model: editor.IModel): void => {
const modeId = model.getModeId();
if (modeId !== this._languageId) {
return;
}
const doValidate = async (resource: Uri): Promise<void> => {
const worker = await getWorker(resource);
const diagnostics = await worker.doValidation(String(resource));
const markers = diagnostics.map(toDiagnostics);
const model = editor.getModel(resource);
// Return value from getModel can be null if model not found
// (e.g. if user navigates away from editor)
if (model && model.getLanguageId() === languageId) {
editor.setModelMarkers(model, languageId, markers);
}
};
const onModelAdd = (model: editor.IModel): void => {
if (model.getLanguageId() !== languageId) {
return;
}
let handle: ReturnType<typeof setTimeout>;
listeners.set(
String(model.uri),
model.onDidChangeContent(() => {
let handle: number;
this._listener[String(toString)] = model.onDidChangeContent(() => {
clearTimeout(handle);
handle = setTimeout(() => doValidate(model.uri), 500);
handle = setTimeout(() => this._doValidate(model.uri, modeId), 500);
});
this._doValidate(model.uri, modeId);
};
const onModelRemoved = (model: editor.IModel): void => {
editor.setModelMarkers(model, this._languageId, []);
const uriStr = String(model.uri);
const listener = this._listener[uriStr];
if (listener) {
listener.dispose();
delete this._listener[uriStr];
}
};
this._disposables.push(
editor.onDidCreateModel(onModelAdd),
editor.onWillDisposeModel((model) => {
onModelRemoved(model);
this._resetSchema(model.uri);
}),
editor.onDidChangeModelLanguage((event) => {
onModelRemoved(event.model);
onModelAdd(event.model);
this._resetSchema(event.model.uri);
}),
defaults.onDidChange(() => {
editor.getModels().forEach((model) => {
if (model.getModeId() === this._languageId) {
onModelRemoved(model);
onModelAdd(model);
}
});
}),
{
dispose: () => {
editor.getModels().forEach(onModelRemoved);
for (const disposable of Object.values(this._listener)) {
disposable.dispose();
}
},
},
);
doValidate(model.uri);
};
editor.getModels().forEach(onModelAdd);
}
const onModelRemoved = (model: editor.IModel): void => {
editor.setModelMarkers(model, languageId, []);
const uriStr = String(model.uri);
const listener = listeners.get(uriStr);
if (listener) {
listener.dispose();
listeners.delete(uriStr);
}
};
dispose(): void {
this._disposables.forEach((d) => d && d.dispose());
this._disposables = [];
}
editor.onDidCreateModel(onModelAdd);
editor.onWillDisposeModel((model) => {
onModelRemoved(model);
resetSchema(model.uri);
});
editor.onDidChangeModelLanguage((event) => {
onModelRemoved(event.model);
onModelAdd(event.model);
resetSchema(event.model.uri);
});
defaults.onDidChange(() => {
for (const model of editor.getModels()) {
if (model.getLanguageId() === languageId) {
onModelRemoved(model);
onModelAdd(model);
}
}
});
private _resetSchema(resource: Uri): void {
this._worker().then((worker) => {
worker.resetSchema(String(resource));
});
}
for (const model of editor.getModels()) {
onModelAdd(model);
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 };
}
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 {
if (!range) {
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;
switch (kind) {
@ -207,101 +231,122 @@ function toTextEdit(textEdit: ls.TextEdit): editor.ISingleEditOperation {
};
}
export function createCompletionItemProvider(
getWorker: WorkerAccessor,
): languages.CompletionItemProvider {
return {
triggerCharacters: [' ', ':'],
export class CompletionAdapter implements languages.CompletionItemProvider {
triggetCharacters = [' ', ':'];
async provideCompletionItems(model, position) {
const resource = model.uri;
constructor(private _worker: WorkerAccessor) {}
const worker = await getWorker(resource);
const info = await worker.doComplete(String(resource), fromPosition(position));
if (!info) {
return;
}
provideCompletionItems(
model: editor.IReadOnlyModel,
position: Position,
): PromiseLike<languages.CompletionList> {
const resource = model.uri;
const wordInfo = model.getWordUntilPosition(position);
const wordRange = new Range(
position.lineNumber,
wordInfo.startColumn,
position.lineNumber,
wordInfo.endColumn,
);
return this._worker(resource)
.then((worker) => worker.doComplete(String(resource), fromPosition(position)))
.then((info) => {
if (!info) {
return;
}
const items = info.items.map((entry) => {
const item: languages.CompletionItem = {
label: entry.label,
insertText: entry.insertText || entry.label,
sortText: entry.sortText,
filterText: entry.filterText,
documentation: entry.documentation,
detail: entry.detail,
kind: toCompletionItemKind(entry.kind),
range: wordRange,
const wordInfo = model.getWordUntilPosition(position);
const wordRange = new Range(
position.lineNumber,
wordInfo.startColumn,
position.lineNumber,
wordInfo.endColumn,
);
const items: languages.CompletionItem[] = info.items.map((entry) => {
const item: languages.CompletionItem = {
label: entry.label,
insertText: entry.insertText || entry.label,
sortText: entry.sortText,
filterText: entry.filterText,
documentation: entry.documentation,
detail: entry.detail,
kind: toCompletionItemKind(entry.kind),
range: wordRange,
};
if (entry.textEdit) {
item.range = toRange(
'range' in entry.textEdit ? entry.textEdit.range : entry.textEdit.replace,
);
item.insertText = entry.textEdit.newText;
}
if (entry.additionalTextEdits) {
item.additionalTextEdits = entry.additionalTextEdits.map(toTextEdit);
}
if (entry.insertTextFormat === ls.InsertTextFormat.Snippet) {
item.insertTextRules = languages.CompletionItemInsertTextRule.InsertAsSnippet;
}
return item;
});
return {
isIncomplete: info.isIncomplete,
suggestions: items,
};
if (entry.textEdit) {
item.range = toRange(
'range' in entry.textEdit ? entry.textEdit.range : entry.textEdit.replace,
);
item.insertText = entry.textEdit.newText;
}
if (entry.additionalTextEdits) {
item.additionalTextEdits = entry.additionalTextEdits.map(toTextEdit);
}
if (entry.insertTextFormat === ls.InsertTextFormat.Snippet) {
item.insertTextRules = languages.CompletionItemInsertTextRule.InsertAsSnippet;
}
return item;
});
return {
incomplete: info.isIncomplete,
suggestions: items,
};
},
};
}
}
// --- definition ------
function isMarkupContent(thing: unknown): thing is ls.MarkupContent {
return thing && typeof thing === 'object' && typeof (thing as ls.MarkupContent).kind === 'string';
}
export function createDefinitionProvider(getWorker: WorkerAccessor): languages.DefinitionProvider {
return {
async provideDefinition(model, position) {
const resource = model.uri;
function toMarkdownString(entry: ls.MarkedString | ls.MarkupContent): IMarkdownString {
if (typeof entry === 'string') {
return {
value: entry,
};
}
if (isMarkupContent(entry)) {
if (entry.kind === 'plaintext') {
return {
value: entry.value.replace(/[!#()*+.[\\\]_`{}-]/g, '\\$&'),
};
}
return {
value: entry.value,
};
}
const worker = await getWorker(resource);
const definitions = await worker.doDefinition(String(resource), fromPosition(position));
return { value: `\`\`\`${entry.language}\n${entry.value}\n\`\`\`\n` };
}
return definitions?.map((definition) => ({
originSelectionRange: definition.originSelectionRange,
range: toRange(definition.targetRange),
targetSelectionRange: definition.targetSelectionRange,
uri: Uri.parse(definition.targetUri),
}));
},
};
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 ------
export function createHoverProvider(getWorker: WorkerAccessor): languages.HoverProvider {
return {
async provideHover(model, position) {
const resource = model.uri;
export class HoverAdapter implements languages.HoverProvider {
constructor(private _worker: WorkerAccessor) {}
const worker = await getWorker(resource);
const info = await worker.doHover(String(resource), fromPosition(position));
if (!info) {
return;
}
return {
range: toRange(info.range),
contents: [{ value: (info.contents as ls.MarkupContent).value }],
};
},
};
provideHover(model: editor.IReadOnlyModel, position: Position): PromiseLike<languages.Hover> {
const resource = model.uri;
return this._worker(resource)
.then((worker) => worker.doHover(String(resource), fromPosition(position)))
.then((info) => {
if (!info) {
return;
}
return {
range: toRange(info.range),
contents: toMarkedStringArray(info.contents),
} as languages.Hover;
});
}
}
// --- document symbols ------
@ -353,31 +398,31 @@ function toSymbolKind(kind: ls.SymbolKind): languages.SymbolKind {
function toDocumentSymbol(item: ls.DocumentSymbol): languages.DocumentSymbol {
return {
detail: item.detail || '',
detail: '',
range: toRange(item.range),
name: item.name,
kind: toSymbolKind(item.kind),
selectionRange: toRange(item.selectionRange),
children: item.children.map(toDocumentSymbol),
children: item.children.map((child) => toDocumentSymbol(child)),
tags: [],
};
}
export function createDocumentSymbolProvider(
getWorker: WorkerAccessor,
): languages.DocumentSymbolProvider {
return {
async provideDocumentSymbols(model) {
const resource = model.uri;
export class DocumentSymbolAdapter implements languages.DocumentSymbolProvider {
constructor(private _worker: WorkerAccessor) {}
const worker = await getWorker(resource);
const items = await worker.findDocumentSymbols(String(resource));
if (!items) {
return;
}
return items.map(toDocumentSymbol);
},
};
provideDocumentSymbols(model: editor.IReadOnlyModel): PromiseLike<languages.DocumentSymbol[]> {
const resource = model.uri;
return this._worker(resource)
.then((worker) => worker.findDocumentSymbols(String(resource)))
.then((items) => {
if (!items) {
return;
}
return items.map((item) => toDocumentSymbol(item));
});
}
}
function fromFormattingOptions(
@ -390,42 +435,46 @@ function fromFormattingOptions(
};
}
export function createDocumentFormattingEditProvider(
getWorker: WorkerAccessor,
): languages.DocumentFormattingEditProvider {
return {
async provideDocumentFormattingEdits(model, options) {
const resource = model.uri;
export class DocumentFormattingEditProvider implements languages.DocumentFormattingEditProvider {
constructor(private _worker: WorkerAccessor) {}
const worker = await getWorker(resource);
const edits = await worker.format(String(resource), fromFormattingOptions(options));
if (!edits || edits.length === 0) {
return;
}
return edits.map(toTextEdit);
},
};
provideDocumentFormattingEdits(
model: editor.IReadOnlyModel,
options: languages.FormattingOptions,
): PromiseLike<editor.ISingleEditOperation[]> {
const resource = model.uri;
return this._worker(resource).then((worker) =>
worker.format(String(resource), null, fromFormattingOptions(options)).then((edits) => {
if (!edits || edits.length === 0) {
return;
}
return edits.map(toTextEdit);
}),
);
}
}
function toLink(link: ls.DocumentLink): languages.ILink {
return {
range: toRange(link.range),
tooltip: link.tooltip,
url: link.target,
};
}
export function createLinkProvider(getWorker: WorkerAccessor): languages.LinkProvider {
return {
async provideLinks(model) {
const resource = model.uri;
const worker = await getWorker(resource);
const links = await worker.findLinks(String(resource));
return {
links: links.map(toLink),
};
},
};
export class DocumentRangeFormattingEditProvider
implements languages.DocumentRangeFormattingEditProvider {
constructor(private _worker: WorkerAccessor) {}
provideDocumentRangeFormattingEdits(
model: editor.IReadOnlyModel,
range: Range,
options: languages.FormattingOptions,
): PromiseLike<editor.ISingleEditOperation[]> {
const resource = model.uri;
return this._worker(resource).then((worker) =>
worker
.format(String(resource), fromRange(range), fromFormattingOptions(options))
.then((edits) => {
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';
// 2min
const STOP_WHEN_IDLE_FOR = 2 * 60 * 1000;
export function createWorkerManager(
defaults: languages.yaml.LanguageServiceDefaults,
): WorkerAccessor {
let worker: editor.MonacoWebWorker<YAMLWorker>;
let client: Promise<YAMLWorker>;
let lastUsedTime = 0;
export class WorkerManager {
private _defaults: LanguageServiceDefaultsImpl;
private _idleCheckInterval: number;
private _lastUsedTime: number;
private _configChangeListener: IDisposable;
const stopWorker = (): void => {
if (worker) {
worker.dispose();
worker = undefined;
private _worker: editor.MonacoWebWorker<YAMLWorker>;
private _client: Promise<YAMLWorker>;
constructor(defaults: LanguageServiceDefaultsImpl) {
this._defaults = defaults;
this._worker = null;
this._idleCheckInterval = setInterval(() => this._checkIfIdle(), 30 * 1000);
this._lastUsedTime = 0;
this._configChangeListener = this._defaults.onDidChange(() => this._stopWorker());
}
dispose(): void {
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;
}
client = undefined;
};
this._client = null;
}
setInterval(() => {
if (!worker) {
private _checkIfIdle(): void {
if (!this._worker) {
return;
}
const timePassedSinceLastUsed = Date.now() - lastUsedTime;
const timePassedSinceLastUsed = Date.now() - this._lastUsedTime;
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)
defaults.onDidChange(() => stopWorker());
private _getClient(): Promise<YAMLWorker> {
this._lastUsedTime = Date.now();
const getClient = (): Promise<YAMLWorker> => {
lastUsedTime = Date.now();
if (!client) {
worker = editor.createWebWorker<YAMLWorker>({
if (!this._client) {
this._worker = editor.createWebWorker<YAMLWorker>({
// Module that exports the create() method and returns a `YAMLWorker` instance
moduleId: 'vs/language/yaml/yamlWorker',
label: defaults.languageId,
label: this._defaults.languageId,
// Passed in to the create() method
createData: {
languageSettings: defaults.diagnosticsOptions,
enableSchemaRequest: defaults.diagnosticsOptions.enableSchemaRequest,
isKubernetes: defaults.diagnosticsOptions.isKubernetes,
customTags: defaults.diagnosticsOptions.customTags,
languageSettings: this._defaults.diagnosticsOptions,
languageId: this._defaults.languageId,
enableSchemaRequest: this._defaults.diagnosticsOptions.enableSchemaRequest,
prefix: this._defaults.diagnosticsOptions.prefix,
isKubernetes: this._defaults.diagnosticsOptions.isKubernetes,
},
});
client = worker.getProxy();
this._client = this._worker.getProxy();
}
return client;
};
return async (...resources) => {
const client = await getClient();
await worker.withSyncedResources(resources);
return client;
};
return this._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 = () => {
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 {
createCompletionItemProvider,
createDefinitionProvider,
createDiagnosticsAdapter,
createDocumentFormattingEditProvider,
createDocumentSymbolProvider,
createHoverProvider,
createLinkProvider,
} from './languageFeatures';
import { createWorkerManager } from './workerManager';
import * as languageFeatures from './languageFeatures';
import { LanguageServiceDefaultsImpl } from './monaco.contribution';
import { WorkerManager } from './workerManager';
import { YAMLWorker } from './yamlWorker';
const richEditConfiguration: languages.LanguageConfiguration = {
comments: {
@ -44,18 +37,44 @@ const richEditConfiguration: languages.LanguageConfiguration = {
],
};
export function setupMode(defaults: languages.yaml.LanguageServiceDefaults): void {
const worker = createWorkerManager(defaults);
export function setupMode(defaults: LanguageServiceDefaultsImpl): void {
const disposables: IDisposable[] = [];
languages.registerCompletionItemProvider(languageId, createCompletionItemProvider(worker));
languages.registerHoverProvider(languageId, createHoverProvider(worker));
languages.registerDefinitionProvider(languageId, createDefinitionProvider(worker));
languages.registerDocumentSymbolProvider(languageId, createDocumentSymbolProvider(worker));
languages.registerDocumentFormattingEditProvider(
languageId,
createDocumentFormattingEditProvider(worker),
const client = new WorkerManager(defaults);
disposables.push(client);
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(
languageId,
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);
languages.setLanguageConfiguration(languageId, richEditConfiguration);
// Color adapter should be necessary most of the time:
// 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 { Promisable } from 'type-fest';
import { TextDocument } from 'vscode-languageserver-textdocument';
import { worker } from 'monaco-editor/esm/vs/editor/editor.api';
import * as ls from 'vscode-languageserver-types';
import {
CustomFormatterOptions,
getLanguageService,
LanguageSettings,
} from 'yaml-language-server/lib/esm/languageservice/yamlLanguageService.js';
import * as yamlService from 'yaml-language-server';
import { languageId } from './constants';
let defaultSchemaRequestService: (url: string) => PromiseLike<string>;
async function schemaRequestService(uri: string): Promise<string> {
const response = await fetch(uri);
if (response.ok) {
return response.text();
if (typeof fetch !== 'undefined') {
defaultSchemaRequestService = (url) => fetch(url).then((response) => response.text());
}
export class YAMLWorker {
private _ctx: worker.IWorkerContext;
private _languageService: yamlService.LanguageService;
private _languageSettings: yamlService.LanguageSettings;
private _languageId: string;
private _isKubernetes: boolean;
constructor(ctx: worker.IWorkerContext, createData: ICreateData) {
const prefix = createData.prefix || '';
const service = (url: string): PromiseLike<string> =>
defaultSchemaRequestService(`${prefix}${url}`);
this._ctx = ctx;
this._languageSettings = createData.languageSettings;
this._languageId = createData.languageId;
this._languageService = yamlService.getLanguageService(
createData.enableSchemaRequest && service,
null,
[],
);
this._isKubernetes = createData.isKubernetes || false;
this._languageService.configure({
...this._languageSettings,
hover: true,
isKubernetes: this._isKubernetes,
});
}
throw new Error(`Schema request failed for ${uri}`);
}
export interface YAMLWorker {
doValidation: (uri: string) => Promisable<ls.Diagnostic[]>;
doValidation(uri: string): PromiseLike<ls.Diagnostic[]> {
const document = this._getTextDocument(uri);
if (document) {
return this._languageService.doValidation(document, this._isKubernetes);
}
return Promise.resolve([]);
}
doComplete: (uri: string, position: ls.Position) => Promisable<ls.CompletionList>;
doComplete(uri: string, position: ls.Position): PromiseLike<ls.CompletionList> {
const document = this._getTextDocument(uri);
return this._languageService.doComplete(document, position, this._isKubernetes);
}
doDefinition: (uri: string, position: ls.Position) => Promisable<ls.LocationLink[]>;
doResolve(item: ls.CompletionItem): PromiseLike<ls.CompletionItem> {
return this._languageService.doResolve(item);
}
doHover: (uri: string, position: ls.Position) => Promisable<ls.Hover>;
doHover(uri: string, position: ls.Position): PromiseLike<ls.Hover> {
const document = this._getTextDocument(uri);
return this._languageService.doHover(document, position);
}
format: (uri: string, options: CustomFormatterOptions) => Promisable<ls.TextEdit[]>;
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) => Promisable<boolean>;
resetSchema(uri: string): PromiseLike<boolean> {
return Promise.resolve(this._languageService.resetSchema(uri));
}
findDocumentSymbols: (uri: string) => Promisable<ls.DocumentSymbol[]>;
findDocumentSymbols(uri: string): PromiseLike<ls.DocumentSymbol[]> {
const document = this._getTextDocument(uri);
const symbols = this._languageService.findDocumentSymbols2(document);
return Promise.resolve(symbols);
}
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,
);
languageService.configure(languageSettings);
const getTextDocument = (uri: string): TextDocument => {
const models = ctx.getMirrorModels();
private _getTextDocument(uri: string): ls.TextDocument {
const models = this._ctx.getMirrorModels();
for (const model of models) {
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 {
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 {
languageSettings: LanguageSettings;
languageId: string;
languageSettings: yamlService.LanguageSettings;
enableSchemaRequest: boolean;
prefix?: string;
isKubernetes?: boolean;
}
export function create(ctx: worker.IWorkerContext, createData: ICreateData): YAMLWorker {
return new YAMLWorker(ctx, createData);
}

View file

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