├── .nvmrc ├── src ├── constants.ts ├── fillers │ ├── lodash.ts │ ├── ajv.ts │ ├── schemaSelectionHandlers.ts │ └── vscode-nls.ts ├── index.ts ├── yamlMode.ts ├── languageFeatures.ts └── yaml.worker.ts ├── .npmrc ├── .remarkrc.yaml ├── .prettierignore ├── .prettierrc.yaml ├── .gitignore ├── .eslintrc.yaml ├── examples ├── monaco-editor-webpack-plugin │ ├── src │ │ ├── index.ejs │ │ └── index.js │ ├── package.json │ ├── webpack.config.js │ └── README.md ├── vite-example │ ├── index.html │ ├── package.json │ ├── README.md │ └── index.js └── demo │ ├── package.json │ ├── src │ ├── types.d.ts │ ├── icon.svg │ ├── schema.json │ ├── index.ejs │ ├── index.css │ └── index.ts │ ├── README.md │ └── webpack.config.js ├── netlify.toml ├── tsconfig.json ├── .editorconfig ├── playwright.config.ts ├── test ├── index.html ├── build.js ├── marker-data.test.ts ├── index.ts └── serve.js ├── CONTRIBUTING.md ├── LICENSE.md ├── package.json ├── .github └── workflows │ └── ci.yaml ├── index.d.ts └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const languageId = 'yaml' 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | install-links = false 2 | lockfile-version = 3 3 | -------------------------------------------------------------------------------- /.remarkrc.yaml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - remark-preset-remcohaszing 3 | -------------------------------------------------------------------------------- /src/fillers/lodash.ts: -------------------------------------------------------------------------------- 1 | export const cloneDeep = structuredClone 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | /index.js 3 | /yaml.worker.js 4 | test/out/ 5 | playwright-report/ 6 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | proseWrap: always 2 | semi: false 3 | singleQuote: true 4 | trailingComma: none 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | test/out/ 4 | playwright-report/ 5 | /index.js 6 | /yaml.worker.js 7 | *.log 8 | *.map 9 | *.tgz 10 | -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | extends: 2 | - remcohaszing 3 | - remcohaszing/typechecking 4 | rules: 5 | import/no-extraneous-dependencies: off 6 | 7 | jsdoc/require-jsdoc: off 8 | 9 | n/no-extraneous-import: off 10 | -------------------------------------------------------------------------------- /src/fillers/ajv.ts: -------------------------------------------------------------------------------- 1 | import { type ValidateFunction } from 'ajv' 2 | 3 | export default class AJVStub { 4 | // eslint-disable-next-line class-methods-use-this 5 | compile(): ValidateFunction { 6 | return () => true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/fillers/schemaSelectionHandlers.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a stub for `monaco-yaml/lib/esm/schemaSelectionHandlers.js`. 3 | */ 4 | // eslint-disable-next-line @typescript-eslint/no-empty-function 5 | export function JSONSchemaSelection(): void {} 6 | -------------------------------------------------------------------------------- /examples/monaco-editor-webpack-plugin/src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Monaco YAML 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = 'examples/demo/dist/' 3 | command = 'npm ci && npm run prepack && npm --workspace demo run build' 4 | 5 | [[headers]] 6 | for = '/*' 7 | [headers.values] 8 | Content-Security-Policy = "default-src 'self'; connect-src https:; img-src 'self' data:; style-src 'self' 'unsafe-inline'" 9 | -------------------------------------------------------------------------------- /examples/vite-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Monaco YAML 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "forceConsistentCasingInFileNames": true, 4 | "module": "nodenext", 5 | "noEmit": true, 6 | "resolveJsonModule": true, 7 | "skipLibCheck": true, 8 | "sourceMap": true, 9 | "strict": true, 10 | "target": "es2017", 11 | "types": [] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 100 11 | trim_trailing_whitespace = true 12 | 13 | [COMMIT_EDITMSG] 14 | max_line_length = 72 15 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { type PlaywrightTestConfig } from '@playwright/test' 2 | 3 | const config: PlaywrightTestConfig = { 4 | reporter: 'html', 5 | timeout: 120_000, 6 | webServer: { 7 | command: 'node test/serve.js', 8 | port: 3000, 9 | reuseExistingServer: !process.env.CI 10 | } 11 | } 12 | export default config 13 | -------------------------------------------------------------------------------- /examples/vite-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "start": "vite", 8 | "build": "vite build" 9 | }, 10 | "dependencies": { 11 | "monaco-editor": "^0.38.0", 12 | "monaco-yaml": "file:../..", 13 | "vite": "^4.0.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/build.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | 3 | import { build } from 'esbuild' 4 | 5 | await build({ 6 | entryPoints: { 7 | 'editor.worker': 'monaco-editor/esm/vs/editor/editor.worker.js', 8 | 'yaml.worker': fileURLToPath(new URL('../yaml.worker.js', import.meta.url)), 9 | index: fileURLToPath(new URL('index.ts', import.meta.url)) 10 | }, 11 | bundle: true, 12 | format: 'iife', 13 | outdir: fileURLToPath(new URL('out/', import.meta.url)), 14 | loader: { 15 | '.ttf': 'file' 16 | } 17 | }) 18 | -------------------------------------------------------------------------------- /examples/monaco-editor-webpack-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monaco-editor-webpack-plugin-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "start": "webpack serve --open --mode development", 8 | "build": "webpack --mode production" 9 | }, 10 | "dependencies": { 11 | "css-loader": "^6.0.0", 12 | "html-webpack-plugin": "^5.0.0", 13 | "monaco-editor": "^0.38.0", 14 | "monaco-editor-webpack-plugin": "^7.0.0", 15 | "monaco-yaml": "file:../..", 16 | "style-loader": "^3.0.0", 17 | "webpack": "^5.0.0", 18 | "webpack-cli": "^5.0.0", 19 | "webpack-dev-server": "^4.0.0" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/fillers/vscode-nls.ts: -------------------------------------------------------------------------------- 1 | interface LocalizeInfo { 2 | key: string 3 | comment: string[] 4 | } 5 | type LocalizeFunc = (info: LocalizeInfo | string, message: string, ...args: string[]) => string 6 | type LoadFunc = (file?: string) => LocalizeFunc 7 | 8 | function format(message: string, args: string[]): string { 9 | return args.length === 0 10 | ? message 11 | : message.replace(/{(\d+)}/g, (match, [index]: number[]) => 12 | index in args ? args[index] : match 13 | ) 14 | } 15 | 16 | const localize: LocalizeFunc = (key, message, ...args) => format(message, args) 17 | 18 | export function loadMessageBundle(): LocalizeFunc { 19 | return localize 20 | } 21 | 22 | export function config(): LoadFunc { 23 | return loadMessageBundle 24 | } 25 | -------------------------------------------------------------------------------- /examples/demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "start": "webpack serve --open --mode development", 8 | "build": "webpack --mode production" 9 | }, 10 | "dependencies": { 11 | "@fortawesome/fontawesome-free": "^6.0.0", 12 | "@schemastore/schema-catalog": "^0.0.6", 13 | "css-loader": "^6.0.0", 14 | "css-minimizer-webpack-plugin": "^5.0.0", 15 | "html-webpack-plugin": "^5.0.0", 16 | "mini-css-extract-plugin": "^2.0.0", 17 | "monaco-editor": "^0.38.0", 18 | "monaco-yaml": "file:../..", 19 | "ts-loader": "^9.0.0", 20 | "webpack": "^5.0.0", 21 | "webpack-cli": "^5.0.0", 22 | "webpack-dev-server": "^4.0.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/demo/src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'monaco-editor/esm/vs/editor/common/services/languageFeatures.js' { 2 | export const ILanguageFeaturesService: { documentSymbolProvider: unknown } 3 | } 4 | 5 | declare module 'monaco-editor/esm/vs/editor/contrib/documentSymbols/browser/outlineModel.js' { 6 | import { type editor, type languages } from 'monaco-editor' 7 | 8 | export abstract class OutlineModel { 9 | static create(registry: unknown, model: editor.ITextModel): Promise 10 | 11 | asListOfDocumentSymbols(): languages.DocumentSymbol[] 12 | } 13 | } 14 | 15 | declare module 'monaco-editor/esm/vs/editor/standalone/browser/standaloneServices.js' { 16 | export const StandaloneServices: { 17 | get: (id: unknown) => { documentSymbolProvider: unknown } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/vite-example/README.md: -------------------------------------------------------------------------------- 1 | # Vite Example 2 | 3 | This minimal example shows how `monaco-yaml` can be used with [Vite](https://vitejs.dev). 4 | 5 | ## Table of Contents 6 | 7 | - [Prerequisites](#prerequisites) 8 | - [Setup](#setup) 9 | - [Running](#running) 10 | 11 | ## Prerequisites 12 | 13 | - [NodeJS](https://nodejs.org) 16 or higher 14 | - [npm](https://github.com/npm/cli) 8.1.2 or higher 15 | 16 | ## Setup 17 | 18 | To run the project locally, clone the repository and set it up: 19 | 20 | ```sh 21 | git clone https://github.com/remcohaszing/monaco-yaml 22 | cd monaco-yaml 23 | npm ci 24 | npm run prepack 25 | ``` 26 | 27 | ## Running 28 | 29 | To start it, simply run: 30 | 31 | ```sh 32 | npm --workspace vite-example start 33 | ``` 34 | 35 | The demo will be available on . 36 | -------------------------------------------------------------------------------- /test/marker-data.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, type Page, test } from '@playwright/test' 2 | 3 | let page: Page 4 | 5 | test.beforeAll(async ({ browser }) => { 6 | page = await browser.newPage() 7 | await page.goto('') 8 | }) 9 | 10 | test.afterAll(async () => { 11 | await page.close() 12 | }) 13 | 14 | test.describe('Marker data', () => { 15 | test('should display warning when type is incorrect', async () => { 16 | await page.locator('.view-lines').hover({ position: { x: 30, y: 10 } }) 17 | const warn = page.locator('.marker.hover-contents > span:nth-child(1)') 18 | await expect(warn).toHaveText('Incorrect type. Expected "number".') 19 | }) 20 | 21 | test('should underline warning when type is incorrect', async () => { 22 | await expect(page.locator('.cdr.squiggly-warning')).toHaveCount(2) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /examples/monaco-editor-webpack-plugin/webpack.config.js: -------------------------------------------------------------------------------- 1 | import HtmlWebPackPlugin from 'html-webpack-plugin' 2 | import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin' 3 | 4 | export default { 5 | module: { 6 | rules: [ 7 | { 8 | test: /\.css$/, 9 | use: ['style-loader', 'css-loader'] 10 | }, 11 | { 12 | test: /\.ttf$/, 13 | type: 'asset' 14 | } 15 | ] 16 | }, 17 | plugins: [ 18 | new HtmlWebPackPlugin(), 19 | new MonacoWebpackPlugin({ 20 | languages: ['yaml'], 21 | customLanguages: [ 22 | { 23 | label: 'yaml', 24 | entry: 'monaco-yaml', 25 | worker: { 26 | id: 'monaco-yaml/yamlWorker', 27 | entry: 'monaco-yaml/yaml.worker' 28 | } 29 | } 30 | ] 31 | }) 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/demo/README.md: -------------------------------------------------------------------------------- 1 | # Demo 2 | 3 | This demo is deployed to [monaco-yaml.js.org](https://monaco-yaml.js.org). It shows how 4 | `monaco-editor` and `monaco-yaml` can be used with 5 | [Webpack 5](https://webpack.js.org/concepts/entry-points). 6 | 7 | ## Table of Contents 8 | 9 | - [Prerequisites](#prerequisites) 10 | - [Setup](#setup) 11 | - [Running](#running) 12 | 13 | ## Prerequisites 14 | 15 | - [NodeJS](https://nodejs.org) 16 or higher 16 | - [npm](https://github.com/npm/cli) 8.1.2 or higher 17 | 18 | ## Setup 19 | 20 | To run the project locally, clone the repository and set it up: 21 | 22 | ```sh 23 | git clone https://github.com/remcohaszing/monaco-yaml 24 | cd monaco-yaml 25 | npm ci 26 | npm run prepack 27 | ``` 28 | 29 | ## Running 30 | 31 | To start it, simply run: 32 | 33 | ```sh 34 | npm --workspace demo start 35 | ``` 36 | 37 | The demo will open in your browser. 38 | -------------------------------------------------------------------------------- /test/index.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor' 2 | import { type SchemasSettings, setDiagnosticsOptions } from 'monaco-yaml' 3 | 4 | self.MonacoEnvironment = { 5 | getWorkerUrl(workerId, label) { 6 | if (label === 'yaml') { 7 | return 'out/yaml.worker.js' 8 | } 9 | return 'out/editor.worker.js' 10 | } 11 | } 12 | 13 | const schema: SchemasSettings = { 14 | fileMatch: ['*'], 15 | uri: 'http://schemas/my-schema.json', 16 | schema: { 17 | type: 'object', 18 | properties: { 19 | p1: { 20 | description: 'number property', 21 | type: 'number' 22 | }, 23 | p2: { 24 | type: 'boolean' 25 | } 26 | } 27 | } 28 | } 29 | 30 | setDiagnosticsOptions({ 31 | schemas: [schema] 32 | }) 33 | const value = 'p1: \np2: \n' 34 | 35 | monaco.editor.create(document.querySelector('#editor')!, { 36 | language: 'yaml', 37 | tabSize: 2, 38 | value 39 | }) 40 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Table of Contents 4 | 5 | - [Prerequisites](#prerequisites) 6 | - [Getting started](#getting-started) 7 | - [Building](#building) 8 | - [Running](#running) 9 | 10 | ## Prerequisites 11 | 12 | The following are required to start working on this project: 13 | 14 | - [Git](https://git-scm.com) 15 | - [NodeJS](https://nodejs.org) 16 or higher 16 | - [npm](https://github.com/npm/cli) 8.1.2 or higher 17 | 18 | ## Getting started 19 | 20 | To get started with contributing, clone the repository and install its dependencies. 21 | 22 | ```sh 23 | git clone https://github.com/remcohaszing/monaco-yaml 24 | cd monaco-yaml 25 | npm ci 26 | ``` 27 | 28 | ## Building 29 | 30 | To build the repository, run: 31 | 32 | ```sh 33 | npm run prepack 34 | ``` 35 | 36 | ## Running 37 | 38 | To test it, run one of the 39 | [examples](https://github.com/remcohaszing/monaco-yaml/tree/main/examples). 40 | 41 | ```sh 42 | npm --workspace demo start 43 | ``` 44 | -------------------------------------------------------------------------------- /examples/demo/webpack.config.js: -------------------------------------------------------------------------------- 1 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin' 2 | import HtmlWebPackPlugin from 'html-webpack-plugin' 3 | import MiniCssExtractPlugin from 'mini-css-extract-plugin' 4 | 5 | export default { 6 | output: { 7 | filename: '[contenthash].js' 8 | }, 9 | devtool: 'source-map', 10 | resolve: { 11 | extensions: ['.mjs', '.js', '.ts'] 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.css$/, 17 | use: [MiniCssExtractPlugin.loader, 'css-loader'] 18 | }, 19 | { 20 | // Monaco editor uses .ttf icons. 21 | test: /\.(svg|ttf)$/, 22 | type: 'asset/resource' 23 | }, 24 | { 25 | test: /\.ts$/, 26 | loader: 'ts-loader', 27 | options: { transpileOnly: true } 28 | } 29 | ] 30 | }, 31 | optimization: { 32 | minimizer: ['...', new CssMinimizerPlugin()] 33 | }, 34 | plugins: [new HtmlWebPackPlugin(), new MiniCssExtractPlugin({ filename: '[contenthash].css' })] 35 | } 36 | -------------------------------------------------------------------------------- /test/serve.js: -------------------------------------------------------------------------------- 1 | import { readFile } from 'node:fs/promises' 2 | import http from 'node:http' 3 | import { extname } from 'node:path' 4 | 5 | import './build.js' 6 | 7 | const contentTypes = { 8 | '.html': 'text/html', 9 | '.js': 'text/javascript', 10 | '.css': 'text/css', 11 | '.ttf': 'font/ttf' 12 | } 13 | 14 | http 15 | .createServer(async (req, res) => { 16 | const file = req.url === '/' ? 'index.html' : req.url.slice(1) 17 | const url = new URL(file, import.meta.url) 18 | const ext = extname(url.pathname) 19 | try { 20 | const content = await readFile(url) 21 | res.setHeader('Content-type', contentTypes[ext] ?? 'text/plain') 22 | res.end(content) 23 | } catch (err) { 24 | if (err.code === 'ENOENT') { 25 | res.writeHead(404, 'Not Found') 26 | } else { 27 | res.writeHead(500, 'Internal server error') 28 | } 29 | res.end(String(err)) 30 | } 31 | }) 32 | // eslint-disable-next-line no-console 33 | .listen(3000, () => console.log('listening...')) 34 | -------------------------------------------------------------------------------- /examples/demo/src/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/demo/src/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { 4 | "property": { 5 | "description": "I have a description" 6 | }, 7 | "titledProperty": { 8 | "title": "I have a title", 9 | "description": "I also have a description" 10 | }, 11 | "markdown": { 12 | "markdownDescription": "Even **markdown** _descriptions_ `are` ~~not~~ supported!" 13 | }, 14 | "enum": { 15 | "description": "Pick your starter", 16 | "enum": ["Bulbasaur", "Squirtle", "Charmander", "Pikachu"] 17 | }, 18 | "number": { 19 | "description": "Numbers work!", 20 | "minimum": 42, 21 | "maximum": 1337 22 | }, 23 | "boolean": { 24 | "description": "Are boolean supported?", 25 | "type": "boolean" 26 | }, 27 | "string": { 28 | "type": "string" 29 | }, 30 | "reference": { 31 | "description": "JSON schemas can be referenced, even recursively", 32 | "$ref": "#" 33 | }, 34 | "array": { 35 | "description": "It also works in arrays", 36 | "items": { 37 | "$ref": "#" 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Microsoft Corporation 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 6 | associated documentation files (the “Software”), to deal in the Software without restriction, 7 | including without limitation the rights to use, copy, modify, merge, publish, distribute, 8 | sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial 12 | portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 15 | NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 16 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES 17 | OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /examples/monaco-editor-webpack-plugin/README.md: -------------------------------------------------------------------------------- 1 | # Monaco Editor Webpack Loader Plugin Example 2 | 3 | This demo demonstrates how bundle `monaco-editor` and `monaco-yaml` with 4 | [monaco-editor-webpack-plugin](https://github.com/microsoft/monaco-editor/tree/main/webpack-plugin). 5 | The build output is 6 | [esm library](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules). Example is 7 | based on 8 | [link](https://github.com/microsoft/monaco-editor/tree/main/samples/browser-esm-webpack-monaco-plugin). 9 | To start it, simply run: 10 | 11 | ## Table of Contents 12 | 13 | - [Prerequisites](#prerequisites) 14 | - [Setup](#setup) 15 | - [Running](#running) 16 | 17 | ## Prerequisites 18 | 19 | - [NodeJS](https://nodejs.org) 16 or higher 20 | - [npm](https://github.com/npm/cli) 8.1.2 or higher 21 | 22 | ## Setup 23 | 24 | To run the project locally, clone the repository and set it up: 25 | 26 | ```sh 27 | git clone https://github.com/remcohaszing/monaco-yaml 28 | cd monaco-yaml 29 | npm ci 30 | npm run prepack 31 | ``` 32 | 33 | ## Running 34 | 35 | To start it, simply run: 36 | 37 | ```sh 38 | npm --workspace monaco-editor-webpack-plugin-example start 39 | ``` 40 | 41 | The demo will open in your browser. 42 | -------------------------------------------------------------------------------- /examples/monaco-editor-webpack-plugin/src/index.js: -------------------------------------------------------------------------------- 1 | import { editor, Uri } from 'monaco-editor' 2 | import { setDiagnosticsOptions } from 'monaco-yaml' 3 | 4 | // The uri is used for the schema file match. 5 | const modelUri = Uri.parse('a://b/foo.yaml') 6 | 7 | setDiagnosticsOptions({ 8 | enableSchemaRequest: true, 9 | hover: true, 10 | completion: true, 11 | validate: true, 12 | format: true, 13 | schemas: [ 14 | { 15 | // Id of the first schema 16 | uri: 'http://myserver/foo-schema.json', 17 | // Associate with our model 18 | fileMatch: [String(modelUri)], 19 | schema: { 20 | type: 'object', 21 | properties: { 22 | p1: { 23 | enum: ['v1', 'v2'] 24 | }, 25 | p2: { 26 | // Reference the second schema 27 | $ref: 'http://myserver/bar-schema.json' 28 | } 29 | } 30 | } 31 | }, 32 | { 33 | // Id of the first schema 34 | uri: 'http://myserver/bar-schema.json', 35 | schema: { 36 | type: 'object', 37 | properties: { 38 | q1: { 39 | enum: ['x1', 'x2'] 40 | } 41 | } 42 | } 43 | } 44 | ] 45 | }) 46 | 47 | const value = 'p1: \np2: \n' 48 | 49 | editor.create(document.getElementById('editor'), { 50 | automaticLayout: true, 51 | model: editor.createModel(value, 'yaml', modelUri) 52 | }) 53 | -------------------------------------------------------------------------------- /examples/demo/src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Monaco YAML 7 | 8 | 9 | 10 | 11 | 38 |
39 | 42 | 43 |
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/vite-example/index.js: -------------------------------------------------------------------------------- 1 | import { editor, Uri } from 'monaco-editor' 2 | import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker' 3 | import { setDiagnosticsOptions } from 'monaco-yaml' 4 | import YamlWorker from 'monaco-yaml/yaml.worker?worker' 5 | 6 | window.MonacoEnvironment = { 7 | getWorker(moduleId, label) { 8 | switch (label) { 9 | case 'editorWorkerService': 10 | return new EditorWorker() 11 | case 'yaml': 12 | return new YamlWorker() 13 | default: 14 | throw new Error(`Unknown label ${label}`) 15 | } 16 | } 17 | } 18 | 19 | // The uri is used for the schema file match. 20 | const modelUri = Uri.parse('a://b/foo.yaml') 21 | 22 | setDiagnosticsOptions({ 23 | enableSchemaRequest: true, 24 | hover: true, 25 | completion: true, 26 | validate: true, 27 | format: true, 28 | schemas: [ 29 | { 30 | // Id of the first schema 31 | uri: 'http://myserver/foo-schema.json', 32 | // Associate with our model 33 | fileMatch: [String(modelUri)], 34 | schema: { 35 | type: 'object', 36 | properties: { 37 | p1: { 38 | enum: ['v1', 'v2'] 39 | }, 40 | p2: { 41 | // Reference the second schema 42 | $ref: 'http://myserver/bar-schema.json' 43 | } 44 | } 45 | } 46 | }, 47 | { 48 | // Id of the first schema 49 | uri: 'http://myserver/bar-schema.json', 50 | schema: { 51 | type: 'object', 52 | properties: { 53 | q1: { 54 | enum: ['x1', 'x2'] 55 | } 56 | } 57 | } 58 | } 59 | ] 60 | }) 61 | 62 | const value = 'p1: \np2: \n' 63 | 64 | editor.create(document.getElementById('editor'), { 65 | automaticLayout: true, 66 | model: editor.createModel(value, 'yaml', modelUri) 67 | }) 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monaco-yaml", 3 | "version": "4.0.4", 4 | "description": "YAML plugin for the Monaco Editor", 5 | "homepage": "https://monaco-yaml.js.org", 6 | "scripts": { 7 | "prepack": "node build.js", 8 | "start": "npm --workspace demo start", 9 | "test": "playwright test" 10 | }, 11 | "type": "module", 12 | "main": "./index.js", 13 | "typings": "./index.d.ts", 14 | "files": [ 15 | "index.js", 16 | "index.js.map", 17 | "index.d.ts", 18 | "yaml.worker.js", 19 | "yaml.worker.js.map" 20 | ], 21 | "workspaces": [ 22 | "examples/*" 23 | ], 24 | "author": "Kevin Decker (http://incaseofstairs.com)", 25 | "maintainers": [ 26 | "Remco Haszing (https://github.com/remcohaszing)" 27 | ], 28 | "license": "MIT", 29 | "repository": "remcohaszing/monaco-yaml", 30 | "bugs": "https://github.com/remcohaszing/monaco-yaml/issues", 31 | "funding": "https://github.com/sponsors/remcohaszing", 32 | "keywords": [ 33 | "editor", 34 | "frontend", 35 | "front-end", 36 | "monaco", 37 | "monaco-editor", 38 | "yaml" 39 | ], 40 | "dependencies": { 41 | "@types/json-schema": "^7.0.0", 42 | "jsonc-parser": "^3.0.0", 43 | "monaco-languageserver-types": "^0.2.0", 44 | "monaco-marker-data-provider": "^1.0.0", 45 | "monaco-worker-manager": "^2.0.0", 46 | "path-browserify": "^1.0.0", 47 | "prettier": "^2.0.0", 48 | "vscode-languageserver-textdocument": "^1.0.0", 49 | "vscode-languageserver-types": "^3.0.0", 50 | "vscode-uri": "^3.0.0", 51 | "yaml": "^2.0.0" 52 | }, 53 | "peerDependencies": { 54 | "monaco-editor": ">=0.36" 55 | }, 56 | "devDependencies": { 57 | "@playwright/test": "^1.0.0", 58 | "esbuild": "^0.17.0", 59 | "eslint": "^8.0.0", 60 | "eslint-config-remcohaszing": "^9.0.0", 61 | "monaco-editor": "^0.38.0", 62 | "remark-cli": "^11.0.0", 63 | "remark-preset-remcohaszing": "^1.0.0", 64 | "typescript": "^5.0.0", 65 | "yaml-language-server": "^1.0.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' 2 | import { setMonaco } from 'monaco-languageserver-types' 3 | import { type DiagnosticsOptions, type LanguageServiceDefaults } from 'monaco-yaml' 4 | 5 | import { languageId } from './constants.js' 6 | import { setupMode } from './yamlMode.js' 7 | 8 | // --- YAML configuration and defaults --------- 9 | 10 | const diagnosticDefault: DiagnosticsOptions = { 11 | completion: true, 12 | customTags: [], 13 | enableSchemaRequest: false, 14 | format: true, 15 | isKubernetes: false, 16 | hover: true, 17 | schemas: [], 18 | validate: true, 19 | yamlVersion: '1.2' 20 | } 21 | 22 | setMonaco(monaco) 23 | 24 | export function createLanguageServiceDefaults( 25 | initialDiagnosticsOptions: DiagnosticsOptions 26 | ): LanguageServiceDefaults { 27 | const onDidChange = new monaco.Emitter() 28 | let diagnosticsOptions = initialDiagnosticsOptions 29 | 30 | const languageServiceDefaults: LanguageServiceDefaults = { 31 | get onDidChange() { 32 | return onDidChange.event 33 | }, 34 | 35 | get diagnosticsOptions() { 36 | return diagnosticsOptions 37 | }, 38 | 39 | setDiagnosticsOptions(options) { 40 | diagnosticsOptions = { ...diagnosticDefault, ...options } 41 | onDidChange.fire(languageServiceDefaults) 42 | } 43 | } 44 | 45 | return languageServiceDefaults 46 | } 47 | 48 | export const yamlDefaults = createLanguageServiceDefaults(diagnosticDefault) 49 | 50 | // --- Registration to monaco editor --- 51 | 52 | monaco.languages.register({ 53 | id: languageId, 54 | extensions: ['.yaml', '.yml'], 55 | aliases: ['YAML', 'yaml', 'YML', 'yml'], 56 | mimetypes: ['application/x-yaml'] 57 | }) 58 | 59 | monaco.languages.onLanguage('yaml', () => { 60 | setupMode(yamlDefaults) 61 | }) 62 | 63 | /** 64 | * Configure `monaco-yaml` diagnostics options. 65 | * 66 | * @param options The options to set. 67 | */ 68 | export function setDiagnosticsOptions(options: DiagnosticsOptions = {}): void { 69 | yamlDefaults.setDiagnosticsOptions(options) 70 | } 71 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [main] 7 | tags: ['*'] 8 | 9 | jobs: 10 | eslint: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-node@v3 15 | with: { node-version: 18 } 16 | - run: npm ci 17 | - run: npx eslint . 18 | 19 | examples: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v3 23 | - uses: actions/setup-node@v3 24 | with: { node-version: 18 } 25 | - run: npm ci 26 | - run: npm run prepack 27 | - run: npm run build --workspaces 28 | 29 | pack: 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions/setup-node@v3 34 | with: { node-version: 18 } 35 | - run: npm ci 36 | - run: npm pack 37 | - uses: actions/upload-artifact@v3 38 | with: 39 | name: package 40 | path: '*.tgz' 41 | 42 | prettier: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v3 46 | - uses: actions/setup-node@v3 47 | with: { node-version: 18 } 48 | - run: npm ci 49 | - run: npx prettier --check . 50 | 51 | remark: 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v3 55 | - uses: actions/setup-node@v3 56 | with: { node-version: 18 } 57 | - run: npm ci 58 | - run: npx remark --frail . 59 | 60 | tsc: 61 | runs-on: ubuntu-latest 62 | steps: 63 | - uses: actions/checkout@v3 64 | - uses: actions/setup-node@v3 65 | with: { node-version: 18 } 66 | - run: npm ci 67 | - run: npx tsc 68 | 69 | playwright: 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/checkout@v3 73 | - uses: actions/setup-node@v3 74 | with: { node-version: 18 } 75 | - run: npm ci 76 | - run: npx playwright install --with-deps 77 | - run: npm run prepack 78 | - run: npx playwright test 79 | - uses: actions/upload-artifact@v3 80 | if: always() 81 | with: 82 | name: playwright-report 83 | path: playwright-report/ 84 | retention-days: 30 85 | 86 | release: 87 | runs-on: ubuntu-latest 88 | needs: 89 | - eslint 90 | - examples 91 | - pack 92 | - playwright 93 | - prettier 94 | - remark 95 | - tsc 96 | if: startsWith(github.ref, 'refs/tags/') 97 | permissions: 98 | id-token: write 99 | steps: 100 | - uses: actions/setup-node@v3 101 | with: 102 | node-version: 18 103 | registry-url: https://registry.npmjs.org 104 | - uses: actions/download-artifact@v3 105 | with: 106 | name: package 107 | - run: npm publish *.tgz --provenance 108 | env: 109 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 110 | -------------------------------------------------------------------------------- /examples/demo/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background-color: hsl(0, 0%, 96%); 3 | --editor-background: hsl(60, 100%, 100%); 4 | --error-color: hsl(0, 85%, 62%); 5 | --foreground-color: hsl(0, 0%, 0%); 6 | --primary-color: hsl(189, 100%, 63%); 7 | --shadow-color: hsla(0, 0%, 27%, 0.239); 8 | --scrollbar-color: hsla(0, 0%, 47%, 0.4); 9 | --warning-color: hsl(49, 100%, 40%); 10 | } 11 | 12 | @media (prefers-color-scheme: dark) { 13 | :root { 14 | --background-color: hsl(0, 0%, 23%); 15 | --editor-background: hsl(0, 0%, 12%); 16 | --foreground-color: hsl(0, 0%, 100%); 17 | --shadow-color: hsl(0, 0%, 43%); 18 | } 19 | } 20 | 21 | body { 22 | background: var(--background-color); 23 | display: flex; 24 | flex-flow: column; 25 | font-family: sans-serif; 26 | height: 100vh; 27 | margin: 0; 28 | } 29 | 30 | h1 { 31 | margin: 0 auto 0 1rem; 32 | } 33 | 34 | nav { 35 | align-items: center; 36 | background: var(--primary-color); 37 | box-shadow: 0px 5px 5px var(--shadow-color); 38 | display: flex; 39 | flex: 0 0 auto; 40 | height: 3rem; 41 | justify-content: space-between; 42 | } 43 | 44 | .nav-icon { 45 | margin-right: 1rem; 46 | text-decoration: none; 47 | } 48 | 49 | .nav-icon > img { 50 | vertical-align: middle; 51 | } 52 | 53 | main { 54 | background: var(--editor-background); 55 | box-shadow: 0 0 10px var(--shadow-color); 56 | display: flex; 57 | flex: 1 1 auto; 58 | flex-flow: column; 59 | margin: 1.5rem; 60 | } 61 | 62 | #schema-selection { 63 | background-color: var(--editor-background); 64 | border: none; 65 | border-bottom: 1px solid var(--shadow-color); 66 | color: var(--foreground-color); 67 | width: 100%; 68 | } 69 | 70 | #breadcrumbs { 71 | border-bottom: 1px solid var(--shadow-color); 72 | color: var(--foreground-color); 73 | flex: 0 0 1rem; 74 | } 75 | 76 | .breadcrumb { 77 | cursor: pointer; 78 | } 79 | 80 | #breadcrumbs::before, 81 | .breadcrumb:not(:last-child)::after { 82 | content: '›'; 83 | margin: 0 0.2rem; 84 | } 85 | 86 | .breadcrumb.array::before { 87 | content: '[]'; 88 | } 89 | 90 | .breadcrumb.object::before { 91 | content: '{}'; 92 | } 93 | 94 | #editor { 95 | flex: 1 1 auto; 96 | } 97 | 98 | #problems { 99 | border-top: 1px solid var(--shadow-color); 100 | flex: 0 0 20vh; 101 | color: var(--foreground-color); 102 | overflow-y: scroll; 103 | } 104 | 105 | .problem { 106 | align-items: center; 107 | cursor: pointer; 108 | display: flex; 109 | padding: 0.25rem; 110 | } 111 | 112 | .problem:hover { 113 | background-color: var(--shadow-color); 114 | } 115 | 116 | .problem-text { 117 | margin-left: 0.5rem; 118 | } 119 | 120 | .problem .codicon-warning { 121 | color: var(--warning-color); 122 | } 123 | 124 | .problem .codicon-error { 125 | color: var(--error-color); 126 | } 127 | 128 | *::-webkit-scrollbar { 129 | box-shadow: 1px 0 0 0 var(--scrollbar-color) inset; 130 | width: 14px; 131 | } 132 | 133 | *::-webkit-scrollbar-thumb { 134 | background: var(--scrollbar-color); 135 | } 136 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { type JSONSchema4, type JSONSchema6, type JSONSchema7 } from 'json-schema' 2 | import { type IEvent } from 'monaco-editor' 3 | 4 | export interface SchemasSettings { 5 | /** 6 | * A `Uri` file match which will trigger the schema validation. This may be a glob or an exact 7 | * path. 8 | * 9 | * @example '.gitlab-ci.yml' 10 | * @example 'file://**\/.github/actions/*.yaml' 11 | */ 12 | fileMatch: string[] 13 | 14 | /** 15 | * The JSON schema which will be used for validation. If not specified, it will be downloaded from 16 | * `uri`. 17 | */ 18 | schema?: JSONSchema4 | JSONSchema6 | JSONSchema7 19 | 20 | /** 21 | * The source URI of the JSON schema. The JSON schema will be downloaded from here if no schema 22 | * was supplied. It will also be displayed as the source in hover tooltips. 23 | */ 24 | uri: string 25 | } 26 | 27 | export interface DiagnosticsOptions { 28 | /** 29 | * If set, enable schema based autocompletion. 30 | * 31 | * @default true 32 | */ 33 | readonly completion?: boolean 34 | 35 | /** 36 | * A list of custom tags. 37 | * 38 | * @default [] 39 | */ 40 | readonly customTags?: string[] 41 | 42 | /** 43 | * If set, the schema service would load schema content on-demand with 'fetch' if available 44 | * 45 | * @default false 46 | */ 47 | readonly enableSchemaRequest?: boolean 48 | 49 | /** 50 | * If true, formatting using Prettier is enabled. Setting this to `false` does **not** exclude 51 | * Prettier from the bundle. 52 | * 53 | * @default true 54 | */ 55 | readonly format?: boolean 56 | 57 | /** 58 | * If set, enable hover typs based the JSON schema. 59 | * 60 | * @default true 61 | */ 62 | readonly hover?: boolean 63 | 64 | /** 65 | * If true, a different diffing algorithm is used to generate error messages. 66 | * 67 | * @default false 68 | */ 69 | readonly isKubernetes?: boolean 70 | 71 | /** 72 | * A list of known schemas and/or associations of schemas to file names. 73 | * 74 | * @default [] 75 | */ 76 | readonly schemas?: SchemasSettings[] 77 | 78 | /** 79 | * If set, the validator will be enabled and perform syntax validation as well as schema 80 | * based validation. 81 | * 82 | * @default true 83 | */ 84 | readonly validate?: boolean 85 | 86 | /** 87 | * The YAML version to use for parsing. 88 | * 89 | * @default '1.2' 90 | */ 91 | readonly yamlVersion?: '1.1' | '1.2' 92 | } 93 | 94 | export interface LanguageServiceDefaults { 95 | readonly onDidChange: IEvent 96 | readonly diagnosticsOptions: DiagnosticsOptions 97 | setDiagnosticsOptions: (options: DiagnosticsOptions) => void 98 | } 99 | 100 | export function createLanguageServiceDefaults( 101 | initialDiagnosticsOptions: DiagnosticsOptions 102 | ): LanguageServiceDefaults 103 | 104 | export const yamlDefaults: LanguageServiceDefaults 105 | 106 | /** 107 | * Configure `monaco-yaml` diagnostics options. 108 | * 109 | * @param options The options to set. 110 | */ 111 | export function setDiagnosticsOptions(options?: DiagnosticsOptions): void 112 | -------------------------------------------------------------------------------- /src/yamlMode.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js' 2 | import { registerMarkerDataProvider } from 'monaco-marker-data-provider' 3 | import { createWorkerManager } from 'monaco-worker-manager' 4 | import { type LanguageServiceDefaults } from 'monaco-yaml' 5 | 6 | import { languageId } from './constants.js' 7 | import { 8 | createCodeActionProvider, 9 | createCompletionItemProvider, 10 | createDefinitionProvider, 11 | createDocumentFormattingEditProvider, 12 | createDocumentSymbolProvider, 13 | createHoverProvider, 14 | createLinkProvider, 15 | createMarkerDataProvider 16 | } from './languageFeatures.js' 17 | import { type CreateData, type YAMLWorker } from './yaml.worker.js' 18 | 19 | const richEditConfiguration: monaco.languages.LanguageConfiguration = { 20 | comments: { 21 | lineComment: '#' 22 | }, 23 | brackets: [ 24 | ['{', '}'], 25 | ['[', ']'], 26 | ['(', ')'] 27 | ], 28 | autoClosingPairs: [ 29 | { open: '{', close: '}' }, 30 | { open: '[', close: ']' }, 31 | { open: '(', close: ')' }, 32 | { open: '"', close: '"' }, 33 | { open: "'", close: "'" } 34 | ], 35 | surroundingPairs: [ 36 | { open: '{', close: '}' }, 37 | { open: '[', close: ']' }, 38 | { open: '(', close: ')' }, 39 | { open: '"', close: '"' }, 40 | { open: "'", close: "'" } 41 | ], 42 | 43 | onEnterRules: [ 44 | { 45 | beforeText: /:\s*$/, 46 | action: { indentAction: monaco.languages.IndentAction.Indent } 47 | } 48 | ] 49 | } 50 | 51 | export function setupMode(defaults: LanguageServiceDefaults): void { 52 | const worker = createWorkerManager(monaco, { 53 | label: 'yaml', 54 | moduleId: 'monaco-yaml/yaml.worker', 55 | createData: { 56 | languageSettings: defaults.diagnosticsOptions, 57 | enableSchemaRequest: defaults.diagnosticsOptions.enableSchemaRequest 58 | } 59 | }) 60 | 61 | defaults.onDidChange(() => { 62 | worker.updateCreateData({ 63 | languageSettings: defaults.diagnosticsOptions, 64 | enableSchemaRequest: defaults.diagnosticsOptions.enableSchemaRequest 65 | }) 66 | }) 67 | 68 | monaco.languages.registerCompletionItemProvider( 69 | languageId, 70 | createCompletionItemProvider(worker.getWorker) 71 | ) 72 | monaco.languages.registerHoverProvider(languageId, createHoverProvider(worker.getWorker)) 73 | monaco.languages.registerDefinitionProvider( 74 | languageId, 75 | createDefinitionProvider(worker.getWorker) 76 | ) 77 | monaco.languages.registerDocumentSymbolProvider( 78 | languageId, 79 | createDocumentSymbolProvider(worker.getWorker) 80 | ) 81 | monaco.languages.registerDocumentFormattingEditProvider( 82 | languageId, 83 | createDocumentFormattingEditProvider(worker.getWorker) 84 | ) 85 | monaco.languages.registerLinkProvider(languageId, createLinkProvider(worker.getWorker)) 86 | monaco.languages.registerCodeActionProvider( 87 | languageId, 88 | createCodeActionProvider(worker.getWorker) 89 | ) 90 | monaco.languages.setLanguageConfiguration(languageId, richEditConfiguration) 91 | 92 | let markerDataProvider = registerMarkerDataProvider( 93 | monaco, 94 | languageId, 95 | createMarkerDataProvider(worker.getWorker) 96 | ) 97 | defaults.onDidChange(() => { 98 | markerDataProvider.dispose() 99 | markerDataProvider = registerMarkerDataProvider( 100 | monaco, 101 | languageId, 102 | createMarkerDataProvider(worker.getWorker) 103 | ) 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /src/languageFeatures.ts: -------------------------------------------------------------------------------- 1 | import { type languages } from 'monaco-editor/esm/vs/editor/editor.api.js' 2 | import { 3 | fromMarkerData, 4 | fromPosition, 5 | fromRange, 6 | toCodeAction, 7 | toCompletionList, 8 | toDocumentSymbol, 9 | toHover, 10 | toLink, 11 | toLocationLink, 12 | toMarkerData, 13 | toTextEdit 14 | } from 'monaco-languageserver-types' 15 | import { type MarkerDataProvider } from 'monaco-marker-data-provider' 16 | import { type WorkerGetter } from 'monaco-worker-manager' 17 | 18 | import { languageId } from './constants.js' 19 | import { type YAMLWorker } from './yaml.worker.js' 20 | 21 | export type WorkerAccessor = WorkerGetter 22 | 23 | export function createMarkerDataProvider(getWorker: WorkerAccessor): MarkerDataProvider { 24 | return { 25 | owner: languageId, 26 | async provideMarkerData(model) { 27 | const worker = await getWorker(model.uri) 28 | const diagnostics = await worker.doValidation(String(model.uri)) 29 | 30 | return diagnostics?.map((diagnostic) => toMarkerData(diagnostic)) 31 | }, 32 | 33 | async doReset(model) { 34 | const worker = await getWorker(model.uri) 35 | await worker.resetSchema(String(model.uri)) 36 | } 37 | } 38 | } 39 | 40 | export function createCompletionItemProvider( 41 | getWorker: WorkerAccessor 42 | ): languages.CompletionItemProvider { 43 | return { 44 | triggerCharacters: [' ', ':'], 45 | 46 | async provideCompletionItems(model, position) { 47 | const resource = model.uri 48 | 49 | const worker = await getWorker(resource) 50 | const info = await worker.doComplete(String(resource), fromPosition(position)) 51 | if (!info) { 52 | return 53 | } 54 | 55 | const wordInfo = model.getWordUntilPosition(position) 56 | 57 | return toCompletionList(info, { 58 | range: { 59 | startLineNumber: position.lineNumber, 60 | startColumn: wordInfo.startColumn, 61 | endLineNumber: position.lineNumber, 62 | endColumn: wordInfo.endColumn 63 | } 64 | }) 65 | } 66 | } 67 | } 68 | 69 | export function createDefinitionProvider(getWorker: WorkerAccessor): languages.DefinitionProvider { 70 | return { 71 | async provideDefinition(model, position) { 72 | const resource = model.uri 73 | 74 | const worker = await getWorker(resource) 75 | const locationLinks = await worker.doDefinition(String(resource), fromPosition(position)) 76 | 77 | return locationLinks?.map(toLocationLink) 78 | } 79 | } 80 | } 81 | 82 | export function createHoverProvider(getWorker: WorkerAccessor): languages.HoverProvider { 83 | return { 84 | async provideHover(model, position) { 85 | const resource = model.uri 86 | 87 | const worker = await getWorker(resource) 88 | const info = await worker.doHover(String(resource), fromPosition(position)) 89 | if (!info) { 90 | return 91 | } 92 | 93 | return toHover(info) 94 | } 95 | } 96 | } 97 | 98 | export function createDocumentSymbolProvider( 99 | getWorker: WorkerAccessor 100 | ): languages.DocumentSymbolProvider { 101 | return { 102 | async provideDocumentSymbols(model) { 103 | const resource = model.uri 104 | 105 | const worker = await getWorker(resource) 106 | const items = await worker.findDocumentSymbols(String(resource)) 107 | 108 | return items?.map(toDocumentSymbol) 109 | } 110 | } 111 | } 112 | 113 | export function createDocumentFormattingEditProvider( 114 | getWorker: WorkerAccessor 115 | ): languages.DocumentFormattingEditProvider { 116 | return { 117 | async provideDocumentFormattingEdits(model) { 118 | const resource = model.uri 119 | 120 | const worker = await getWorker(resource) 121 | const edits = await worker.format(String(resource), {}) 122 | 123 | return edits?.map(toTextEdit) 124 | } 125 | } 126 | } 127 | 128 | export function createLinkProvider(getWorker: WorkerAccessor): languages.LinkProvider { 129 | return { 130 | async provideLinks(model) { 131 | const resource = model.uri 132 | 133 | const worker = await getWorker(resource) 134 | const links = await worker.findLinks(String(resource)) 135 | 136 | if (!links) { 137 | return 138 | } 139 | 140 | return { 141 | links: links.map(toLink) 142 | } 143 | } 144 | } 145 | } 146 | 147 | export function createCodeActionProvider(getWorker: WorkerAccessor): languages.CodeActionProvider { 148 | return { 149 | async provideCodeActions(model, range, context) { 150 | const resource = model.uri 151 | 152 | const worker = await getWorker(resource) 153 | const codeActions = await worker.getCodeAction( 154 | String(resource), 155 | fromRange(range), 156 | context.markers.map(fromMarkerData) 157 | ) 158 | 159 | if (!codeActions) { 160 | return 161 | } 162 | 163 | return { 164 | actions: codeActions.map((codeAction) => toCodeAction(codeAction)), 165 | dispose() { 166 | // This is required by the TypeScript interface, but it’s not implemented. 167 | } 168 | } 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/yaml.worker.ts: -------------------------------------------------------------------------------- 1 | import { initialize } from 'monaco-worker-manager/worker' 2 | import { TextDocument } from 'vscode-languageserver-textdocument' 3 | import { 4 | type CodeAction, 5 | type CompletionList, 6 | type Diagnostic, 7 | type DocumentLink, 8 | type DocumentSymbol, 9 | type Hover, 10 | type LocationLink, 11 | type Position, 12 | type Range, 13 | type TextEdit 14 | } from 'vscode-languageserver-types' 15 | import { type Telemetry } from 'yaml-language-server/lib/esm/languageservice/telemetry.js' 16 | import { 17 | type CustomFormatterOptions, 18 | getLanguageService, 19 | type LanguageSettings, 20 | type WorkspaceContextService 21 | } from 'yaml-language-server/lib/esm/languageservice/yamlLanguageService.js' 22 | 23 | import { languageId } from './constants.js' 24 | 25 | async function schemaRequestService(uri: string): Promise { 26 | const response = await fetch(uri) 27 | if (response.ok) { 28 | return response.text() 29 | } 30 | throw new Error(`Schema request failed for ${uri}`) 31 | } 32 | 33 | export interface CreateData { 34 | languageSettings: LanguageSettings 35 | enableSchemaRequest?: boolean 36 | } 37 | 38 | export interface YAMLWorker { 39 | doValidation: (uri: string) => Diagnostic[] | undefined 40 | 41 | doComplete: (uri: string, position: Position) => CompletionList | undefined 42 | 43 | doDefinition: (uri: string, position: Position) => LocationLink[] | undefined 44 | 45 | doHover: (uri: string, position: Position) => Hover | null | undefined 46 | 47 | format: (uri: string, options: CustomFormatterOptions) => TextEdit[] | undefined 48 | 49 | resetSchema: (uri: string) => boolean | undefined 50 | 51 | findDocumentSymbols: (uri: string) => DocumentSymbol[] | undefined 52 | 53 | findLinks: (uri: string) => DocumentLink[] | undefined 54 | 55 | getCodeAction: (uri: string, range: Range, diagnostics: Diagnostic[]) => CodeAction[] | undefined 56 | } 57 | 58 | const telemetry: Telemetry = { 59 | // eslint-disable-next-line @typescript-eslint/no-empty-function 60 | send() {}, 61 | sendError(name, properties) { 62 | // eslint-disable-next-line no-console 63 | console.error('monaco-yaml', name, properties) 64 | }, 65 | // eslint-disable-next-line @typescript-eslint/no-empty-function 66 | sendTrack() {} 67 | } 68 | 69 | const workspaceContext: WorkspaceContextService = { 70 | resolveRelativePath(relativePath, resource) { 71 | return String(new URL(relativePath, resource)) 72 | } 73 | } 74 | 75 | initialize((ctx, { enableSchemaRequest, languageSettings }) => { 76 | const languageService = getLanguageService({ 77 | // @ts-expect-error Type definitions are wrong. This may be null. 78 | schemaRequestService: enableSchemaRequest ? schemaRequestService : null, 79 | telemetry, 80 | workspaceContext 81 | }) 82 | languageService.configure(languageSettings) 83 | 84 | const getTextDocument = (uri: string): TextDocument | undefined => { 85 | const models = ctx.getMirrorModels() 86 | for (const model of models) { 87 | if (String(model.uri) === uri) { 88 | return TextDocument.create(uri, languageId, model.version, model.getValue()) 89 | } 90 | } 91 | } 92 | 93 | return { 94 | doValidation(uri) { 95 | const document = getTextDocument(uri) 96 | if (document) { 97 | return languageService.doValidation(document, Boolean(languageSettings.isKubernetes)) 98 | } 99 | }, 100 | 101 | doComplete(uri, position) { 102 | const document = getTextDocument(uri) 103 | if (document) { 104 | return languageService.doComplete( 105 | document, 106 | position, 107 | Boolean(languageSettings.isKubernetes) 108 | ) 109 | } 110 | }, 111 | 112 | doDefinition(uri, position) { 113 | const document = getTextDocument(uri) 114 | if (document) { 115 | return languageService.doDefinition(document, { position, textDocument: { uri } }) 116 | } 117 | }, 118 | 119 | doHover(uri, position) { 120 | const document = getTextDocument(uri) 121 | if (document) { 122 | return languageService.doHover(document, position) 123 | } 124 | }, 125 | 126 | format(uri, options) { 127 | const document = getTextDocument(uri) 128 | if (document) { 129 | return languageService.doFormat(document, options) 130 | } 131 | }, 132 | 133 | resetSchema(uri) { 134 | return languageService.resetSchema(uri) 135 | }, 136 | 137 | findDocumentSymbols(uri) { 138 | const document = getTextDocument(uri) 139 | if (document) { 140 | return languageService.findDocumentSymbols2(document, {}) 141 | } 142 | }, 143 | 144 | findLinks(uri) { 145 | const document = getTextDocument(uri) 146 | if (document) { 147 | return languageService.findLinks(document) 148 | } 149 | }, 150 | 151 | getCodeAction(uri, range, diagnostics) { 152 | const document = getTextDocument(uri) 153 | if (document) { 154 | return languageService.getCodeAction(document, { 155 | range, 156 | textDocument: { uri }, 157 | context: { diagnostics } 158 | }) 159 | } 160 | } 161 | } 162 | }) 163 | -------------------------------------------------------------------------------- /examples/demo/src/index.ts: -------------------------------------------------------------------------------- 1 | import { type JSONSchemaForSchemaStoreOrgCatalogFiles } from '@schemastore/schema-catalog' 2 | import { editor, languages, MarkerSeverity, type Position, Range, Uri } from 'monaco-editor' 3 | import { ILanguageFeaturesService } from 'monaco-editor/esm/vs/editor/common/services/languageFeatures.js' 4 | import { OutlineModel } from 'monaco-editor/esm/vs/editor/contrib/documentSymbols/browser/outlineModel.js' 5 | import { StandaloneServices } from 'monaco-editor/esm/vs/editor/standalone/browser/standaloneServices.js' 6 | import { type SchemasSettings, setDiagnosticsOptions } from 'monaco-yaml' 7 | 8 | import './index.css' 9 | import schema from './schema.json' 10 | 11 | window.MonacoEnvironment = { 12 | getWorker(moduleId, label) { 13 | switch (label) { 14 | case 'editorWorkerService': 15 | return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url)) 16 | case 'yaml': 17 | return new Worker(new URL('monaco-yaml/yaml.worker', import.meta.url)) 18 | default: 19 | throw new Error(`Unknown label ${label}`) 20 | } 21 | } 22 | } 23 | 24 | const defaultSchema: SchemasSettings = { 25 | uri: 'https://github.com/remcohaszing/monaco-yaml/blob/HEAD/examples/demo/src/schema.json', 26 | // @ts-expect-error TypeScript can’t narrow down the type of JSON imports 27 | schema, 28 | fileMatch: ['monaco-yaml.yaml'] 29 | } 30 | 31 | setDiagnosticsOptions({ 32 | schemas: [defaultSchema] 33 | }) 34 | 35 | const value = ` 36 | # Property descriptions are displayed when hovering over properties using your cursor 37 | property: This property has a JSON schema description 38 | 39 | 40 | # Titles work too! 41 | titledProperty: Titles work too! 42 | 43 | 44 | # Even markdown descriptions work 45 | markdown: hover me to get a markdown based description 😮 46 | 47 | 48 | # Enums can be autocompleted by placing the cursor after the colon and pressing Ctrl+Space 49 | enum: 50 | 51 | 52 | # Unused anchors will be reported 53 | unused anchor: &unused anchor 54 | 55 | 56 | # Of course numbers are supported! 57 | number: 12 58 | 59 | 60 | # As well as booleans! 61 | boolean: true 62 | 63 | 64 | # And strings 65 | string: I am a string 66 | 67 | 68 | # This property is using the JSON schema recursively 69 | reference: 70 | boolean: Not a boolean 71 | 72 | 73 | # Also works in arrays 74 | array: 75 | - string: 12 76 | enum: Mewtwo 77 | reference: 78 | reference: 79 | boolean: true 80 | 81 | 82 | # JSON referenses can be clicked for navigation 83 | pointer: 84 | $ref: '#/array' 85 | 86 | 87 | # This anchor can be referenced 88 | anchorRef: &anchor can be clicked as well 89 | 90 | 91 | # Press control while hovering over the anchor 92 | anchorPointer: *anchor 93 | 94 | 95 | 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. 96 | 97 | 98 | 99 | 100 | 101 | 102 | `.replace(/:$/m, ': ') 103 | 104 | const ed = editor.create(document.getElementById('editor')!, { 105 | automaticLayout: true, 106 | model: editor.createModel(value, 'yaml', Uri.parse('monaco-yaml.yaml')), 107 | theme: window.matchMedia('(prefers-color-scheme: dark)').matches ? 'vs-dark' : 'vs-light' 108 | }) 109 | 110 | const select = document.getElementById('schema-selection') as HTMLSelectElement 111 | 112 | // eslint-disable-next-line unicorn/prefer-top-level-await 113 | fetch('https://www.schemastore.org/api/json/catalog.json').then(async (response) => { 114 | if (!response.ok) { 115 | return 116 | } 117 | const catalog = (await response.json()) as JSONSchemaForSchemaStoreOrgCatalogFiles 118 | const schemas = [defaultSchema] 119 | catalog.schemas.sort((a, b) => a.name.localeCompare(b.name)) 120 | for (const { fileMatch, name, url } of catalog.schemas) { 121 | const match = 122 | typeof name === 'string' && fileMatch?.find((filename) => /\.ya?ml$/i.test(filename)) 123 | if (!match) { 124 | continue 125 | } 126 | const option = document.createElement('option') 127 | option.value = match 128 | 129 | option.textContent = name 130 | select.append(option) 131 | schemas.push({ 132 | fileMatch: [match], 133 | uri: url 134 | }) 135 | } 136 | 137 | setDiagnosticsOptions({ 138 | validate: true, 139 | enableSchemaRequest: true, 140 | format: true, 141 | hover: true, 142 | completion: true, 143 | schemas 144 | }) 145 | }) 146 | 147 | select.addEventListener('change', () => { 148 | const oldModel = ed.getModel() 149 | const newModel = editor.createModel(oldModel?.getValue() ?? '', 'yaml', Uri.parse(select.value)) 150 | ed.setModel(newModel) 151 | oldModel?.dispose() 152 | }) 153 | 154 | function* iterateSymbols( 155 | symbols: languages.DocumentSymbol[], 156 | position: Position 157 | ): Iterable { 158 | for (const symbol of symbols) { 159 | if (Range.containsPosition(symbol.range, position)) { 160 | yield symbol 161 | if (symbol.children) { 162 | yield* iterateSymbols(symbol.children, position) 163 | } 164 | } 165 | } 166 | } 167 | 168 | ed.onDidChangeCursorPosition(async (event) => { 169 | const breadcrumbs = document.getElementById('breadcrumbs')! 170 | const { documentSymbolProvider } = StandaloneServices.get(ILanguageFeaturesService) 171 | const outline = await OutlineModel.create(documentSymbolProvider, ed.getModel()!) 172 | const symbols = outline.asListOfDocumentSymbols() 173 | while (breadcrumbs.lastChild) { 174 | breadcrumbs.lastChild.remove() 175 | } 176 | for (const symbol of iterateSymbols(symbols, event.position)) { 177 | const breadcrumb = document.createElement('span') 178 | breadcrumb.setAttribute('role', 'button') 179 | breadcrumb.classList.add('breadcrumb') 180 | breadcrumb.textContent = symbol.name 181 | breadcrumb.title = symbol.detail 182 | if (symbol.kind === languages.SymbolKind.Array) { 183 | breadcrumb.classList.add('array') 184 | } else if (symbol.kind === languages.SymbolKind.Module) { 185 | breadcrumb.classList.add('object') 186 | } 187 | breadcrumb.addEventListener('click', () => { 188 | ed.setPosition({ 189 | lineNumber: symbol.range.startLineNumber, 190 | column: symbol.range.startColumn 191 | }) 192 | ed.focus() 193 | }) 194 | breadcrumbs.append(breadcrumb) 195 | } 196 | }) 197 | 198 | editor.onDidChangeMarkers(([resource]) => { 199 | const problems = document.getElementById('problems')! 200 | const markers = editor.getModelMarkers({ resource }) 201 | while (problems.lastChild) { 202 | problems.lastChild.remove() 203 | } 204 | for (const marker of markers) { 205 | if (marker.severity === MarkerSeverity.Hint) { 206 | continue 207 | } 208 | const wrapper = document.createElement('div') 209 | wrapper.setAttribute('role', 'button') 210 | const codicon = document.createElement('div') 211 | const text = document.createElement('div') 212 | wrapper.classList.add('problem') 213 | codicon.classList.add( 214 | 'codicon', 215 | marker.severity === MarkerSeverity.Warning ? 'codicon-warning' : 'codicon-error' 216 | ) 217 | text.classList.add('problem-text') 218 | text.textContent = marker.message 219 | wrapper.append(codicon, text) 220 | wrapper.addEventListener('click', () => { 221 | ed.setPosition({ lineNumber: marker.startLineNumber, column: marker.startColumn }) 222 | ed.focus() 223 | }) 224 | problems.append(wrapper) 225 | } 226 | }) 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Monaco YAML 2 | 3 | [![ci workflow](https://github.com/remcohaszing/monaco-yaml/actions/workflows/ci.yaml/badge.svg)](https://github.com/remcohaszing/monaco-yaml/actions/workflows/ci.yaml) 4 | [![npm version](https://img.shields.io/npm/v/monaco-yaml)](https://www.npmjs.com/package/monaco-yaml) 5 | [![prettier code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io) 6 | [![demo](https://img.shields.io/badge/demo-monaco--yaml.js.org-61ffcf.svg)](https://monaco-yaml.js.org) 7 | [![netlify status](https://api.netlify.com/api/v1/badges/20b08937-99d0-4882-b9a3-d5f09ddd29b7/deploy-status)](https://app.netlify.com/sites/monaco-yaml/deploys) 8 | 9 | YAML language plugin for the Monaco Editor. It provides the following features when editing YAML 10 | files: 11 | 12 | - Code completion, based on JSON schemas or by looking at similar objects in the same file 13 | - Hovers, based on JSON schemas 14 | - Validation: Syntax errors and schema validation 15 | - Formatting using Prettier 16 | - Document Symbols 17 | - Automatically load remote schema files (by enabling DiagnosticsOptions.enableSchemaRequest) 18 | - Links from JSON references. 19 | - Links and hover effects from YAML anchors. 20 | 21 | Schemas can also be provided by configuration. See 22 | [here](https://github.com/remcohaszing/monaco-yaml/blob/main/index.d.ts) for the API that the plugin 23 | offers to configure the YAML language support. 24 | 25 | ## Table of Contents 26 | 27 | - [Installation](#installation) 28 | - [Usage](#usage) 29 | - [Examples](#examples) 30 | - [FAQ](#faq) 31 | - [Does this work with the Monaco UMD bundle?](#does-this-work-with-the-monaco-umd-bundle) 32 | - [Does this work with Monaco Editor from a CDN?](#does-this-work-with-monaco-editor-from-a-cdn) 33 | - [Does this work with `@monaco-editor/loader` or `@monaco-editor/react`?](#does-this-work-with-monaco-editorloader-or-monaco-editorreact) 34 | - [Is the web worker necessary?](#is-the-web-worker-necessary) 35 | - [Does it work without a bundler?](#does-it-work-without-a-bundler) 36 | - [How do I integrate `monaco-yaml` with a framework? (Angular, React, Vue, etc.)](#how-do-i-integrate-monaco-yaml-with-a-framework-angular-react-vue-etc) 37 | - [Does `monaco-yaml` work with `create-react-app`?](#does-monaco-yaml-work-with-create-react-app) 38 | - [Why doesn’t it work with Vite?](#why-doesnt-it-work-with-vite) 39 | - [Why isn’t `monaco-yaml` working? Official Monaco language extensions do work.](#why-isnt-monaco-yaml-working-official-monaco-language-extensions-do-work) 40 | - [Using Monaco webpack loader plugin](#using-monaco-webpack-loader-plugin) 41 | - [Why does it try to download my schema even when I provided one as an object?](#why-does-it-try-to-download-my-schema-even-when-i-provided-one-as-an-object) 42 | - [Contributing](#contributing) 43 | - [Credits](#credits) 44 | - [License](#license) 45 | 46 | ## Installation 47 | 48 | ```sh 49 | npm install monaco-yaml 50 | ``` 51 | 52 | ## Usage 53 | 54 | Import `monaco-yaml` and configure it before an editor instance is created. 55 | 56 | ```typescript 57 | import { editor, Uri } from 'monaco-editor' 58 | import { setDiagnosticsOptions } from 'monaco-yaml' 59 | 60 | // The uri is used for the schema file match. 61 | const modelUri = Uri.parse('a://b/foo.yaml') 62 | 63 | setDiagnosticsOptions({ 64 | enableSchemaRequest: true, 65 | hover: true, 66 | completion: true, 67 | validate: true, 68 | format: true, 69 | schemas: [ 70 | { 71 | // Id of the first schema 72 | uri: 'http://myserver/foo-schema.json', 73 | // Associate with our model 74 | fileMatch: [String(modelUri)], 75 | schema: { 76 | type: 'object', 77 | properties: { 78 | p1: { 79 | enum: ['v1', 'v2'] 80 | }, 81 | p2: { 82 | // Reference the second schema 83 | $ref: 'http://myserver/bar-schema.json' 84 | } 85 | } 86 | } 87 | }, 88 | { 89 | // Id of the first schema 90 | uri: 'http://myserver/bar-schema.json', 91 | schema: { 92 | type: 'object', 93 | properties: { 94 | q1: { 95 | enum: ['x1', 'x2'] 96 | } 97 | } 98 | } 99 | } 100 | ] 101 | }) 102 | 103 | editor.create(document.createElement('editor'), { 104 | // Monaco-yaml features should just work if the editor language is set to 'yaml'. 105 | language: 'yaml', 106 | model: editor.createModel('p1: \n', 'yaml', modelUri) 107 | }) 108 | ``` 109 | 110 | Also make sure to register the web worker. When using Webpack 5, this looks like the code below. 111 | Other bundlers may use a different syntax, but the idea is the same. Languages you don’t used can be 112 | omitted. 113 | 114 | ```js 115 | window.MonacoEnvironment = { 116 | getWorker(moduleId, label) { 117 | switch (label) { 118 | case 'editorWorkerService': 119 | return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url)) 120 | case 'css': 121 | case 'less': 122 | case 'scss': 123 | return new Worker(new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url)) 124 | case 'handlebars': 125 | case 'html': 126 | case 'razor': 127 | return new Worker( 128 | new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url) 129 | ) 130 | case 'json': 131 | return new Worker( 132 | new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url) 133 | ) 134 | case 'javascript': 135 | case 'typescript': 136 | return new Worker( 137 | new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url) 138 | ) 139 | case 'yaml': 140 | return new Worker(new URL('monaco-yaml/yaml.worker', import.meta.url)) 141 | default: 142 | throw new Error(`Unknown label ${label}`) 143 | } 144 | } 145 | } 146 | ``` 147 | 148 | ## Examples 149 | 150 | A demo is available on [monaco-yaml.js.org](https://monaco-yaml.js.org). 151 | 152 | Some usage examples can be found in the 153 | [examples](https://github.com/remcohaszing/monaco-yaml/tree/main/examples) directory. 154 | 155 | ## FAQ 156 | 157 | ### Does this work with the Monaco UMD bundle? 158 | 159 | No. Only ESM is supported. 160 | 161 | ### Does this work with Monaco Editor from a CDN? 162 | 163 | No, because these use a UMD bundle, which isn’t supported. 164 | 165 | ### Does this work with `@monaco-editor/loader` or `@monaco-editor/react`? 166 | 167 | No. These packages pull in the Monaco UMD bundle from a CDN. Because UMD isn’t supported, neither 168 | are these packages. 169 | 170 | ### Is the web worker necessary? 171 | 172 | Yes. The web worker provides the core functionality of `monaco-yaml`. 173 | 174 | ### Does it work without a bundler? 175 | 176 | No. `monaco-yaml` uses dependencies from `node_modules`, so they can be deduped and your bundle size 177 | is decreased. This comes at the cost of not being able to use it without a bundler. 178 | 179 | ### How do I integrate `monaco-yaml` with a framework? (Angular, React, Vue, etc.) 180 | 181 | `monaco-yaml` only uses the Monaco Editor. It’s not tied to a framework, all that’s needed is a DOM 182 | node to attach the Monaco Editor to. See the 183 | [Monaco Editor examples](https://github.com/microsoft/monaco-editor/tree/main/monaco-editor-samples) 184 | for examples on how to integrate Monaco Editor in your project, then configure `monaco-yaml` as 185 | described above. 186 | 187 | ### Does `monaco-yaml` work with `create-react-app`? 188 | 189 | Yes, but you’ll have to eject. See 190 | [#92 (comment)](https://github.com/remcohaszing/monaco-yaml/issues/92#issuecomment-905836058) for 191 | details. 192 | 193 | ### Why doesn’t it work with Vite? 194 | 195 | Some users have experienced the following error when using Vite: 196 | 197 | ``` 198 | Uncaught (in promise) Error: Unexpected usage 199 | at EditorSimpleWorker.loadForeignModule (editorSimpleWorker.js) 200 | at webWorker.js 201 | ``` 202 | 203 | As a workaround, create a file named `yaml.worker.js` in your own project with the following 204 | contents: 205 | 206 | ```js 207 | import 'monaco-yaml/yaml.worker.js' 208 | ``` 209 | 210 | Then in your Monaco environment `getWorker` function, reference this file instead of referencing 211 | `monaco-yaml/yaml.worker.js` directly: 212 | 213 | ```js 214 | import YamlWorker from './yaml.worker.js?worker' 215 | 216 | window.MonacoEnvironment = { 217 | getWorker(moduleId, label) { 218 | switch (label) { 219 | // Handle other cases 220 | case 'yaml': 221 | return new YamlWorker() 222 | default: 223 | throw new Error(`Unknown label ${label}`) 224 | } 225 | } 226 | } 227 | ``` 228 | 229 | ### Why isn’t `monaco-yaml` working? Official Monaco language extensions do work. 230 | 231 | This is most likely due to the fact that `monaco-yaml` is using a different instance of the 232 | `monaco-editor` package than you are. This is something you’ll want to avoid regardless of 233 | `monaco-editor`, because it means your bundle is significantly larger than it needs to be. This is 234 | likely caused by one of the following issues: 235 | 236 | - A code splitting misconfiguration 237 | 238 | To solve this, try inspecting your bundle using for example 239 | [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer). If 240 | `monaco-editor` is in there twice, this is the issue. It’s up to you to solve this, as it’s 241 | project-specific. 242 | 243 | - You’re using a package which imports `monaco-editor` for you, but it’s using a different version. 244 | 245 | You can find out why the `monaco-editor` is installed using `npm ls monaco-editor` or 246 | `yarn why monaco-editor`. It should exist only once, but it’s ok if it’s deduped. 247 | 248 | You may be able to solve this by deleting your `node_modules` folder and `package-lock.json` or 249 | `yarn.lock`, then running `npm install` or `yarn install` respectively. 250 | 251 | ### Using Monaco webpack loader plugin 252 | 253 | If you’re using 254 | [monaco webpack plugin](https://github.com/microsoft/monaco-editor/tree/main/webpack-plugin), then 255 | instead of the above code, you can extend the plugin’s configuration. Extend your 256 | `webpack.config.js` file with the following: 257 | 258 | ```js 259 | import { MonacoWebpackPlugin } from 'monaco-editor-webpack-plugin' 260 | 261 | export default { 262 | // ...the rest of your webpack configuration... 263 | plugins: [ 264 | new MonacoWebpackPlugin({ 265 | languages: ['yaml'], 266 | customLanguages: [ 267 | { 268 | label: 'yaml', 269 | entry: 'monaco-yaml', 270 | worker: { 271 | id: 'monaco-yaml/yamlWorker', 272 | entry: 'monaco-yaml/yaml.worker' 273 | } 274 | } 275 | ] 276 | }) 277 | ] 278 | } 279 | ``` 280 | 281 | You can also refer to the 282 | [example](https://github.com/remcohaszing/monaco-yaml/tree/main/examples/monaco-editor-webpack-plugin) 283 | of a complete project. 284 | 285 | ### Why does it try to download my schema even when I provided one as an object? 286 | 287 | You may have provided a schema configured like this: 288 | 289 | ```Javascript 290 | { 291 | uri: "http://example.com", 292 | fileMatch: ["file_name.yml"], 293 | schema: { 294 | $schema: "http://json-schema.org/draft-07/schema#", 295 | $id: "http://example.com", 296 | title: "placeholder title", 297 | description: "placeholder description", 298 | type: "object", 299 | properties: { 300 | name: { 301 | description: "name property description", 302 | type: "string", 303 | }, 304 | }, 305 | required: ["name"], 306 | }, 307 | } 308 | ``` 309 | 310 | And would be surprised to see the error: 311 | 312 | > `Unable to load schema from '': Failed to fetch.` 313 | 314 | It happens because plugin uses schema URI not only as the URL to download the schema from, but also 315 | to determine the schema name. To fix this, change the `uri` parameter to 316 | `http://example.com/schema-name.json`. 317 | 318 | ## Contributing 319 | 320 | Please see our [contributing guidelines](CONTRIBUTING.md) 321 | 322 | ## Credits 323 | 324 | Originally [@kpdecker](https://github.com/kpdecker) forked this repository from 325 | [`monaco-json`](https://github.com/microsoft/monaco-json) by 326 | [@microsoft](https://github.com/microsoft) and rewrote it to work with 327 | [`yaml-language-server`](https://github.com/redhat-developer/yaml-language-server) instead. Later 328 | the repository maintenance was taken over by [@pengx17](https://github.com/pengx17). Eventually the 329 | repository was tranferred to the account of [@remcohaszing](https://github.com/remcohaszing), who is 330 | currently maintaining this repository with the help of [@fleon](https://github.com/fleon) and 331 | [@yazaabed](https://github.com/yazaabed). 332 | 333 | The heavy processing is done in 334 | [`yaml-language-server`](https://github.com/redhat-developer/yaml-language-server), best known for 335 | being the backbone for [`vscode-yaml`](https://github.com/redhat-developer/vscode-yaml). This 336 | repository provides a thin layer to add functionality provided by `yaml-language-server` into 337 | `monaco-editor`. 338 | 339 | ## License 340 | 341 | [MIT](https://github.com/remcohaszing/monaco-yaml/blob/main/LICENSE.md) 342 | --------------------------------------------------------------------------------