├── packages ├── monaco-editor-wrapper │ ├── .gitignore │ ├── styles │ │ ├── css.d.ts │ │ ├── index.d.ts │ │ └── index.js │ ├── tsconfig.src.json │ ├── tsconfig.json │ ├── tsconfig.test.json │ ├── build │ │ ├── tsconfig.src.json │ │ └── src │ │ │ └── buildAssets.mts │ ├── vite.bundle.config.ts │ ├── src │ │ ├── logger.ts │ │ ├── utils.ts │ │ ├── index.ts │ │ ├── editorAppClassic.ts │ │ ├── editorAppExtended.ts │ │ ├── wrapper.ts │ │ └── editorAppBase.ts │ ├── LICENSE │ ├── test │ │ ├── logger.test.ts │ │ ├── helper.ts │ │ ├── editorAppBase.test.ts │ │ ├── editorAppExtended.test.ts │ │ ├── wrapper.test.ts │ │ ├── editorAppClassic.test.ts │ │ ├── utils.test.ts │ │ └── languageClientWrapper.test.ts │ ├── package.json │ ├── README.md │ └── CHANGELOG.md ├── examples │ ├── src │ │ ├── langium │ │ │ ├── worker │ │ │ │ ├── statemachine-server.ts │ │ │ │ ├── statemachine-server-port.ts │ │ │ │ └── startLanguageServer.ts │ │ │ ├── content │ │ │ │ ├── example.statemachine │ │ │ │ └── example.langium │ │ │ ├── reactStatemachine.tsx │ │ │ ├── config │ │ │ │ ├── wrapperLangiumClassic.ts │ │ │ │ ├── langium.monarch.ts │ │ │ │ ├── langium.configuration.json │ │ │ │ ├── wrapperLangiumExtended.ts │ │ │ │ ├── wrapperStatemachineConfig.ts │ │ │ │ └── langium.tmLanguage.json │ │ │ ├── wrapperStatemachine.ts │ │ │ └── wrapperLangium.ts │ │ ├── servers │ │ │ ├── python-server.ts │ │ │ ├── json-server.ts │ │ │ └── langium-server.ts │ │ ├── verifyPrepare.ts │ │ ├── reactTs.tsx │ │ ├── wrapperWs.ts │ │ ├── common.ts │ │ ├── wrapperTs.ts │ │ ├── reactPython.tsx │ │ └── wrapperAdvanced.ts │ ├── tsconfig.json │ ├── tsconfig.src.json │ ├── react_python.html │ ├── react_statemachine.html │ ├── react_ts.html │ ├── wrapper_ws.html │ ├── wrapper_ts.html │ ├── wrapper_statemachine.html │ ├── wrapper_adv.html │ ├── wrapper_langium.html │ ├── vite.bundle-mew.ts │ ├── CHANGELOG.md │ ├── build │ │ └── buildWorker.mts │ ├── verify_wrapper.html │ ├── package.json │ └── verify_alt.html └── monaco-editor-react │ ├── tsconfig.json │ ├── tsconfig.src.json │ ├── vite.bundle.config.ts │ ├── LICENSE │ ├── package.json │ ├── CHANGELOG.md │ ├── README.md │ └── src │ └── index.tsx ├── .gitpod.yml ├── .gitignore ├── .editorconfig ├── CHANGELOG.md ├── tsconfig.build.json ├── .vscode ├── settings.json └── launch.json ├── LICENSE ├── .github └── workflows │ └── actions.yml ├── tsconfig.json ├── README.md ├── package.json ├── index.html ├── vite.config.ts └── .eslintrc.cjs /packages/monaco-editor-wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | resources/themes 2 | resources/wasm 3 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/styles/css.d.ts: -------------------------------------------------------------------------------- 1 | export declare const getMonacoCss: () => string; 2 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | ports: 2 | - port: 20001 3 | tasks: 4 | - init: npm install && npm run build 5 | command: npm run dev 6 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/styles/index.d.ts: -------------------------------------------------------------------------------- 1 | export declare const addMonacoStyles: (idOfStyleElement: string) => Promise; 2 | -------------------------------------------------------------------------------- /packages/examples/src/langium/worker/statemachine-server.ts: -------------------------------------------------------------------------------- 1 | import { start } from './startLanguageServer.js'; 2 | 3 | declare const self: DedicatedWorkerGlobalScope; 4 | 5 | start(self, 'statemachine-server'); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bundle 2 | dist 3 | lib 4 | node_modules 5 | *.tsbuildinfo 6 | 7 | # exclude generated file 8 | packages/monaco-editor-wrapper/styles/css.js 9 | 10 | # exclude libs used for bundle verification 11 | packages/examples/libs 12 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/tsconfig.src.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist", 6 | "declarationDir": "dist" 7 | }, 8 | "include": [ 9 | "src/**/*.ts", 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | // this file is required for VSCode to work properly 2 | { 3 | "extends": "./tsconfig.src.json", 4 | "compilerOptions": { 5 | "noEmit": true, 6 | "rootDir": "." 7 | }, 8 | "include": [ 9 | "src/**/*", 10 | "test/**/*" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/monaco-editor-react/tsconfig.json: -------------------------------------------------------------------------------- 1 | // this file is required for VSCode to work properly 2 | { 3 | "extends": "./tsconfig.src.json", 4 | "compilerOptions": { 5 | "noEmit": true, 6 | "rootDir": "." 7 | }, 8 | "include": [ 9 | "src/**/*", 10 | "test/**/*" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/tsconfig.json: -------------------------------------------------------------------------------- 1 | // this file is required for VSCode to work properly 2 | { 3 | "extends": "./tsconfig.src.json", 4 | "compilerOptions": { 5 | "noEmit": true, 6 | "rootDir": "." 7 | }, 8 | "include": [ 9 | "src/**/*", 10 | "test/**/*" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.src.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "rootDir": "test" 6 | }, 7 | "references": [{ 8 | "path": "./tsconfig.src.json" 9 | }], 10 | "include": [ 11 | "test/**/*.ts", 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.json] 12 | indent_size = 2 13 | 14 | [*.yml] 15 | indent_size = 2 16 | 17 | [*.md] 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/styles/index.js: -------------------------------------------------------------------------------- 1 | import { getMonacoCss } from './css.js'; 2 | 3 | export const addMonacoStyles = (idOfStyleElement) => { 4 | const style = document.createElement('style'); 5 | style.id = idOfStyleElement; 6 | style.innerHTML = getMonacoCss(); 7 | document.head.appendChild(style); 8 | }; 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | All notable changes for **monaco-editor-wrapper** are found here: 4 | 5 | [monaco-editor-wrapper CHANGELOG](./packages/monaco-editor-wrapper/CHANGELOG.md) 6 | 7 | All notable changes for **@typefox/monaco-editor-react** are found here: 8 | 9 | [monaco-editor-react CHANGELOG](./packages/monaco-editor-react/CHANGELOG.md) 10 | -------------------------------------------------------------------------------- /packages/examples/src/servers/python-server.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { getLocalDirectory, runPythonServer } from 'monaco-languageclient-examples/node'; 3 | 4 | const baseDir = resolve(getLocalDirectory(import.meta.url)); 5 | const relativeDir = '../../../../node_modules/pyright/dist/pyright-langserver.js'; 6 | runPythonServer(baseDir, relativeDir); 7 | -------------------------------------------------------------------------------- /packages/examples/src/verifyPrepare.ts: -------------------------------------------------------------------------------- 1 | import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js'; 2 | import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution.js'; 3 | import 'monaco-editor/esm/vs/language/typescript/monaco.contribution.js'; 4 | export * from 'monaco-editor-wrapper'; 5 | export * from 'monaco-editor-wrapper/styles'; 6 | -------------------------------------------------------------------------------- /packages/examples/tsconfig.src.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist", 6 | "declarationDir": "dist" 7 | }, 8 | "references": [{ 9 | "path": "../monaco-editor-wrapper/tsconfig.src.json" 10 | }], 11 | "include": [ 12 | "src/**/*.ts", 13 | "src/**/*.tsx", 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/examples/src/servers/json-server.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { getLocalDirectory, runJsonServer } from 'monaco-languageclient-examples/node'; 3 | 4 | const baseDir = resolve(getLocalDirectory(import.meta.url)); 5 | const relativeDir = '../../../../node_modules/monaco-languageclient-examples/dist/json/server/json-server.js'; 6 | runJsonServer(baseDir, relativeDir); 7 | -------------------------------------------------------------------------------- /packages/monaco-editor-react/tsconfig.src.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist", 6 | "declarationDir": "dist", 7 | }, 8 | "references": [{ 9 | "path": "../monaco-editor-wrapper/tsconfig.src.json" 10 | }], 11 | "include": [ 12 | "src/**/*.ts", 13 | "src/**/*.tsx" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/build/tsconfig.src.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist", 6 | "declarationDir": "dist", 7 | "noEmit": true 8 | }, 9 | "references": [{ 10 | "path": "../tsconfig.src.json" 11 | }], 12 | "include": [ 13 | "src/**/*.ts", 14 | "src/**/*.mts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/examples/react_python.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Monaco Editor React Component Python 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/examples/react_statemachine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Monaco Editor React: Langium Statemachine Web Worker 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /packages/examples/react_ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Monaco Editor React Component TypeScript 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "packages/monaco-editor-wrapper/tsconfig.src.json" }, 5 | { "path": "packages/monaco-editor-wrapper/tsconfig.test.json" }, 6 | { "path": "packages/monaco-editor-wrapper/build/tsconfig.src.json" }, 7 | { "path": "packages/monaco-editor-react/tsconfig.src.json" }, 8 | { "path": "packages/examples/tsconfig.src.json" } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /packages/examples/src/langium/worker/statemachine-server-port.ts: -------------------------------------------------------------------------------- 1 | import { start } from './startLanguageServer.js'; 2 | 3 | declare const self: DedicatedWorkerGlobalScope; 4 | 5 | self.onmessage = async (event: MessageEvent) => { 6 | const data = event.data; 7 | if (data.port) { 8 | start(data.port, 'statemachine-server-port'); 9 | 10 | setTimeout(() => { 11 | // test independen communication 12 | self.postMessage('started'); 13 | }, 1000); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /packages/examples/src/langium/content/example.statemachine: -------------------------------------------------------------------------------- 1 | statemachine TrafficLight 2 | 3 | events 4 | switchCapacity 5 | next 6 | 7 | initialState PowerOff 8 | 9 | state PowerOff 10 | switchCapacity => RedLight 11 | end 12 | 13 | state RedLight 14 | switchCapacity => PowerOff 15 | next => GreenLight 16 | end 17 | 18 | state YellowLight 19 | switchCapacity => PowerOff 20 | next => RedLight 21 | end 22 | 23 | state GreenLight 24 | switchCapacity => PowerOff 25 | next => YellowLight 26 | end 27 | -------------------------------------------------------------------------------- /packages/examples/wrapper_ws.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Monaco Editor Wrapper Web Socket LSP Example 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/examples/wrapper_ts.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Monaco Editor Wrapper TypeScript Example 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.guides.bracketPairs": true, 3 | "editor.formatOnSave": false, 4 | "[javascript]": { 5 | "editor.formatOnSave": true 6 | }, 7 | "[typescriptreact]": { 8 | "editor.formatOnSave": true 9 | }, 10 | "[typescript]": { 11 | "editor.formatOnSave": true 12 | }, 13 | "[yml]": { 14 | "editor.formatOnSave": true 15 | }, 16 | "eslint.codeAction.showDocumentation": { 17 | "enable": true 18 | }, 19 | "eslint.format.enable": true, 20 | "eslint.validate": [ 21 | "javascript", 22 | "typescript", 23 | "typescriptreact" 24 | ], 25 | "[json]": { 26 | "editor.defaultFormatter": "vscode.json-language-features" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/examples/wrapper_statemachine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Monaco Editor Wrapper Langium Web Worker Language Server Example 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/examples/wrapper_adv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Monaco Editor Languageclient Component 8 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/vite.bundle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import path from 'path'; 3 | 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: path.resolve(__dirname, 'src/index.ts'), 8 | name: 'monaco-editor-wrapper', 9 | fileName: () => 'index.js', 10 | formats: ['es'] 11 | }, 12 | outDir: 'bundle', 13 | assetsDir: 'bundle/assets', 14 | emptyOutDir: true, 15 | cssCodeSplit: false, 16 | sourcemap: true, 17 | rollupOptions: { 18 | output: { 19 | name: 'monaco-editor-wrapper', 20 | exports: 'named', 21 | assetFileNames: (assetInfo) => { 22 | return `assets/${assetInfo.name}`; 23 | } 24 | } 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /packages/monaco-editor-react/vite.bundle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import path from 'path'; 3 | 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: path.resolve(__dirname, 'src/index.tsx'), 8 | name: 'monaco-editor-react', 9 | fileName: () => 'index.js', 10 | formats: ['es'] 11 | }, 12 | outDir: 'bundle', 13 | assetsDir: 'bundle/assets', 14 | emptyOutDir: true, 15 | cssCodeSplit: false, 16 | rollupOptions: { 17 | output: { 18 | name: 'monaco-editor-react', 19 | exports: 'named', 20 | sourcemap: false, 21 | assetFileNames: (assetInfo) => { 22 | return `assets/${assetInfo.name}`; 23 | } 24 | } 25 | } 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /packages/examples/wrapper_langium.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Langium Grammar DSL Language Client & Server (Worker) 6 | 7 | 8 | 9 | 10 | 11 |

Langium Grammar DSL Language Client & Server (Worker)

12 |
13 | 14 | 15 | 16 |
17 |
18 | ; 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/src/logger.ts: -------------------------------------------------------------------------------- 1 | export type LoggerConfig = { 2 | enabled: boolean, 3 | debugEnabled?: boolean 4 | }; 5 | 6 | export class Logger { 7 | 8 | private enabled: boolean; 9 | private debugEnabled: boolean; 10 | 11 | constructor(config?: LoggerConfig) { 12 | this.enabled = !config ? true : config!.enabled === true; 13 | this.debugEnabled = this.enabled && config?.debugEnabled === true; 14 | } 15 | 16 | isEnabled() { 17 | return this.enabled; 18 | } 19 | 20 | isDebugEnabled() { 21 | return this.debugEnabled; 22 | } 23 | 24 | info(message: string) { 25 | if (this.enabled) { 26 | console.log(message); 27 | } 28 | } 29 | 30 | debug(message: string, force?: boolean) { 31 | if (this.enabled && (this.debugEnabled || force === true)) { 32 | console.debug(message); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/build/src/buildAssets.mts: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from 'path'; 2 | import { fileURLToPath } from 'url'; 3 | import { readFile, writeFileSync } from 'fs'; 4 | 5 | /** 6 | * Solves: __dirname is not defined in ES module scope 7 | */ 8 | export function getLocalDirectory() { 9 | const __filename = fileURLToPath(import.meta.url); 10 | return dirname(__filename); 11 | } 12 | 13 | readFile(resolve(getLocalDirectory(), '../../bundle/assets/style.css'), 'utf8', (errCss, dataCss) => { 14 | if (errCss) { 15 | console.error(errCss); 16 | return; 17 | } 18 | let output = '// This file is auto-generated. Pleas do not change by hand.\n'; 19 | output = output + '// The file contains the styles generated by vite/rollup production bundle.\n'; 20 | output = output + `export const getMonacoCss = () => {\n return '${dataCss.trim()}';\n};`; 21 | writeFileSync(resolve(getLocalDirectory(), '../../styles/css.js'), output); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/examples/src/langium/worker/startLanguageServer.ts: -------------------------------------------------------------------------------- 1 | 2 | import { startLanguageServer, EmptyFileSystem } from 'langium'; 3 | import { BrowserMessageReader, BrowserMessageWriter, createConnection } from 'vscode-languageserver/browser.js'; 4 | import { createStatemachineServices } from 'langium-statemachine-dsl/out/language-server/statemachine-module.js'; 5 | 6 | export const start = (port: MessagePort | DedicatedWorkerGlobalScope, name: string) => { 7 | console.log(`Starting ${name}...`); 8 | /* browser specific setup code */ 9 | const messageReader = new BrowserMessageReader(port); 10 | const messageWriter = new BrowserMessageWriter(port); 11 | 12 | const connection = createConnection(messageReader, messageWriter); 13 | 14 | // Inject the shared services and language-specific services 15 | const { shared } = createStatemachineServices({ connection, ...EmptyFileSystem }); 16 | 17 | // Start the language server with the shared services 18 | startLanguageServer(shared); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/examples/vite.bundle-mew.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { defineConfig } from 'vite'; 3 | 4 | const config = defineConfig({ 5 | build: { 6 | lib: { 7 | entry: resolve(__dirname, './src/verifyPrepare.ts'), 8 | name: 'mew', 9 | fileName: () => 'mew.js', 10 | formats: ['es'] 11 | }, 12 | outDir: resolve(__dirname, 'libs/monaco-editor-wrapper'), 13 | assetsDir: resolve(__dirname, 'libs/monaco-editor-wrapper/assets'), 14 | emptyOutDir: true, 15 | cssCodeSplit: false, 16 | commonjsOptions: { 17 | strictRequires: true 18 | }, 19 | sourcemap: true, 20 | rollupOptions: { 21 | output: { 22 | name: 'mew', 23 | exports: 'named', 24 | assetFileNames: (assetInfo) => { 25 | return `assets/${assetInfo.name}`; 26 | } 27 | } 28 | } 29 | } 30 | }); 31 | 32 | export default config; 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2022 TypeFox GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /.github/workflows/actions.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | tags-ignore: 8 | - '**' 9 | pull_request: 10 | branches: 11 | - main 12 | 13 | jobs: 14 | build: 15 | name: monaco-components 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 10 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v3 21 | - name: Use Node.js 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: '20' 25 | - name: Use Chromedriver 26 | uses: nanasess/setup-chromedriver@v2 27 | - name: Install 28 | shell: bash 29 | run: | 30 | npm ci 31 | - name: Lint 32 | shell: bash 33 | run: | 34 | npm run lint 35 | - name: Build 36 | shell: bash 37 | run: | 38 | npm run build 39 | - name: Test 40 | shell: bash 41 | run: | 42 | export DISPLAY=:99 43 | chromedriver --url-base=/wd/hub & 44 | sudo Xvfb -ac :99 -screen 0 1600x1200x24 > /dev/null 2>&1 & 45 | npm run test 46 | -------------------------------------------------------------------------------- /packages/examples/src/langium/reactStatemachine.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { MonacoEditorReactComp } from '@typefox/monaco-editor-react'; 4 | import { createLangiumGlobalConfig } from './config/wrapperStatemachineConfig.js'; 5 | 6 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 7 | import { loadStatemachinWorkerRegular } from './wrapperStatemachine.js'; 8 | buildWorkerDefinition('../../../../node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); 9 | 10 | const startEditor = async () => { 11 | const langiumGlobalConfig = await createLangiumGlobalConfig(loadStatemachinWorkerRegular()); 12 | const comp = ; 19 | 20 | const root = ReactDOM.createRoot(document.getElementById('root')!); 21 | root.render(comp); 22 | }; 23 | 24 | try { 25 | startEditor(); 26 | } catch (e) { 27 | console.error(e); 28 | } 29 | -------------------------------------------------------------------------------- /packages/monaco-editor-react/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2022 TypeFox GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright 2022 TypeFox GmbH 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /packages/examples/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this npm module are documented in this file. 4 | 5 | ## [3.3.0] - 2023-10-17 6 | 7 | - Started to use version even if it is a private package. Aligned version with `monaco-editor-wrapper`. 8 | - Adjust to underlying api changes (`monaco-vscode-api` and `monaco-languageclient`) 9 | - Renamed `EditorAppVscodeApi` to `EditorAppExtended` and `EditorAppConfigVscodeApi` to `EditorAppConfigExtended` 10 | - BREAKING: `$type` of `EditorAppConfigExtended` was changed from `vscodeApi` to `extended` 11 | - Fix json language server launch 12 | - Move python language server port to 30001 and json language server port to 30000. 13 | - Include all direct dependencies that the code uses in the `package.json`. 14 | 15 | ## 2023-09-21 16 | 17 | - Langium example allows to use semantic highlighting with monarch grammars (monaco-editor classic mode) 18 | - React Typescript example allows to update the text 19 | - Aligned code to minor lib changes 20 | 21 | ## 2023-09-06 22 | 23 | - Moved langium grammer langugae client and server from [monaco-languageclient](https://github.com/TypeFox/monaco-languageclient) here 24 | - Allow to start the editor in both classic and vscode-api mode 25 | -------------------------------------------------------------------------------- /packages/examples/src/servers/langium-server.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2018-2022 TypeFox GmbH (http://www.typefox.io). All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 6 | import { createLangiumGrammarServices, startLanguageServer, EmptyFileSystem, DefaultSharedModuleContext } from 'langium'; 7 | import { BrowserMessageReader, BrowserMessageWriter, createConnection } from 'vscode-languageserver/browser.js'; 8 | 9 | /* browser specific setup code */ 10 | const messageReader = new BrowserMessageReader(self as DedicatedWorkerGlobalScope); 11 | const messageWriter = new BrowserMessageWriter(self as DedicatedWorkerGlobalScope); 12 | 13 | // Inject the shared services and language-specific services 14 | const context = { 15 | connection: createConnection(messageReader, messageWriter), 16 | ...EmptyFileSystem 17 | } as unknown as DefaultSharedModuleContext; 18 | const { shared } = createLangiumGrammarServices(context); 19 | 20 | // Start the language server with the shared services 21 | startLanguageServer(shared); 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch JSON LS", 9 | "type": "node", 10 | "request": "launch", 11 | "args": ["${workspaceRoot}/packages/examples/src/servers/json-server.ts"], 12 | "runtimeArgs": ["--nolazy", "--loader", "ts-node/esm"], 13 | "cwd": "${workspaceRoot}/packages/examples", 14 | "internalConsoleOptions": "openOnSessionStart" 15 | }, 16 | { 17 | "name": "Launch Python LS", 18 | "type": "node", 19 | "request": "launch", 20 | "args": ["${workspaceRoot}/packages/examples/src/servers/python-server.ts"], 21 | "runtimeArgs": ["--nolazy", "--loader", "ts-node/esm"], 22 | "cwd": "${workspaceRoot}/packages/examples", 23 | "internalConsoleOptions": "openOnSessionStart" 24 | }, 25 | { 26 | "name": "Chrome", 27 | "type": "chrome", 28 | "request": "launch", 29 | "url": "http://localhost:20001", 30 | "webRoot": "${workspaceFolder}" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "lib": [ 7 | "ES2022", 8 | "DOM", 9 | "DOM.Iterable", 10 | "WebWorker" 11 | ], 12 | "types": [ 13 | "vite/client", 14 | "node" 15 | ], 16 | "esModuleInterop": true, 17 | "resolveJsonModule": true, 18 | // Enable project compilation 19 | "composite": true, 20 | "declaration": true, 21 | "declarationMap": true, 22 | "sourceMap": true, 23 | "inlineSources": false, 24 | "stripInternal": true, 25 | "strict": true, 26 | "strictPropertyInitialization": false, 27 | "importHelpers": true, 28 | "downlevelIteration": false, 29 | "noImplicitReturns": true, 30 | "noUnusedParameters": true, 31 | "noUnusedLocals": true, 32 | // Skip type checking of declaration files. 33 | "skipLibCheck": true, 34 | // Disallow inconsistently-cased references to the same file 35 | "forceConsistentCasingInFileNames": true, 36 | "noImplicitOverride": true, 37 | "jsx": "react", 38 | "experimentalDecorators": true, 39 | "allowJs": true 40 | }, 41 | "include": [ 42 | "**/src/**/*.ts", 43 | "**/src/**/*.js", 44 | "**/test/**/*.ts", 45 | "**/src/**/*.tsx", 46 | "**/test/**/*.tsx", 47 | "packages/monaco-editor-wrapper/*.ts", 48 | "packages/monaco-editor-react/*.ts" 49 | ], 50 | "exclude": [ 51 | "**/node_modules/**/*", 52 | "**/dist/**/*", 53 | "**/lib/**/*" 54 | ] 55 | } 56 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/test/logger.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { Logger } from 'monaco-editor-wrapper'; 3 | 4 | describe('Logger', () => { 5 | 6 | test('Config: None', () => { 7 | const logger = new Logger(); 8 | 9 | expect(logger.isEnabled()).toBeTruthy(); 10 | expect(logger.isDebugEnabled()).toBeFalsy(); 11 | }); 12 | 13 | test('Config: disabled', () => { 14 | const logger = new Logger({ 15 | enabled: false 16 | }); 17 | 18 | expect(logger.isEnabled()).toBeFalsy(); 19 | expect(logger.isDebugEnabled()).toBeFalsy(); 20 | }); 21 | 22 | test('Config: disabled, debug enabled', () => { 23 | const logger = new Logger({ 24 | enabled: false, 25 | debugEnabled: true 26 | }); 27 | 28 | expect(logger.isEnabled()).toBeFalsy(); 29 | expect(logger.isDebugEnabled()).toBeFalsy(); 30 | }); 31 | 32 | test('Config: enabled, debug disabled', () => { 33 | const logger = new Logger({ 34 | enabled: true, 35 | debugEnabled: false 36 | }); 37 | 38 | expect(logger.isEnabled()).toBeTruthy(); 39 | expect(logger.isDebugEnabled()).toBeFalsy(); 40 | }); 41 | 42 | test('Config: enabled, debug enabled', () => { 43 | const logger = new Logger({ 44 | enabled: true, 45 | debugEnabled: true 46 | }); 47 | 48 | expect(logger.isEnabled()).toBeTruthy(); 49 | expect(logger.isDebugEnabled()).toBeTruthy(); 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { WebSocketConfigOptions, WebSocketConfigOptionsUrl } from './languageClientWrapper.js'; 2 | 3 | export const createUrl = (config: WebSocketConfigOptions | WebSocketConfigOptionsUrl) => { 4 | let buildUrl = ''; 5 | if ((config as WebSocketConfigOptionsUrl).url) { 6 | const options = config as WebSocketConfigOptionsUrl; 7 | if (!options.url.startsWith('ws://') && !options.url.startsWith('wss://')) { 8 | throw new Error(`This is not a proper websocket url: ${options.url}`); 9 | } 10 | buildUrl = options.url; 11 | } else { 12 | const options = config as WebSocketConfigOptions; 13 | const protocol = options.secured ? 'wss' : 'ws'; 14 | buildUrl = `${protocol}://${options.host}`; 15 | if (options.port) { 16 | if (options.port !== 80) { 17 | buildUrl += `:${options.port}`; 18 | } 19 | } 20 | if (options.path) { 21 | buildUrl += `/${options.path}`; 22 | } 23 | if (options.extraParams){ 24 | const url = new URL(buildUrl); 25 | 26 | for (const [key, value] of Object.entries(options.extraParams)) { 27 | if (value) { 28 | url.searchParams.set(key, value instanceof Array ? value.join(',') : value.toString()); 29 | } 30 | } 31 | 32 | buildUrl = url.toString(); 33 | } 34 | } 35 | return buildUrl; 36 | }; 37 | 38 | export const verifyUrlorCreateDataUrl = (input: string | URL) => { 39 | return (input instanceof URL) ? input.href : new URL(`data:text/plain;base64,${btoa(input)}`).href; 40 | }; 41 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/test/helper.ts: -------------------------------------------------------------------------------- 1 | import { UserConfig, EditorAppType, EditorAppExtended, MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; 2 | 3 | export const createMonacoEditorDiv = () => { 4 | const div = document.createElement('div'); 5 | div.id = 'monaco-editor-root'; 6 | document.body.insertAdjacentElement('beforeend', div); 7 | }; 8 | 9 | export const createBaseConfig = (type: EditorAppType): UserConfig => { 10 | return { 11 | wrapperConfig: createWrapperConfig(type) 12 | }; 13 | }; 14 | 15 | export const createWrapperConfig = (type: EditorAppType) => { 16 | return { 17 | editorAppConfig: createEditorAppConfig(type) 18 | }; 19 | }; 20 | 21 | export const createEditorAppConfig = (type: EditorAppType) => { 22 | return { 23 | $type: type, 24 | languageId: 'my-lang', 25 | code: '', 26 | useDiffEditor: false, 27 | }; 28 | }; 29 | 30 | export const updateExtendedAppPrototyp = () => { 31 | EditorAppExtended.prototype.specifyServices = () => { 32 | console.log('Using overriden EditorAppExtended.prototype.specifyServices'); 33 | return {}; 34 | }; 35 | }; 36 | 37 | /** 38 | * WA: Create an instance for testing that does not specify any additional services. 39 | * 40 | * Prevents: 41 | * Error: Unable to load extension-file://vscode.theme-defaults/themes/light_modern.json: 42 | * Unable to read file 'extension-file://vscode.theme-defaults/themes/light_modern.json' (TypeError: Failed to fetch) 43 | */ 44 | export const createWrapper = async (userConfig: UserConfig) => { 45 | updateExtendedAppPrototyp(); 46 | const wrapper = new MonacoEditorLanguageClientWrapper(); 47 | await wrapper.init(userConfig); 48 | return wrapper; 49 | }; 50 | -------------------------------------------------------------------------------- /packages/examples/build/buildWorker.mts: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from 'path'; 2 | import { fileURLToPath } from 'url'; 3 | import { build, Format } from 'esbuild'; 4 | 5 | export const getLocalDirectory = () => { 6 | const __filename = fileURLToPath(import.meta.url); 7 | return dirname(__filename); 8 | }; 9 | 10 | export const bundleWorker = async (format: Format, entryFile: string, outfile: string, workingDir?: string) => { 11 | await build({ 12 | entryPoints: [entryFile], 13 | bundle: true, 14 | treeShaking: true, 15 | minify: true, 16 | format: format, 17 | allowOverwrite: true, 18 | absWorkingDir: workingDir ?? resolve(getLocalDirectory(), '..'), 19 | logLevel: 'info', 20 | outfile: outfile 21 | }); 22 | }; 23 | 24 | const scriptExec = process.argv[2] as string | undefined; 25 | if (scriptExec === '--script') { 26 | console.log('Running in script mode.'); 27 | 28 | const format = process.argv[3] as Format | undefined; 29 | const entry = process.argv[4] as string | undefined; 30 | const outfile = process.argv[5] as string | undefined; 31 | const workingDir = process.argv[6] as string | undefined; 32 | console.log('Bundling worker...'); 33 | console.log(`Using scriptExec: ${scriptExec}`); 34 | console.log(`Using format: ${format}`); 35 | console.log(`Using entry: ${entry}`); 36 | console.log(`Using outFile: ${outfile}`); 37 | 38 | if (workingDir) { 39 | console.log(`Using working dir: ${workingDir}`); 40 | } 41 | 42 | if (format && entry && outfile) { 43 | bundleWorker(format, entry, outfile, workingDir); 44 | } else { 45 | console.error('Please provide format, entry and outfile.'); 46 | } 47 | } else { 48 | console.log('Running in non-script mode.'); 49 | } 50 | -------------------------------------------------------------------------------- /packages/examples/verify_wrapper.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Verify: Monaco Editor Wrapper: Diff 8 | 11 | 12 | 13 | 14 |
15 | ; 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/test/editorAppBase.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { isModelUpdateRequired, EditorAppClassic, ModelUpdateType } from 'monaco-editor-wrapper'; 3 | import { createBaseConfig, createEditorAppConfig, createWrapperConfig } from './helper.js'; 4 | 5 | describe('Test EditorAppBase', () => { 6 | 7 | test('classic type: empty EditorAppConfigClassic', () => { 8 | const wrapperConfig = createWrapperConfig('classic'); 9 | expect(wrapperConfig.editorAppConfig.$type).toBe('classic'); 10 | }); 11 | 12 | test('extended type: empty EditorAppConfigExtended', () => { 13 | const wrapperConfig = createWrapperConfig('extended'); 14 | expect(wrapperConfig.editorAppConfig.$type).toBe('extended'); 15 | }); 16 | 17 | test('config defaults', () => { 18 | const config = createBaseConfig('classic'); 19 | const app = new EditorAppClassic('config defaults', config); 20 | expect(app.getConfig().languageId).toEqual('my-lang'); 21 | expect(app.getConfig().code).toEqual(''); 22 | expect(app.getConfig().codeOriginal).toEqual(''); 23 | expect(app.getConfig().useDiffEditor).toBeFalsy(); 24 | expect(app.getConfig().codeUri).toBeUndefined(); 25 | expect(app.getConfig().codeOriginalUri).toBeUndefined(); 26 | expect(app.getConfig().readOnly).toBeFalsy(); 27 | expect(app.getConfig().domReadOnly).toBeFalsy(); 28 | }); 29 | 30 | test('isModelUpdateRequired', () => { 31 | const config = createEditorAppConfig('classic'); 32 | let modelUpdateType = isModelUpdateRequired(config, { languageId: 'my-lang', code: '' }); 33 | expect(modelUpdateType).toBe(ModelUpdateType.NONE); 34 | 35 | modelUpdateType = isModelUpdateRequired(config, { languageId: 'my-lang' }); 36 | expect(modelUpdateType).toBe(ModelUpdateType.NONE); 37 | 38 | modelUpdateType = isModelUpdateRequired(config, { languageId: 'my-lang', code: 'test' }); 39 | expect(modelUpdateType).toBe(ModelUpdateType.CODE); 40 | 41 | modelUpdateType = isModelUpdateRequired(config, { languageId: 'javascript', code: 'test' }); 42 | expect(modelUpdateType).toBe(ModelUpdateType.MODEL); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Monaco Editor Wrapper and Monaco Editor React Component 2 | 3 | **These projects have been migrated to [monaco-languageclient repo](https://github.com/TypeFox/monaco-languageclient)** 4 | 5 | This repository started as a Lit component for Monaco Editor, but it has transformed into a wrapper for `monaco-editor`, `monaco-languageclient` and `@codingame/monaco-vscode-api` and now features a react component (`@typefox/monaco-editor-react`) that encapsulates the `monaco-editor-wrapper`. 6 | 7 | ## Packages 8 | 9 | There are two npm packages generated from this repository: 10 | 11 | - [Monaco Editor Wrapper](./packages/monaco-editor-wrapper/README.md) + Language Client: Wrapped [monaco-editor](https://github.com/microsoft/monaco-editor) with the capability to plug-in [monaco-languageclient](https://github.com/TypeFox/monaco-languageclient) to connect to languages servers locally running (web worker) or remotely running (web socket). 12 | - [Monaco Editor React Component](./packages/monaco-editor-react/README.md): Monaco Editor React Component enclosing the **Monaco Editor Wrapper** 13 | 14 | Additionally you can find a private [examples packages](./packages/examples/) containing all examples that are served by Vite (see next chapter). 15 | 16 | **Important**: Monaco Editor Workers has been moved to its own [repo](https://github.com/TypeFox/monaco-editor-workers). 17 | 18 | ## Getting Started 19 | 20 | We recommend using [Volta](https://volta.sh/) to ensure your node & npm are on known good versions. 21 | 22 | If you have node.js LTS available, then from the root of the project run: 23 | 24 | ```bash 25 | npm i 26 | npm run build 27 | ``` 28 | 29 | If you get an error with `npm i` regarding tree-mending, you can run `npm ci` to clean things up from previous installations and continue. 30 | 31 | Afterwards, launch the Vite development server: 32 | 33 | ```bash 34 | npm run dev 35 | ``` 36 | 37 | If you want to change dependent code in the examples (`monaco-editor-wrapper` and `@typefox/monaco-editor-react`), you have to watch code changes in parallel: 38 | 39 | ```bash 40 | npm run watch 41 | ``` 42 | 43 | You can find examples (manual human testing) here [index.html](./index.html). They can be used once Vite is running. You can reach it once started on . 44 | -------------------------------------------------------------------------------- /packages/examples/src/reactTs.tsx: -------------------------------------------------------------------------------- 1 | import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; 2 | import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution.js'; 3 | import 'monaco-editor/esm/vs/language/typescript/monaco.contribution.js'; 4 | import ReactDOM from 'react-dom/client'; 5 | import React, { useState } from 'react'; 6 | import { MonacoEditorReactComp } from '@typefox/monaco-editor-react'; 7 | import { UserConfig } from 'monaco-editor-wrapper'; 8 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 9 | 10 | buildWorkerDefinition('../../../../node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); 11 | 12 | const rootElem = document.getElementById('root')!; 13 | const EditorDemo: React.FC = () => { 14 | const logMessage = 'console.log(\'hello\')'; 15 | const [content, setContent] = useState(logMessage); 16 | 17 | const userConfig: UserConfig = { 18 | wrapperConfig: { 19 | serviceConfig: { 20 | userServices: { 21 | ...getKeybindingsServiceOverride() 22 | }, 23 | debugLogging: true 24 | }, 25 | editorAppConfig: { 26 | $type: 'classic', 27 | languageId: 'typescript', 28 | useDiffEditor: false, 29 | theme: 'vs-dark', 30 | code: content 31 | } 32 | } 33 | }; 34 | 35 | const addConsoleMessage = () => { 36 | setContent(`${content}\n${logMessage}`); 37 | }; 38 | 39 | const onTextChanged = (text: string, isDirty: boolean) => { 40 | console.log(`Dirty? ${isDirty} Content: ${text}`); 41 | }; 42 | 43 | return ( 44 | <> 45 | 48 | 56 | 57 | 58 | ); 59 | }; 60 | 61 | const comp = ; 62 | const root = ReactDOM.createRoot(rootElem); 63 | root.render(comp); 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monaco-components", 3 | "type": "module", 4 | "private": true, 5 | "scripts": { 6 | "clean": "npm run clean --workspaces", 7 | "clean:watch": "tsc --build tsconfig.build.json --clean", 8 | "dev": "node --experimental-import-meta-resolve ./node_modules/vite/bin/vite.js", 9 | "dev:debug": "node --experimental-import-meta-resolve ./node_modules/vite/bin/vite.js --debug --force", 10 | "watch": "tsc --build tsconfig.build.json --watch --verbose", 11 | "lint": "eslint {**/src/**/*.ts,**/src/**/*.tsx,**/test/**/*.ts,**/test/**/*.tsx}", 12 | "build": "npm run build --workspaces", 13 | "test": "node --experimental-import-meta-resolve ./node_modules/vitest/vitest.mjs -c vite.config.ts", 14 | "test:run": "node --experimental-import-meta-resolve ./node_modules/vitest/vitest.mjs -c vite.config.ts --run", 15 | "release:prepare": "npm run reset:repo && npm ci && npm run lint && npm run build && npm run test:run", 16 | "reset:repo": "git clean -f -X -d", 17 | "start:http": "npm run start:http --workspace packages/examples", 18 | "start:example:server:json": "npm run start:server:json --workspace packages/examples", 19 | "start:example:server:python": "npm run start:server:python --workspace packages/examples" 20 | }, 21 | "devDependencies": { 22 | "@types/node": "~20.10.6", 23 | "@types/shelljs": "~0.8.15", 24 | "@types/vscode": "~1.85.0", 25 | "@typescript-eslint/eslint-plugin": "~6.17.0", 26 | "@typescript-eslint/parser": "~6.17.0", 27 | "@vitest/browser": "~1.1.1", 28 | "editorconfig": "~2.0.0", 29 | "eslint": "~8.56.0", 30 | "shelljs": "~0.8.5", 31 | "shx": "~0.3.4", 32 | "typescript": "~5.3.3", 33 | "vite": "~5.0.10", 34 | "vitest": "~1.1.1", 35 | "webdriverio": "~8.27.0" 36 | }, 37 | "overrides": { 38 | "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.85.0 <1.86.0", 39 | "vscode": "npm:@codingame/monaco-vscode-api@>=1.85.0 <1.86.0" 40 | }, 41 | "resolutions": { 42 | "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.85.0 <1.86.0", 43 | "vscode": "npm:@codingame/monaco-vscode-api@>=1.85.0 <1.86.0" 44 | }, 45 | "volta": { 46 | "node": "20.10.0", 47 | "npm": "10.2.3" 48 | }, 49 | "workspaces": [ 50 | "packages/monaco-editor-wrapper", 51 | "packages/monaco-editor-react", 52 | "packages/examples" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/test/editorAppExtended.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { EditorAppConfigExtended, EditorAppExtended, verifyUrlorCreateDataUrl } from 'monaco-editor-wrapper'; 3 | import { createBaseConfig, createEditorAppConfig } from './helper.js'; 4 | 5 | describe('Test EditorAppExtended', () => { 6 | 7 | test('verifyUrlorCreateDataUrl: url', () => { 8 | const url = new URL('./editorAppExtended.test.ts', import.meta.url); 9 | expect(verifyUrlorCreateDataUrl(url)).toBe(url.href); 10 | }); 11 | 12 | test('verifyUrlorCreateDataUrl: url', async () => { 13 | const url = new URL('../../../node_modules/langium-statemachine-dsl/syntaxes/statemachine.tmLanguage.json', window.location.href); 14 | const text = await (await fetch(url)).text(); 15 | expect(verifyUrlorCreateDataUrl(text)).toBe(`data:text/plain;base64,${btoa(text)}`); 16 | }); 17 | 18 | test('config userConfiguration', () => { 19 | const config = createBaseConfig('extended'); 20 | const appConfig = config.wrapperConfig.editorAppConfig as EditorAppConfigExtended; 21 | appConfig.userConfiguration = { 22 | json: '{ "editor.semanticHighlighting.enabled": true }' 23 | }; 24 | const app = new EditorAppExtended('config defaults', config); 25 | expect(app.getConfig().userConfiguration?.json).toEqual('{ "editor.semanticHighlighting.enabled": true }'); 26 | }); 27 | 28 | test('isAppConfigDifferent: basic', () => { 29 | const orgConfig = createEditorAppConfig('extended') as EditorAppConfigExtended; 30 | const config = createEditorAppConfig('extended') as EditorAppConfigExtended; 31 | const app = new EditorAppExtended('test', createBaseConfig('extended')); 32 | expect(app.isAppConfigDifferent(orgConfig, config, false)).toBeFalsy(); 33 | 34 | config.code = 'test'; 35 | expect(app.isAppConfigDifferent(orgConfig, config, true)).toBeTruthy(); 36 | 37 | config.code = ''; 38 | config.extensions = [{ 39 | config: { 40 | name: 'Tester', 41 | publisher: 'Tester', 42 | version: '1.0.0', 43 | engines: { 44 | vscode: '*' 45 | } 46 | } 47 | }]; 48 | expect(app.isAppConfigDifferent(orgConfig, config, false)).toBeTruthy(); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | monaco-editor components 8 | 13 | 14 | 15 | 16 |
17 | 18 |

Package Testing

19 | 20 | Please run npm run build if you haven't done it already. 21 | 22 |

Monaco Editor Wrapper

23 | 24 | TypeScript Editor Worker 25 |
26 | Multiple Editors 27 |
28 | Langium Statemachine Client & Language Server (Worker) 29 |
30 | Langium Grammar DSL Language Client & Language Server (Worker) 31 |

32 | Please execute npm run start:example:server:json beforehand:
33 | Language Client & Web Socket Language Server example 34 |
35 | 36 |

Monaco Editor React

37 | Langium Statemachine Client & Language Server (Worker) 38 |
39 | TypeScript Editor Worker 40 |
41 |
42 | Please execute npm run start:example:server:python beforehand:
43 | Python Language Client & Language Server 44 |
45 | 46 |

Verification

47 | Here the bundles are used instead of the direct sources. 48 | 49 |

Monaco Editor Wrapper Bundle Verification

50 | For the "Static HTTP Server Alternative" please start npm run start:http beforehand.
51 | 52 | JavaScript Diff 53 | (Static HTTP Server Alternative) 54 |
55 | JavaScript Diff (direct sources) 56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /packages/monaco-editor-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@typefox/monaco-editor-react", 3 | "version": "2.6.0", 4 | "license": "MIT", 5 | "description": "React component for Monaco-Editor and Monaco Languageclient", 6 | "keywords": [ 7 | "monaco-editor", 8 | "monaco-languageclient", 9 | "typescript", 10 | "react" 11 | ], 12 | "type": "module", 13 | "main": "./dist/index.js", 14 | "module": "./dist/index.js", 15 | "exports": { 16 | ".": { 17 | "types": "./dist/index.d.ts", 18 | "default": "./dist/index.js" 19 | }, 20 | "./bundle": { 21 | "types": "./dist/index.d.ts", 22 | "default": "./bundle/index.js" 23 | } 24 | }, 25 | "typesVersions": { 26 | "*": { 27 | ".": [ 28 | "dist/index" 29 | ], 30 | "bundle": [ 31 | "dist/index" 32 | ] 33 | } 34 | }, 35 | "files": [ 36 | "dist", 37 | "bundle", 38 | "src", 39 | "LICENSE", 40 | "README.md" 41 | ], 42 | "scripts": { 43 | "clean": "shx rm -rf ./dist ./bundle *.tsbuildinfo", 44 | "compile": " tsc --build tsconfig.src.json", 45 | "build:bundle": "vite --config vite.bundle.config.ts build", 46 | "build": "npm run clean && npm run compile && npm run build:bundle" 47 | }, 48 | "volta": { 49 | "node": "20.10.0", 50 | "npm": "10.2.3" 51 | }, 52 | "dependencies": { 53 | "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.85.0 <1.86.0", 54 | "monaco-editor-wrapper": "~3.6.0", 55 | "react": "~18.2.0", 56 | "vscode": "npm:@codingame/monaco-vscode-api@>=1.85.0 <1.86.0" 57 | }, 58 | "peerDependencies": { 59 | "monaco-editor-wrapper": "~3.6.0" 60 | }, 61 | "devDependencies": { 62 | "@types/react": "~18.2.46" 63 | }, 64 | "overrides": { 65 | "monaco-editor": "$monaco-editor", 66 | "vscode": "$vscode" 67 | }, 68 | "resolutions": { 69 | "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.85.0 <1.86.0", 70 | "vscode": "npm:@codingame/monaco-vscode-api@>=1.85.0 <1.86.0" 71 | }, 72 | "repository": { 73 | "type": "git", 74 | "url": "git+https://github.com/TypeFox/monaco-components.git", 75 | "directory": "packages/monaco-editor-react" 76 | }, 77 | "homepage": "https://github.com/TypeFox/monaco-components/blob/main/packages/monaco-editor-react/README.md", 78 | "bugs": "https://github.com/TypeFox/monaco-components/issues", 79 | "author": { 80 | "name": "TypeFox", 81 | "url": "https://www.typefox.io" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /packages/examples/src/langium/config/wrapperLangiumClassic.ts: -------------------------------------------------------------------------------- 1 | import { Uri } from 'vscode'; 2 | import getConfigurationServiceOverride from '@codingame/monaco-vscode-configuration-service-override'; 3 | import getEditorServiceOverride from '@codingame/monaco-vscode-editor-service-override'; 4 | import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; 5 | import { useOpenEditorStub } from 'monaco-languageclient'; 6 | import { UserConfig } from 'monaco-editor-wrapper'; 7 | import { getTextContent } from '../../common.js'; 8 | import { LangiumMonarchContent } from './langium.monarch.js'; 9 | import { loadLangiumWorker } from '../wrapperLangium.js'; 10 | 11 | export const setupLangiumClientClassic = async (): Promise => { 12 | const code = await getTextContent(new URL('./src/langium/content/example.langium', window.location.href)); 13 | 14 | const langiumWorker = loadLangiumWorker(); 15 | return { 16 | loggerConfig: { 17 | enabled: true, 18 | debugEnabled: true 19 | }, 20 | wrapperConfig: { 21 | serviceConfig: { 22 | userServices: { 23 | ...getConfigurationServiceOverride(), 24 | ...getEditorServiceOverride(useOpenEditorStub), 25 | ...getKeybindingsServiceOverride() 26 | }, 27 | debugLogging: true, 28 | workspaceConfig: { 29 | workspaceProvider: { 30 | trusted: true, 31 | workspace: { 32 | workspaceUri: Uri.file('/workspace') 33 | }, 34 | async open() { 35 | return false; 36 | } 37 | } 38 | } 39 | }, 40 | editorAppConfig: { 41 | $type: 'classic', 42 | languageId: 'langium', 43 | code: code, 44 | useDiffEditor: false, 45 | theme: 'vs-dark', 46 | editorOptions: { 47 | 'semanticHighlighting.enabled': true 48 | }, 49 | languageExtensionConfig: { id: 'langium' }, 50 | languageDef: LangiumMonarchContent, 51 | } 52 | }, 53 | languageClientConfig: { 54 | options: { 55 | $type: 'WorkerDirect', 56 | worker: langiumWorker 57 | } 58 | } 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /packages/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monaco-editor-wrapper-examples", 3 | "private": true, 4 | "version": "3.6.0", 5 | "type": "module", 6 | "dependencies": { 7 | "@codingame/monaco-vscode-configuration-service-override": "~1.85.0", 8 | "@codingame/monaco-vscode-editor-service-override": "~1.85.0", 9 | "@codingame/monaco-vscode-json-default-extension": "~1.85.0", 10 | "@codingame/monaco-vscode-keybindings-service-override": "~1.85.0", 11 | "@codingame/monaco-vscode-python-default-extension": "~1.85.0", 12 | "@typefox/monaco-editor-react": "~2.6.0", 13 | "http-server": "~14.1.1", 14 | "langium": "~2.1.3", 15 | "langium-statemachine-dsl": "~2.1.0", 16 | "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.85.0 <1.86.0", 17 | "monaco-editor-workers": "~0.45.0", 18 | "monaco-editor-wrapper": "~3.6.0", 19 | "monaco-languageclient": "~7.3.0", 20 | "monaco-languageclient-examples": "~7.3.0", 21 | "react": "~18.2.0", 22 | "react-dom": "~18.2.0", 23 | "request-light": "~0.7.0", 24 | "vscode": "npm:@codingame/monaco-vscode-api@>=1.85.0 <1.86.0", 25 | "vscode-uri": "~3.0.8", 26 | "vscode-languageclient": "~9.0.1", 27 | "vscode-languageserver": "~9.0.1", 28 | "vscode-json-languageservice": "~5.3.7" 29 | }, 30 | "devDependencies": { 31 | "@types/react": "~18.2.46", 32 | "@types/react-dom": "~18.2.18", 33 | "ts-node": "~10.9.1" 34 | }, 35 | "overrides": { 36 | "monaco-editor": "$monaco-editor", 37 | "vscode": "$vscode" 38 | }, 39 | "resolutions": { 40 | "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.85.0 <1.86.0", 41 | "vscode": "npm:@codingame/monaco-vscode-api@>=1.85.0 <1.86.0" 42 | }, 43 | "volta": { 44 | "node": "20.10.0", 45 | "npm": "10.2.3" 46 | }, 47 | "scripts": { 48 | "clean": "shx rm -rf ./dist ./libs *.tsbuildinfo", 49 | "compile": "tsc --build tsconfig.src.json", 50 | "copy:prepare": "shx mkdir -p ./libs", 51 | "copy:workers": "shx cp -fr ../../node_modules/monaco-editor-workers/dist ./libs/monaco-editor-workers", 52 | "copy:all": "npm run copy:prepare && npm run copy:workers", 53 | "build:bundle:wrapper": "vite --config vite.bundle-mew.ts build", 54 | "build": "npm run clean && npm run compile && npm run build:bundle:wrapper && npm run copy:all", 55 | "start:http": "http-server --port 20002 ./", 56 | "start:server:json": "node --loader ts-node/esm ./src/servers/json-server.ts", 57 | "start:server:python": "node --loader ts-node/esm ./src/servers/python-server.ts" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/examples/src/langium/config/langium.monarch.ts: -------------------------------------------------------------------------------- 1 | export const LangiumMonarchContent = { 2 | keywords: [ 3 | 'bigint', 4 | 'boolean', 5 | 'current', 6 | 'Date', 7 | 'entry', 8 | 'extends', 9 | 'false', 10 | 'fragment', 11 | 'grammar', 12 | 'hidden', 13 | 'import', 14 | 'infer', 15 | 'infers', 16 | 'interface', 17 | 'number', 18 | 'returns', 19 | 'string', 20 | 'terminal', 21 | 'true', 22 | 'type', 23 | 'with', 24 | ], 25 | operators: [ 26 | '->', 27 | ',', 28 | ';', 29 | ':', 30 | '!', 31 | '?', 32 | '?=', 33 | '.', 34 | '..', 35 | '@', 36 | '*', 37 | '&', 38 | '+', 39 | '+=', 40 | '<', 41 | '=', 42 | '=>', 43 | '>', 44 | '|', 45 | ], 46 | symbols: 47 | /->|,|;|:|!|\?|\?=|\.|\.\.|\(|\)|\[|\[\]|\]|\{|\}|@|\*|&|\+|\+=|<|=|=>|>|\|/, 48 | 49 | tokenizer: { 50 | initial: [ 51 | { 52 | regex: /\/(?![*+?])(?:[^\r\n[/\\]|\\.|\[(?:[^\r\n\]\\]|\\.)*\])+\//, 53 | action: { token: 'string' }, 54 | }, 55 | { 56 | regex: /\^?[_a-zA-Z][\w_]*/, 57 | action: { 58 | cases: { 59 | '@keywords': { token: 'keyword' }, 60 | '@default': { token: 'ID' }, 61 | }, 62 | }, 63 | }, 64 | { regex: /"[^"]*"|'[^']*'/, action: { token: 'string' } }, 65 | { include: '@whitespace' }, 66 | { 67 | regex: /@symbols/, 68 | action: { 69 | cases: { 70 | '@operators': { token: 'operator' }, 71 | '@default': { token: '' }, 72 | }, 73 | }, 74 | }, 75 | ], 76 | whitespace: [ 77 | { regex: /\s+/, action: { token: 'white' } }, 78 | { regex: /\/\*/, action: { token: 'comment', next: '@comment' } }, 79 | { regex: /\/\/[^\n\r]*/, action: { token: 'comment' } }, 80 | ], 81 | comment: [ 82 | { regex: /[^/*]+/, action: { token: 'comment' } }, 83 | { regex: /\*\//, action: { token: 'comment', next: '@pop' } }, 84 | { regex: /[/*]/, action: { token: 'comment' } }, 85 | ], 86 | }, 87 | }; 88 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EditorAppBase, 3 | isCodeUpdateRequired, 4 | isModelUpdateRequired, 5 | isEqual, 6 | ModelUpdateType 7 | } from './editorAppBase.js'; 8 | 9 | import type { 10 | EditorAppConfigBase, 11 | EditorAppType, 12 | ModelUpdate 13 | } from './editorAppBase.js'; 14 | 15 | import type { 16 | EditorAppConfigClassic, 17 | } from './editorAppClassic.js'; 18 | 19 | import { 20 | EditorAppClassic 21 | } from './editorAppClassic.js'; 22 | 23 | import type { 24 | ExtensionConfig, 25 | EditorAppConfigExtended, 26 | RegisterExtensionResult, 27 | RegisterLocalProcessExtensionResult, 28 | UserConfiguration 29 | } from './editorAppExtended.js'; 30 | 31 | import { 32 | EditorAppExtended 33 | } from './editorAppExtended.js'; 34 | 35 | import type { 36 | WebSocketCallOptions, 37 | LanguageClientConfigType, 38 | WebSocketConfigOptions, 39 | WebSocketConfigOptionsUrl, 40 | WorkerConfigOptions, 41 | WorkerConfigDirect, 42 | LanguageClientConfig, 43 | LanguageClientError 44 | } from './languageClientWrapper.js'; 45 | 46 | import { 47 | LanguageClientWrapper, 48 | } from './languageClientWrapper.js'; 49 | 50 | import type { 51 | UserConfig, 52 | WrapperConfig 53 | } from './wrapper.js'; 54 | 55 | import { 56 | MonacoEditorLanguageClientWrapper, 57 | } from './wrapper.js'; 58 | 59 | import type { 60 | LoggerConfig 61 | } from './logger.js'; 62 | 63 | import { 64 | Logger 65 | } from './logger.js'; 66 | 67 | export type { 68 | WrapperConfig, 69 | EditorAppConfigBase, 70 | EditorAppType, 71 | EditorAppConfigClassic, 72 | ExtensionConfig, 73 | EditorAppConfigExtended, 74 | RegisterExtensionResult, 75 | RegisterLocalProcessExtensionResult, 76 | UserConfiguration, 77 | WebSocketCallOptions, 78 | LanguageClientConfigType, 79 | WebSocketConfigOptions, 80 | WebSocketConfigOptionsUrl, 81 | WorkerConfigOptions, 82 | WorkerConfigDirect, 83 | LanguageClientConfig, 84 | LanguageClientError, 85 | UserConfig, 86 | ModelUpdate, 87 | LoggerConfig 88 | }; 89 | 90 | export { 91 | MonacoEditorLanguageClientWrapper, 92 | LanguageClientWrapper, 93 | EditorAppBase, 94 | isCodeUpdateRequired, 95 | isModelUpdateRequired, 96 | isEqual, 97 | ModelUpdateType, 98 | EditorAppClassic, 99 | EditorAppExtended, 100 | Logger 101 | }; 102 | 103 | export * from './utils.js'; 104 | -------------------------------------------------------------------------------- /packages/examples/verify_alt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Verify: Monaco Editor Wrapper: Diff 8 | 11 | 12 | 13 | 14 |
15 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/test/wrapper.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { EditorAppClassic, MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; 3 | import { createBaseConfig, createMonacoEditorDiv } from './helper.js'; 4 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 5 | 6 | buildWorkerDefinition('../../../node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); 7 | 8 | describe('Test MonacoEditorLanguageClientWrapper', () => { 9 | 10 | test('New wrapper has undefined editor', () => { 11 | const wrapper = new MonacoEditorLanguageClientWrapper(); 12 | expect(wrapper.getEditor()).toBeUndefined(); 13 | }); 14 | 15 | test('New wrapper has undefined diff editor', () => { 16 | const wrapper = new MonacoEditorLanguageClientWrapper(); 17 | expect(wrapper.getDiffEditor()).toBeUndefined(); 18 | }); 19 | 20 | test('Check default values', async () => { 21 | createMonacoEditorDiv(); 22 | const wrapper = new MonacoEditorLanguageClientWrapper(); 23 | await wrapper.initAndStart(createBaseConfig('classic'), document.getElementById('monaco-editor-root')); 24 | 25 | const app = wrapper.getMonacoEditorApp() as EditorAppClassic; 26 | expect(app).toBeDefined(); 27 | 28 | const appConfig = app.getConfig(); 29 | expect(appConfig.overrideAutomaticLayout).toBeTruthy(); 30 | expect(appConfig.theme).toBe('vs-light'); 31 | }); 32 | 33 | test('No HTML in Userconfig', async () => { 34 | createMonacoEditorDiv(); 35 | const wrapper = new MonacoEditorLanguageClientWrapper(); 36 | await expect(async () => { 37 | await wrapper.initAndStart(createBaseConfig('classic'), null); 38 | }).rejects.toThrowError('No HTMLElement provided for monaco-editor.'); 39 | }); 40 | 41 | test('Expected throw: Start without init', async () => { 42 | createMonacoEditorDiv(); 43 | const wrapper = new MonacoEditorLanguageClientWrapper(); 44 | await expect(async () => { 45 | await wrapper.start(document.getElementById('monaco-editor-root')); 46 | }).rejects.toThrowError('No init was performed. Please call init() before start()'); 47 | }); 48 | 49 | test('Expected throw: Call normal start with prior init', async () => { 50 | createMonacoEditorDiv(); 51 | const wrapper = new MonacoEditorLanguageClientWrapper(); 52 | await expect(async () => { 53 | const config = createBaseConfig('classic'); 54 | await wrapper.init(config); 55 | await wrapper.initAndStart(config, document.getElementById('monaco-editor-root')); 56 | }).rejects.toThrowError('init was already performed. Please call dispose first if you want to re-start.'); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /packages/examples/src/wrapperWs.ts: -------------------------------------------------------------------------------- 1 | import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; 2 | import { whenReady as whenReadyJson } from '@codingame/monaco-vscode-json-default-extension'; 3 | import { disposeEditor, startEditor, swapEditors } from './common.js'; 4 | import { UserConfig } from 'monaco-editor-wrapper'; 5 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 6 | 7 | buildWorkerDefinition('../../../node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); 8 | 9 | const languageId = 'json'; 10 | let codeMain = `{ 11 | "$schema": "http://json.schemastore.org/coffeelint", 12 | "line_endings": {"value": "windows"} 13 | }`; 14 | const codeOrg = `{ 15 | "$schema": "http://json.schemastore.org/coffeelint", 16 | "line_endings": {"value": "unix"} 17 | }`; 18 | 19 | const userConfig: UserConfig = { 20 | wrapperConfig: { 21 | serviceConfig: { 22 | userServices: { 23 | ...getKeybindingsServiceOverride(), 24 | }, 25 | debugLogging: true 26 | }, 27 | editorAppConfig: { 28 | $type: 'extended', 29 | languageId: languageId, 30 | code: codeMain, 31 | useDiffEditor: false, 32 | codeOriginal: codeOrg, 33 | // Ensure all required extensions are loaded before setting up the language extension 34 | awaitExtensionReadiness: [whenReadyJson], 35 | userConfiguration: { 36 | json: JSON.stringify({ 37 | 'workbench.colorTheme': 'Default Dark Modern', 38 | 'editor.guides.bracketPairsHorizontal': 'active', 39 | 'editor.lightbulb.enabled': true 40 | }) 41 | } 42 | } 43 | }, 44 | languageClientConfig: { 45 | options: { 46 | $type: 'WebSocketUrl', 47 | url: 'ws://localhost:30000/sampleServer', 48 | startOptions: { 49 | onCall: () => { 50 | console.log('Connected to socket.'); 51 | }, 52 | reportStatus: true 53 | }, 54 | stopOptions: { 55 | onCall: () => { 56 | console.log('Disconnected from socket.'); 57 | }, 58 | reportStatus: true 59 | } 60 | } 61 | } 62 | }; 63 | 64 | try { 65 | const htmlElement = document.getElementById('monaco-editor-root'); 66 | document.querySelector('#button-start')?.addEventListener('click', () => { 67 | startEditor(userConfig, htmlElement, codeMain, codeOrg); 68 | }); 69 | document.querySelector('#button-swap')?.addEventListener('click', () => { 70 | swapEditors(userConfig, htmlElement, codeMain, codeOrg); 71 | }); 72 | document.querySelector('#button-dispose')?.addEventListener('click', async () => { 73 | codeMain = await disposeEditor(userConfig.wrapperConfig.editorAppConfig.useDiffEditor); 74 | }); 75 | 76 | startEditor(userConfig, htmlElement, codeMain, codeOrg); 77 | } catch (e) { 78 | console.error(e); 79 | } 80 | -------------------------------------------------------------------------------- /packages/monaco-editor-react/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | All notable changes to npm module [@typefox/monaco-editor-react](https://www.npmjs.com/package/@typefox/monaco-editor-react) are documented in this file. 4 | 5 | ## [2.6.0] - 2024-01-04 6 | 7 | - Updated to `monaco-editor-wrapper` `3.6.0` 8 | 9 | ## [2.5.0] - 2023-12-07 10 | 11 | - Updated to `monaco-editor-wrapper` `3.5.0` 12 | 13 | ## [2.4.0] - 2023-11-27 14 | 15 | - Updated to `monaco-editor-wrapper` `3.4.0` 16 | - Make subclassing MonacoEditorReactComp more easy [#58](https://github.com/TypeFox/monaco-components/issues/58) 17 | - Allow to init and start separately [#59](https://github.com/TypeFox/monaco-components/issues/59) 18 | 19 | ## [2.3.0] - 2023-10-17 20 | 21 | - Properly separate and define classic and extended editor [#54](https://github.com/TypeFox/monaco-components/pull/54) 22 | - Renamed `EditorAppVscodeApi` to `EditorAppExtended` and `EditorAppConfigVscodeApi` to `EditorAppConfigExtended` 23 | - BREAKING: `$type` of `EditorAppConfigExtended` was changed from `vscodeApi` to `extended` 24 | - Updated to `monaco-editor-wrapper` `3.3.0` 25 | - Include all direct dependencies that the code uses in the `package.json`. 26 | 27 | ## [2.2.5] - 2023-10-13 28 | 29 | - Updated to `monaco-editor-wrapper` `3.2.5` 30 | 31 | ## [2.2.4] - 2023-10-05 32 | 33 | - Updated to `monaco-editor-wrapper` `3.2.4` 34 | 35 | ## [2.2.3] - 2023-10-04 36 | 37 | - Updated to `monaco-editor-wrapper` `3.2.3` 38 | 39 | ## [2.2.2] - 2023-10-04 40 | 41 | - Updated to `monaco-editor-wrapper` `3.2.2` 42 | 43 | ## [2.2.1] - 2023-09-29 44 | 45 | - Updated to `monaco-editor-wrapper` `3.2.1` 46 | 47 | ## [2.2.0] - 2023-09-29 48 | 49 | - Updated to `monaco-editor-wrapper` `3.2.0` 50 | - htmlElement is no longer part of UserConfig. Must be passed at start [#51](https://github.com/TypeFox/monaco-components/pull/51) 51 | - The HTMLElement it is no longer part of the UserConfig. The component just uses its root. 52 | 53 | ## [2.1.0] - 2023-09-21 54 | 55 | - Improve configuration change detection [#47](https://github.com/TypeFox/monaco-components/pull/47) 56 | - semantic highlighting works with classic editor [#45](https://github.com/TypeFox/monaco-components/pull/45) 57 | - Updated to `monaco-editor-wrapper` `3.1.0` 58 | 59 | ## [2.0.1] - 2023-09-07 60 | 61 | - Updated to `monaco-editor-wrapper` `3.0.1` 62 | 63 | ## [2.0.0] - 2023-08-31 64 | 65 | - Updated to `monaco-editor-wrapper` `3.0.0` 66 | - Removed `onLoading` as the current implemetation made no difference to `onLoad` 67 | 68 | ## [1.1.1] - 2023-07-27 69 | 70 | - Updated to `monaco-editor-wrapper` `2.1.1` 71 | 72 | ## [1.1.0] - 2023-06-16 73 | 74 | - Make worker handling more flexible [#27](https://github.com/TypeFox/monaco-components/pull/27) 75 | - Updated to `monaco-editor-wrapper` `2.1.0` 76 | 77 | ## [1.0.1] - 2023-06-12 78 | 79 | - Updated to `monaco-editor-wrapper` `2.0.1` using `monaco-languageclient` `6.1.0` / `monaco-vscode-api` `1.79.1` and `monaco-editor` `0.38.0` 80 | 81 | ## [1.0.0] - 2023-06-02 82 | 83 | - Initial release 84 | - React component that easily allows to use `monaco-editor-wrapper` and all its underlying features within the react world 85 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monaco-editor-wrapper", 3 | "version": "3.6.0", 4 | "license": "MIT", 5 | "description": "Monaco-Editor and Monaco Languageclient Wrapper", 6 | "keywords": [ 7 | "monaco-editor", 8 | "monaco-languageclient", 9 | "typescript" 10 | ], 11 | "type": "module", 12 | "main": "./dist/index.js", 13 | "module": "./dist/index.js", 14 | "exports": { 15 | ".": { 16 | "types": "./dist/index.d.ts", 17 | "default": "./dist/index.js" 18 | }, 19 | "./bundle": { 20 | "types": "./dist/index.d.ts", 21 | "default": "./bundle/index.js" 22 | }, 23 | "./styles": { 24 | "types": "./styles/index.d.ts", 25 | "default": "./styles/index.js" 26 | } 27 | }, 28 | "typesVersions": { 29 | "*": { 30 | ".": [ 31 | "dist/index" 32 | ], 33 | "bundle": [ 34 | "dist/index" 35 | ], 36 | "styles": [ 37 | "styles/index" 38 | ] 39 | } 40 | }, 41 | "files": [ 42 | "dist", 43 | "bundle", 44 | "src", 45 | "styles", 46 | "LICENSE", 47 | "README.md" 48 | ], 49 | "scripts": { 50 | "clean": "shx rm -rf ./dist ./bundle *.tsbuildinfo", 51 | "process:assets": "vite-node ./build/src/buildAssets.mts", 52 | "compile": "tsc --build tsconfig.src.json", 53 | "bundle": "vite --config vite.bundle.config.ts build && npm run process:assets", 54 | "build": "npm run clean && npm run compile && npm run bundle" 55 | }, 56 | "volta": { 57 | "node": "20.10.0", 58 | "npm": "10.2.3" 59 | }, 60 | "dependencies": { 61 | "@codingame/monaco-vscode-configuration-service-override": "~1.85.0", 62 | "@codingame/monaco-vscode-editor-service-override": "~1.85.0", 63 | "@codingame/monaco-vscode-textmate-service-override": "~1.85.0", 64 | "@codingame/monaco-vscode-theme-defaults-default-extension": "~1.85.0", 65 | "@codingame/monaco-vscode-theme-service-override": "~1.85.0", 66 | "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.85.0 <1.86.0", 67 | "monaco-languageclient": "~7.3.0", 68 | "vscode": "npm:@codingame/monaco-vscode-api@>=1.85.0 <1.86.0", 69 | "vscode-languageclient": "~9.0.1", 70 | "vscode-languageserver-protocol":"~3.17.5", 71 | "vscode-ws-jsonrpc": "~3.1.0" 72 | }, 73 | "peerDependencies": { 74 | "monaco-languageclient": "~7.3.0", 75 | "vscode-ws-jsonrpc": "~3.1.0" 76 | }, 77 | "overrides": { 78 | "monaco-editor": "$monaco-editor", 79 | "vscode": "$vscode" 80 | }, 81 | "resolutions": { 82 | "monaco-editor": "npm:@codingame/monaco-editor-treemended@>=1.85.0 <1.86.0", 83 | "vscode": "npm:@codingame/monaco-vscode-api@>=1.85.0 <1.86.0" 84 | }, 85 | "repository": { 86 | "type": "git", 87 | "url": "git+https://github.com/TypeFox/monaco-components.git", 88 | "directory": "packages/monaco-editor-wrapper" 89 | }, 90 | "homepage": "https://github.com/TypeFox/monaco-components/blob/main/packages/monaco-editor-wrapper/README.md", 91 | "bugs": "https://github.com/TypeFox/monaco-components/issues", 92 | "author": { 93 | "name": "TypeFox", 94 | "url": "https://www.typefox.io" 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/examples/src/langium/config/langium.configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | "lineComment": "//", 4 | "blockComment": [ 5 | "/*", 6 | "*/" 7 | ] 8 | }, 9 | "brackets": [ 10 | [ 11 | "{", 12 | "}" 13 | ], 14 | [ 15 | "[", 16 | "]" 17 | ], 18 | [ 19 | "(", 20 | ")" 21 | ] 22 | ], 23 | "autoClosingPairs": [ 24 | { 25 | "open": "{", 26 | "close": "}" 27 | }, 28 | { 29 | "open": "[", 30 | "close": "]" 31 | }, 32 | { 33 | "open": "(", 34 | "close": ")" 35 | }, 36 | { 37 | "open": "'", 38 | "close": "'", 39 | "notIn": [ 40 | "string", 41 | "comment" 42 | ] 43 | }, 44 | { 45 | "open": "\"", 46 | "close": "\"", 47 | "notIn": [ 48 | "string" 49 | ] 50 | }, 51 | { 52 | "open": "/**", 53 | "close": " */", 54 | "notIn": [ 55 | "string" 56 | ] 57 | } 58 | ], 59 | "autoCloseBefore": "}])`\n\t", 60 | "surroundingPairs": [ 61 | [ 62 | "{", 63 | "}" 64 | ], 65 | [ 66 | "[", 67 | "]" 68 | ], 69 | [ 70 | "(", 71 | ")" 72 | ] 73 | ], 74 | "colorizedBracketPairs": [ 75 | [ 76 | "(", 77 | ")" 78 | ], 79 | [ 80 | "[", 81 | "]" 82 | ], 83 | [ 84 | "{", 85 | "}" 86 | ], 87 | [ 88 | "<", 89 | ">" 90 | ], 91 | [ 92 | "'", 93 | "'" 94 | ], 95 | [ 96 | "\"", 97 | "\"" 98 | ], 99 | [ 100 | "<", 101 | ">" 102 | ] 103 | ], 104 | "onEnterRules": [ 105 | { 106 | "": "// e.g. /** | */", 107 | "beforeText": { 108 | "pattern": "^\\s*/\\*\\*(?!/)([^\\*]|\\*(?!/))*$" 109 | }, 110 | "afterText": { 111 | "pattern": "^\\s*\\*/$" 112 | }, 113 | "action": { 114 | "indent": "indentOutdent", 115 | "appendText": " * " 116 | } 117 | }, 118 | { 119 | "": "// e.g. /** ...|", 120 | "beforeText": { 121 | "pattern": "^\\s*/\\*\\*(?!/)([^\\*]|\\*(?!/))*$" 122 | }, 123 | "action": { 124 | "indent": "none", 125 | "appendText": " * " 126 | } 127 | }, 128 | { 129 | "": "// e.g. * ...|", 130 | "beforeText": { 131 | "pattern": "^(\\t|[ ])*[ ]\\*([ ]([^\\*]|\\*(?!/))*)?$" 132 | }, 133 | "previousLineText": { 134 | "pattern": "(?=^(\\s*(/\\*\\*|\\*)).*)(?=(?!(\\s*\\*/)))" 135 | }, 136 | "action": { 137 | "indent": "none", 138 | "appendText": "* " 139 | } 140 | }, 141 | { 142 | "": "// e.g. */|", 143 | "beforeText": { 144 | "pattern": "^(\\t|[ ])*[ ]\\*/\\s*$" 145 | }, 146 | "action": { 147 | "indent": "none", 148 | "removeText": 1 149 | } 150 | }, 151 | { 152 | "beforeText": ":\\s*$", 153 | "action": { 154 | "indent": "indent" 155 | } 156 | }, 157 | { 158 | "beforeText": ";\\s*$", 159 | "action": { 160 | "indent": "outdent" 161 | } 162 | } 163 | ] 164 | } -------------------------------------------------------------------------------- /packages/examples/src/common.ts: -------------------------------------------------------------------------------- 1 | import { ModelUpdate, MonacoEditorLanguageClientWrapper, UserConfig } from 'monaco-editor-wrapper'; 2 | import { languages } from 'monaco-editor'; 3 | 4 | export const wrapper = new MonacoEditorLanguageClientWrapper(); 5 | 6 | export const startEditor = async (userConfig: UserConfig, htmlElement: HTMLElement | null, code: string, codeOriginal?: string) => { 7 | if (wrapper.isStarted()) { 8 | alert('Editor was already started!'); 9 | return; 10 | } 11 | configureCodeEditors(userConfig, code, codeOriginal); 12 | toggleSwapDiffButton(true); 13 | await restartEditor(userConfig, htmlElement); 14 | }; 15 | 16 | export const updateModel = async (modelUpdate: ModelUpdate) => { 17 | if (wrapper.getMonacoEditorApp()?.getConfig().useDiffEditor) { 18 | await wrapper?.updateDiffModel(modelUpdate); 19 | } else { 20 | await wrapper?.updateModel(modelUpdate); 21 | } 22 | }; 23 | 24 | export const swapEditors = async (userConfig: UserConfig, htmlElement: HTMLElement | null, code: string, codeOriginal?: string) => { 25 | userConfig.wrapperConfig.editorAppConfig.useDiffEditor = !userConfig.wrapperConfig.editorAppConfig.useDiffEditor; 26 | saveMainCode(!userConfig.wrapperConfig.editorAppConfig.useDiffEditor); 27 | configureCodeEditors(userConfig, code, codeOriginal); 28 | await restartEditor(userConfig, htmlElement); 29 | }; 30 | 31 | export const disposeEditor = async (useDiffEditor: boolean) => { 32 | wrapper.reportStatus(); 33 | toggleSwapDiffButton(false); 34 | const codeMain = saveMainCode(useDiffEditor); 35 | 36 | await wrapper.dispose(); 37 | return codeMain; 38 | }; 39 | 40 | const restartEditor = async (userConfig: UserConfig, htmlElement: HTMLElement | null) => { 41 | await wrapper.dispose(); 42 | await wrapper.initAndStart(userConfig, htmlElement); 43 | logEditorInfo(userConfig); 44 | }; 45 | 46 | const configureCodeEditors = (userConfig: UserConfig, code: string, codeOriginal?: string) => { 47 | if (userConfig.wrapperConfig.editorAppConfig.useDiffEditor) { 48 | userConfig.wrapperConfig.editorAppConfig.code = code; 49 | userConfig.wrapperConfig.editorAppConfig.codeOriginal = codeOriginal; 50 | } else { 51 | userConfig.wrapperConfig.editorAppConfig.code = code; 52 | } 53 | }; 54 | 55 | const saveMainCode = (saveFromDiff: boolean) => { 56 | if (saveFromDiff) { 57 | return wrapper.getModel(true)!.getValue(); 58 | } else { 59 | return wrapper.getModel()!.getValue(); 60 | } 61 | }; 62 | 63 | const toggleSwapDiffButton = (enabled: boolean) => { 64 | const button = document.getElementById('button-swap') as HTMLButtonElement; 65 | if (button !== null) { 66 | button.disabled = !enabled; 67 | } 68 | }; 69 | 70 | const logEditorInfo = (userConfig: UserConfig) => { 71 | console.log(`# of configured languages: ${languages.getLanguages().length}`); 72 | console.log(`Main code: ${wrapper.getModel(true)!.getValue()}`); 73 | if (userConfig.wrapperConfig.editorAppConfig.useDiffEditor) { 74 | console.log(`Modified code: ${wrapper.getModel()!.getValue()}`); 75 | } 76 | }; 77 | 78 | export const getTextContent = async (url: URL) => { 79 | const response = await fetch(url.href); 80 | return response.text(); 81 | }; 82 | -------------------------------------------------------------------------------- /packages/examples/src/langium/wrapperStatemachine.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; 3 | import { createLangiumGlobalConfig } from './config/wrapperStatemachineConfig.js'; 4 | 5 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 6 | buildWorkerDefinition('../../../../node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); 7 | 8 | const wrapper = new MonacoEditorLanguageClientWrapper(); 9 | const wrapper2 = new MonacoEditorLanguageClientWrapper(); 10 | 11 | const startEditor = async () => { 12 | if (wrapper.isStarted() && wrapper2.isStarted()) { 13 | alert('Editor was already started!'); 14 | return; 15 | } 16 | 17 | // init first worker regularly 18 | const stateMachineWorkerRegular = loadStatemachinWorkerRegular(); 19 | const langiumGlobalConfig = await createLangiumGlobalConfig(stateMachineWorkerRegular); 20 | await wrapper.initAndStart(langiumGlobalConfig, document.getElementById('monaco-editor-root')); 21 | 22 | // init second worker with port for client and worker 23 | const stateMachineWorkerPort = loadStatemachinWorkerPort(); 24 | // use callback to receive message back from worker independent of the message channel the LSP is using 25 | stateMachineWorkerPort.onmessage = (event) => { 26 | console.log('Received message from worker: ' + event.data); 27 | }; 28 | const channel = new MessageChannel(); 29 | stateMachineWorkerPort.postMessage({ 30 | port: channel.port2 31 | }, [channel.port2]); 32 | 33 | const langiumGlobalConfig2 = await createLangiumGlobalConfig(stateMachineWorkerPort, channel.port1); 34 | await wrapper2.initAndStart(langiumGlobalConfig2, document.getElementById('monaco-editor-root2')); 35 | 36 | vscode.commands.getCommands().then((x) => { 37 | console.log('Currently registered # of vscode commands: ' + x.length); 38 | }); 39 | }; 40 | 41 | const disposeEditor = async () => { 42 | wrapper.reportStatus(); 43 | await wrapper.dispose(); 44 | console.log(wrapper.reportStatus().join('\n')); 45 | 46 | wrapper2.reportStatus(); 47 | await wrapper2.dispose(); 48 | console.log(wrapper2.reportStatus().join('\n')); 49 | }; 50 | 51 | export const run = async () => { 52 | try { 53 | document.querySelector('#button-start')?.addEventListener('click', startEditor); 54 | document.querySelector('#button-dispose')?.addEventListener('click', disposeEditor); 55 | } catch (e) { 56 | console.error(e); 57 | } 58 | }; 59 | 60 | export const loadStatemachinWorkerRegular = () => { 61 | // Language Server preparation 62 | const workerUrl = new URL('./src/langium/worker/statemachine-server.ts', window.location.href); 63 | console.log(`Langium worker URL: ${workerUrl}`); 64 | 65 | return new Worker(workerUrl, { 66 | type: 'module', 67 | name: 'Statemachine Server Regular', 68 | }); 69 | }; 70 | 71 | export const loadStatemachinWorkerPort = () => { 72 | // Language Server preparation 73 | const workerUrl = new URL('./src/langium/worker/statemachine-server-port.ts', window.location.href); 74 | console.log(`Langium worker URL: ${workerUrl}`); 75 | 76 | return new Worker(workerUrl, { 77 | type: 'module', 78 | name: 'Statemachine Server Port', 79 | }); 80 | }; 81 | -------------------------------------------------------------------------------- /packages/examples/src/langium/config/wrapperLangiumExtended.ts: -------------------------------------------------------------------------------- 1 | import getEditorServiceOverride from '@codingame/monaco-vscode-editor-service-override'; 2 | import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; 3 | import { useOpenEditorStub } from 'monaco-languageclient'; 4 | import { UserConfig } from 'monaco-editor-wrapper'; 5 | import { getTextContent } from '../../common.js'; 6 | import { loadLangiumWorker } from '../wrapperLangium.js'; 7 | 8 | export const setupLangiumClientExtended = async (): Promise => { 9 | const code = await getTextContent(new URL('./src/langium/content/example.langium', window.location.href)); 10 | 11 | const extensionFilesOrContents = new Map(); 12 | const langiumLanguageConfig = new URL('./src/langium/config/langium.configuration.json', window.location.href); 13 | const langiumTextmateGrammar = await getTextContent(new URL('./src/langium/config/langium.tmLanguage.json', window.location.href)); 14 | // test both url and string content 15 | extensionFilesOrContents.set('/langium-configuration.json', langiumLanguageConfig); 16 | extensionFilesOrContents.set('/langium-grammar.json', langiumTextmateGrammar); 17 | 18 | const langiumWorker = loadLangiumWorker(); 19 | return { 20 | wrapperConfig: { 21 | serviceConfig: { 22 | userServices: { 23 | ...getEditorServiceOverride(useOpenEditorStub), 24 | ...getKeybindingsServiceOverride() 25 | }, 26 | debugLogging: true 27 | }, 28 | editorAppConfig: { 29 | $type: 'extended', 30 | languageId: 'langium', 31 | code: code, 32 | useDiffEditor: false, 33 | extensions: [{ 34 | config: { 35 | name: 'langium-example', 36 | publisher: 'monaco-editor-wrapper-examples', 37 | version: '1.0.0', 38 | engines: { 39 | vscode: '*' 40 | }, 41 | contributes: { 42 | languages: [{ 43 | id: 'langium', 44 | extensions: ['.langium'], 45 | aliases: ['langium', 'LANGIUM'], 46 | configuration: './langium-configuration.json' 47 | }], 48 | grammars: [{ 49 | language: 'langium', 50 | scopeName: 'source.langium', 51 | path: './langium-grammar.json' 52 | }] 53 | } 54 | }, 55 | filesOrContents: extensionFilesOrContents 56 | }], 57 | userConfiguration: { 58 | json: JSON.stringify({ 59 | 'workbench.colorTheme': 'Default Dark Modern', 60 | 'editor.guides.bracketPairsHorizontal': 'active', 61 | 'editor.lightbulb.enabled': true 62 | }) 63 | } 64 | } 65 | }, 66 | languageClientConfig: { 67 | options: { 68 | $type: 'WorkerDirect', 69 | worker: langiumWorker 70 | } 71 | } 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/test/editorAppClassic.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { EditorAppClassic, EditorAppConfigClassic } from 'monaco-editor-wrapper'; 3 | import { createBaseConfig, createEditorAppConfig } from './helper.js'; 4 | 5 | const buildConfig = () => { 6 | const config = createBaseConfig('classic'); 7 | (config.wrapperConfig.editorAppConfig as EditorAppConfigClassic).editorOptions = {}; 8 | return config; 9 | }; 10 | 11 | describe('Test EditorAppClassic', () => { 12 | 13 | test('editorOptions: semanticHighlighting=true', () => { 14 | const config = buildConfig(); 15 | const configclassic = config.wrapperConfig.editorAppConfig as EditorAppConfigClassic; 16 | configclassic.editorOptions!['semanticHighlighting.enabled'] = true; 17 | 18 | const app = new EditorAppClassic('config defaults', config); 19 | expect(configclassic.$type).toEqual('classic'); 20 | expect(app.getConfig().editorOptions?.['semanticHighlighting.enabled']).toBeTruthy(); 21 | }); 22 | 23 | test('editorOptions: semanticHighlighting=false', () => { 24 | const config = buildConfig(); 25 | const configclassic = config.wrapperConfig.editorAppConfig as EditorAppConfigClassic; 26 | configclassic.editorOptions!['semanticHighlighting.enabled'] = false; 27 | 28 | const app = new EditorAppClassic('config defaults', config); 29 | expect(app.getConfig().editorOptions?.['semanticHighlighting.enabled']).toBeFalsy(); 30 | }); 31 | 32 | test('editorOptions: semanticHighlighting="configuredByTheme"', () => { 33 | const config = buildConfig(); 34 | const configclassic = config.wrapperConfig.editorAppConfig as EditorAppConfigClassic; 35 | configclassic.editorOptions!['semanticHighlighting.enabled'] = 'configuredByTheme'; 36 | 37 | const app = new EditorAppClassic('config defaults', config); 38 | expect(app.getConfig().editorOptions?.['semanticHighlighting.enabled']).toEqual('configuredByTheme'); 39 | }); 40 | 41 | test('isAppConfigDifferent: basic', () => { 42 | const orgConfig = createEditorAppConfig('classic') as EditorAppConfigClassic; 43 | const config = createEditorAppConfig('classic') as EditorAppConfigClassic; 44 | const app = new EditorAppClassic('test', createBaseConfig('classic')); 45 | expect(app.isAppConfigDifferent(orgConfig, config, false)).toBeFalsy(); 46 | 47 | config.code = 'test'; 48 | expect(app.isAppConfigDifferent(orgConfig, config, false)).toBeFalsy(); 49 | expect(app.isAppConfigDifferent(orgConfig, config, true)).toBeTruthy(); 50 | 51 | config.code = ''; 52 | config.useDiffEditor = true; 53 | expect(app.isAppConfigDifferent(orgConfig, config, false)).toBeTruthy(); 54 | }); 55 | 56 | test('isAppConfigDifferent: non-simple properties"', () => { 57 | const config1 = buildConfig(); 58 | const config2 = buildConfig(); 59 | const configclassic1 = config1.wrapperConfig.editorAppConfig as EditorAppConfigClassic; 60 | configclassic1.editorOptions!['semanticHighlighting.enabled'] = true; 61 | const configclassic2 = config2.wrapperConfig.editorAppConfig as EditorAppConfigClassic; 62 | configclassic2.editorOptions!['semanticHighlighting.enabled'] = true; 63 | 64 | const app = new EditorAppClassic('config defaults', config1); 65 | expect(app.isAppConfigDifferent(configclassic1, configclassic2, false)).toBeFalsy(); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /packages/examples/src/langium/config/wrapperStatemachineConfig.ts: -------------------------------------------------------------------------------- 1 | import getEditorServiceOverride from '@codingame/monaco-vscode-editor-service-override'; 2 | import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; 3 | import { useOpenEditorStub } from 'monaco-languageclient'; 4 | import { UserConfig } from 'monaco-editor-wrapper'; 5 | import { getTextContent } from '../../common.js'; 6 | 7 | export const createLangiumGlobalConfig = async (worker: Worker, messagePort?: MessagePort): Promise => { 8 | const code = await getTextContent(new URL('./src/langium/content/example.statemachine', window.location.href)); 9 | 10 | const extensionFilesOrContents = new Map(); 11 | const statemachineLanguageConfig = new URL('../../../node_modules/langium-statemachine-dsl/language-configuration.json', window.location.href); 12 | const responseStatemachineTm = new URL('../../../node_modules/langium-statemachine-dsl/syntaxes/statemachine.tmLanguage.json', window.location.href); 13 | extensionFilesOrContents.set('/statemachine-configuration.json', statemachineLanguageConfig); 14 | extensionFilesOrContents.set('/statemachine-grammar.json', responseStatemachineTm); 15 | 16 | return { 17 | wrapperConfig: { 18 | serviceConfig: { 19 | userServices: { 20 | ...getEditorServiceOverride(useOpenEditorStub), 21 | ...getKeybindingsServiceOverride() 22 | }, 23 | debugLogging: true 24 | }, 25 | editorAppConfig: { 26 | $type: 'extended', 27 | languageId: 'statemachine', 28 | code: code, 29 | useDiffEditor: false, 30 | extensions: [{ 31 | config: { 32 | name: 'statemachine-example', 33 | publisher: 'monaco-editor-wrapper-examples', 34 | version: '1.0.0', 35 | engines: { 36 | vscode: '*' 37 | }, 38 | contributes: { 39 | languages: [{ 40 | id: 'statemachine', 41 | extensions: ['.statemachine'], 42 | aliases: ['statemachine', 'Statemachine'], 43 | configuration: './statemachine-configuration.json' 44 | }], 45 | grammars: [{ 46 | language: 'statemachine', 47 | scopeName: 'source.statemachine', 48 | path: './statemachine-grammar.json' 49 | }] 50 | } 51 | }, 52 | filesOrContents: extensionFilesOrContents 53 | }], 54 | userConfiguration: { 55 | json: JSON.stringify({ 56 | 'workbench.colorTheme': 'Default Dark Modern', 57 | 'editor.guides.bracketPairsHorizontal': 'active', 58 | 'editor.lightbulb.enabled': true 59 | }) 60 | } 61 | } 62 | }, 63 | languageClientConfig: { 64 | options: { 65 | $type: 'WorkerDirect', 66 | worker, 67 | messagePort 68 | } 69 | } 70 | }; 71 | }; 72 | -------------------------------------------------------------------------------- /packages/examples/src/langium/wrapperLangium.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) 2018-2022 TypeFox GmbH (http://www.typefox.io). All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 6 | import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; 7 | import { setupLangiumClientExtended } from './config/wrapperLangiumExtended.js'; 8 | import { setupLangiumClientClassic } from './config/wrapperLangiumClassic.js'; 9 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 10 | 11 | buildWorkerDefinition('../../../node_modules/monaco-editor-workers/dist/workers/', new URL('', window.location.href).href, false); 12 | 13 | let wrapper: MonacoEditorLanguageClientWrapper | undefined; 14 | let extended = false; 15 | 16 | const htmlElement = document.getElementById('monaco-editor-root'); 17 | export const run = async () => { 18 | try { 19 | document.querySelector('#button-start-classic')?.addEventListener('click', startLangiumClientClassic); 20 | document.querySelector('#button-start-extended')?.addEventListener('click', startLangiumClientExtended); 21 | document.querySelector('#button-dispose')?.addEventListener('click', disposeEditor); 22 | } catch (e) { 23 | console.error(e); 24 | } 25 | }; 26 | 27 | export const startLangiumClientExtended = async () => { 28 | try { 29 | if (checkStarted()) return; 30 | extended = true; 31 | disableButton('button-start-classic', true); 32 | disableButton('button-start-extended', true); 33 | const config = await setupLangiumClientExtended(); 34 | wrapper = new MonacoEditorLanguageClientWrapper(); 35 | wrapper.initAndStart(config, htmlElement); 36 | } catch (e) { 37 | console.log(e); 38 | } 39 | }; 40 | 41 | export const startLangiumClientClassic = async () => { 42 | try { 43 | if (checkStarted()) return; 44 | disableButton('button-start-classic', true); 45 | disableButton('button-start-extended', true); 46 | const config = await setupLangiumClientClassic(); 47 | wrapper = new MonacoEditorLanguageClientWrapper(); 48 | await wrapper.initAndStart(config, htmlElement!); 49 | } catch (e) { 50 | console.log(e); 51 | } 52 | }; 53 | 54 | const checkStarted = () => { 55 | if (wrapper?.isStarted()) { 56 | alert('Editor was already started!\nPlease reload the page to test the alternative editor.'); 57 | return true; 58 | } 59 | return false; 60 | }; 61 | 62 | const disableButton = (id: string, disabled: boolean) => { 63 | const button = document.getElementById(id) as HTMLButtonElement; 64 | if (button !== null) { 65 | button.disabled = disabled; 66 | } 67 | }; 68 | 69 | export const disposeEditor = async () => { 70 | if (!wrapper) return; 71 | wrapper.reportStatus(); 72 | await wrapper.dispose(); 73 | wrapper = undefined; 74 | if (extended) { 75 | disableButton('button-start-extended', false); 76 | } else { 77 | disableButton('button-start-classic', false); 78 | } 79 | }; 80 | 81 | export const loadLangiumWorker = () => { 82 | // Language Server preparation 83 | const workerUrl = new URL('./src/servers/langium-server.ts', window.location.href); 84 | console.log(`Langium worker URL: ${workerUrl}`); 85 | 86 | return new Worker(workerUrl, { 87 | type: 'module', 88 | name: 'Langium LS', 89 | }); 90 | }; 91 | -------------------------------------------------------------------------------- /packages/examples/src/wrapperTs.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from 'vscode'; 2 | import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; 3 | import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution.js'; 4 | import 'monaco-editor/esm/vs/language/typescript/monaco.contribution.js'; 5 | import { disposeEditor, startEditor, swapEditors, updateModel, wrapper } from './common.js'; 6 | import { UserConfig } from 'monaco-editor-wrapper'; 7 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 8 | 9 | buildWorkerDefinition('../../../node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); 10 | 11 | const codeUri = '/workspace/hello.ts'; 12 | let code = `function sayHello(): string { 13 | return "Hello"; 14 | };`; 15 | 16 | const codeOriginalUri = '/workspace/goodbye.ts'; 17 | let codeOriginal = `function sayGoodbye(): string { 18 | return "Goodbye"; 19 | };`; 20 | 21 | const monacoEditorConfig = { 22 | glyphMargin: true, 23 | guides: { 24 | bracketPairs: true 25 | }, 26 | lightbulb: { 27 | enabled: true 28 | }, 29 | theme: 'vs-dark', 30 | renderSideBySide: false 31 | }; 32 | 33 | const userConfig: UserConfig = { 34 | wrapperConfig: { 35 | serviceConfig: { 36 | userServices: { 37 | ...getKeybindingsServiceOverride() 38 | }, 39 | debugLogging: true 40 | }, 41 | editorAppConfig: { 42 | $type: 'classic', 43 | languageId: 'typescript', 44 | code, 45 | codeUri: codeUri, 46 | codeOriginal: codeOriginal, 47 | useDiffEditor: false, 48 | editorOptions: monacoEditorConfig, 49 | diffEditorOptions: monacoEditorConfig 50 | } 51 | } 52 | }; 53 | 54 | try { 55 | const htmlElement = document.getElementById('monaco-editor-root'); 56 | document.querySelector('#button-start')?.addEventListener('click', () => { 57 | startEditor(userConfig, htmlElement, code, codeOriginal); 58 | }); 59 | document.querySelector('#button-swap')?.addEventListener('click', () => { 60 | swapEditors(userConfig, htmlElement, code, codeOriginal); 61 | }); 62 | document.querySelector('#button-swap-code')?.addEventListener('click', () => { 63 | if (wrapper.getMonacoEditorApp()?.getConfig().codeUri === codeUri) { 64 | updateModel({ 65 | code: codeOriginal, 66 | codeUri: codeOriginalUri, 67 | languageId: 'typescript', 68 | }); 69 | } else { 70 | updateModel({ 71 | code: code, 72 | codeUri: codeUri, 73 | languageId: 'typescript', 74 | }); 75 | } 76 | }); 77 | document.querySelector('#button-dispose')?.addEventListener('click', async () => { 78 | if (wrapper.getMonacoEditorApp()?.getConfig().codeUri === codeUri) { 79 | code = await disposeEditor(userConfig.wrapperConfig.editorAppConfig.useDiffEditor); 80 | } else { 81 | codeOriginal = await disposeEditor(userConfig.wrapperConfig.editorAppConfig.useDiffEditor); 82 | } 83 | }); 84 | 85 | await startEditor(userConfig, htmlElement, code, codeOriginal); 86 | 87 | vscode.commands.getCommands().then((x) => { 88 | console.log(`Found ${x.length} commands`); 89 | const finding = x.find((elem) => elem === 'actions.find'); 90 | console.log(`Found command: ${finding}`); 91 | }); 92 | 93 | wrapper.getEditor()?.focus(); 94 | await vscode.commands.executeCommand('actions.find'); 95 | } catch (e) { 96 | console.error(e); 97 | } 98 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import * as path from 'path'; 3 | import * as fs from 'fs'; 4 | import url from 'url'; 5 | 6 | export default defineConfig(() => { 7 | const config = { 8 | build: { 9 | target: 'esnext', 10 | rollupOptions: { 11 | input: { 12 | index: path.resolve(__dirname, '/index.html'), 13 | wrapperStatemachine: path.resolve(__dirname, '/packages/examples/wrapper_statemachine.html'), 14 | wrapperLangium: path.resolve(__dirname, '/packages/examples/wrapper_langium.html'), 15 | wrapperTs: path.resolve(__dirname, '/packages/examples/wrapper_ts.html'), 16 | wrapperWebSocket: path.resolve(__dirname, '/packages/examples/wrapper_ws.html'), 17 | wrapperAdvanced: path.resolve(__dirname, '/packages/examples/wrapper_adv.html'), 18 | reactPython: path.resolve(__dirname, '/packages/examples/react_python.html'), 19 | reactStatemachine: path.resolve(__dirname, '/packages/examples/react_statemachine.html'), 20 | reactTs: path.resolve(__dirname, '/packages/examples/react_ts.html'), 21 | workers: path.resolve(__dirname, '/packages/examples/workers.html'), 22 | verifyWrapper: path.resolve(__dirname, '/packages/examples/verify_wrapper.html'), 23 | verifyAlt: path.resolve(__dirname, '/packages/examples/verify_alt.html') 24 | } 25 | } 26 | }, 27 | resolve: { 28 | dedupe: ['monaco-editor', 'vscode'] 29 | }, 30 | server: { 31 | origin: 'http://localhost:20001', 32 | port: 20001 33 | }, 34 | optimizeDeps: { 35 | esbuildOptions: { 36 | plugins: [ 37 | // copied from "https://github.com/CodinGame/monaco-vscode-api/blob/main/demo/vite.config.ts" 38 | { 39 | name: 'import.meta.url', 40 | setup({ onLoad }) { 41 | // Help vite that bundles/move files in dev mode without touching `import.meta.url` which breaks asset urls 42 | onLoad({ filter: /.*\.js/, namespace: 'file' }, async args => { 43 | const code = fs.readFileSync(args.path, 'utf8'); 44 | 45 | const assetImportMetaUrlRE = /\bnew\s+URL\s*\(\s*('[^']+'|"[^"]+"|`[^`]+`)\s*,\s*import\.meta\.url\s*(?:,\s*)?\)/g; 46 | let i = 0; 47 | let newCode = ''; 48 | for (let match = assetImportMetaUrlRE.exec(code); match != null; match = assetImportMetaUrlRE.exec(code)) { 49 | newCode += code.slice(i, match.index); 50 | const path = match[1].slice(1, -1); 51 | 52 | const resolved = await import.meta.resolve!(path, url.pathToFileURL(args.path)); 53 | newCode += `new URL(${JSON.stringify(url.fileURLToPath(resolved))}, import.meta.url)`; 54 | i = assetImportMetaUrlRE.lastIndex; 55 | } 56 | newCode += code.slice(i); 57 | return { contents: newCode }; 58 | }); 59 | } 60 | } 61 | ] 62 | } 63 | }, 64 | define: { 65 | rootDirectory: JSON.stringify(__dirname) 66 | }, 67 | test: { 68 | pool: 'threads', 69 | poolOptions: { 70 | threads: { 71 | isolate: true 72 | } 73 | }, 74 | browser: { 75 | enabled: true, 76 | headless: true, 77 | name: 'chrome', 78 | api: { 79 | port: 20101 80 | } 81 | } 82 | } 83 | }; 84 | return config; 85 | }); 86 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/test/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { WebSocketConfigOptions, WebSocketConfigOptionsUrl, createUrl } from 'monaco-editor-wrapper'; 3 | 4 | describe('createUrl', () => { 5 | 6 | test('test createUrl: ws', () => { 7 | const url = createUrl({ 8 | secured: false, 9 | host: 'localhost', 10 | port: 30000, 11 | path: 'sampleServer' 12 | } as WebSocketConfigOptions); 13 | 14 | expect(url).toBe('ws://localhost:30000/sampleServer'); 15 | }); 16 | 17 | test('test createUrl: wss', () => { 18 | const url = createUrl({ 19 | secured: true, 20 | host: 'localhost', 21 | port: 30000, 22 | path: 'sampleServer' 23 | } as WebSocketConfigOptions); 24 | 25 | expect(url).toBe('wss://localhost:30000/sampleServer'); 26 | }); 27 | 28 | test('test createUrl: wss, no port, with path', () => { 29 | const url = createUrl({ 30 | secured: true, 31 | host: 'localhost', 32 | path: 'sampleServer' 33 | } as WebSocketConfigOptions); 34 | 35 | expect(url).toBe('wss://localhost/sampleServer'); 36 | }); 37 | 38 | test('test createUrl: wss, with port, no path', () => { 39 | const url = createUrl({ 40 | secured: true, 41 | host: 'localhost', 42 | port: 30000 43 | } as WebSocketConfigOptions); 44 | 45 | expect(url).toBe('wss://localhost:30000'); 46 | }); 47 | 48 | test('test createUrl: wss, no port, no path', () => { 49 | const url = createUrl({ 50 | secured: true, 51 | host: 'localhost' 52 | } as WebSocketConfigOptions); 53 | 54 | expect(url).toBe('wss://localhost'); 55 | }); 56 | 57 | test('test createUrl: ws, normalize port 80', () => { 58 | const url = createUrl({ 59 | secured: false, 60 | host: 'localhost', 61 | port: 80 62 | } as WebSocketConfigOptions); 63 | 64 | expect(url).toBe('ws://localhost'); 65 | }); 66 | 67 | test('test createUrl: ws, normalize port 80, with path', () => { 68 | const url = createUrl({ 69 | secured: false, 70 | host: 'localhost', 71 | port: 80, 72 | path: 'sampleServer' 73 | } as WebSocketConfigOptions); 74 | 75 | expect(url).toBe('ws://localhost/sampleServer'); 76 | }); 77 | 78 | test('test createUrl: optionsUrl: ws', () => { 79 | const url = createUrl({ 80 | url: 'ws://localhost:30000/sampleServer' 81 | } as WebSocketConfigOptionsUrl); 82 | 83 | expect(url).toBe('ws://localhost:30000/sampleServer'); 84 | }); 85 | 86 | test('test createUrl: optionsUrl: wss', () => { 87 | const url = createUrl({ 88 | url: 'wss://localhost:30000/sampleServer' 89 | } as WebSocketConfigOptionsUrl); 90 | 91 | expect(url).toBe('wss://localhost:30000/sampleServer'); 92 | }); 93 | 94 | test('test createUrl: optionsUrl, with port, no path', () => { 95 | const url = createUrl({ 96 | url: 'wss://localhost:30000' 97 | } as WebSocketConfigOptionsUrl); 98 | 99 | expect(url).toBe('wss://localhost:30000'); 100 | }); 101 | 102 | test('test createUrl: optionsUrl, no port, with path', () => { 103 | const url = createUrl({ 104 | url: 'ws://localhost/sampleServer' 105 | } as WebSocketConfigOptionsUrl); 106 | 107 | expect(url).toBe('ws://localhost/sampleServer'); 108 | }); 109 | 110 | test('test createUrl: optionsUrl, no port, no path', () => { 111 | const url = createUrl({ 112 | url: 'wss://www.testme.com' 113 | } as WebSocketConfigOptionsUrl); 114 | 115 | expect(url).toBe('wss://www.testme.com'); 116 | }); 117 | 118 | test('test createUrl: ws, not proper url', () => { 119 | expect(() => createUrl({ 120 | url: 'http://www.testme.com:30000/sampleServer' 121 | } as WebSocketConfigOptionsUrl)).toThrowError('This is not a proper websocket url: http://www.testme.com:30000/sampleServer'); 122 | }); 123 | 124 | }); 125 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/src/editorAppClassic.ts: -------------------------------------------------------------------------------- 1 | import { editor, languages } from 'monaco-editor'; 2 | import { EditorAppBase, EditorAppConfigBase, ModelUpdateType, isEqual, isModelUpdateRequired } from './editorAppBase.js'; 3 | import { UserConfig } from './wrapper.js'; 4 | import { Logger } from './logger.js'; 5 | 6 | export type EditorAppConfigClassic = EditorAppConfigBase & { 7 | $type: 'classic'; 8 | theme?: editor.BuiltinTheme | string; 9 | languageExtensionConfig?: languages.ILanguageExtensionPoint; 10 | languageDef?: languages.IMonarchLanguage; 11 | themeData?: editor.IStandaloneThemeData; 12 | }; 13 | 14 | /** 15 | * The classic monaco-editor app uses the classic monaco-editor configuration. 16 | */ 17 | export class EditorAppClassic extends EditorAppBase { 18 | 19 | private config: EditorAppConfigClassic; 20 | private logger: Logger | undefined; 21 | 22 | constructor(id: string, userConfig: UserConfig, logger?: Logger) { 23 | super(id); 24 | this.logger = logger; 25 | const userAppConfig = userConfig.wrapperConfig.editorAppConfig as EditorAppConfigClassic; 26 | this.config = this.buildConfig(userAppConfig) as EditorAppConfigClassic; 27 | // default to vs-light 28 | this.config.theme = userAppConfig.theme ?? 'vs-light'; 29 | this.config.languageExtensionConfig = userAppConfig.languageExtensionConfig ?? undefined; 30 | this.config.languageDef = userAppConfig.languageDef ?? undefined; 31 | this.config.themeData = userAppConfig.themeData ?? undefined; 32 | } 33 | 34 | getConfig(): EditorAppConfigClassic { 35 | return this.config; 36 | } 37 | 38 | override specifyServices(): editor.IEditorOverrideServices { 39 | return {}; 40 | } 41 | 42 | async init() { 43 | // await all extenson that should be ready beforehand 44 | await this.awaitReadiness(this.config.awaitExtensionReadiness); 45 | 46 | // register own language first 47 | const extLang = this.config.languageExtensionConfig; 48 | if (extLang) { 49 | languages.register(extLang); 50 | } 51 | 52 | const languageRegistered = languages.getLanguages().filter(x => x.id === this.config.languageId); 53 | if (languageRegistered.length === 0) { 54 | // this is only meaningful for languages supported by monaco out of the box 55 | languages.register({ 56 | id: this.config.languageId 57 | }); 58 | } 59 | 60 | // apply monarch definitions 61 | const tokenProvider = this.config.languageDef; 62 | if (tokenProvider) { 63 | languages.setMonarchTokensProvider(this.config.languageId, tokenProvider); 64 | } 65 | const themeData = this.config.themeData; 66 | if (themeData) { 67 | editor.defineTheme(this.config.theme!, themeData); 68 | } 69 | editor.setTheme(this.config.theme!); 70 | 71 | if (this.config.editorOptions?.['semanticHighlighting.enabled'] !== undefined) { 72 | // use updateConfiguration here as otherwise semantic highlighting will not work 73 | const json = JSON.stringify({ 74 | 'editor.semanticHighlighting.enabled': this.config.editorOptions['semanticHighlighting.enabled'] 75 | }); 76 | await this.updateUserConfiguration(json); 77 | } 78 | this.logger?.info('Init of Classic App was completed.'); 79 | } 80 | 81 | disposeApp(): void { 82 | this.disposeEditor(); 83 | this.disposeDiffEditor(); 84 | } 85 | 86 | isAppConfigDifferent(orgConfig: EditorAppConfigClassic, config: EditorAppConfigClassic, includeModelData: boolean): boolean { 87 | let different = false; 88 | if (includeModelData) { 89 | different = isModelUpdateRequired(orgConfig, config) !== ModelUpdateType.NONE; 90 | } 91 | type ClassicKeys = keyof typeof orgConfig; 92 | const propsClassic = ['useDiffEditor', 'domReadOnly', 'readOnly', 'awaitExtensionReadiness', 'overrideAutomaticLayout', 'editorOptions', 'diffEditorOptions', 'languageDef', 'languageExtensionConfig', 'theme', 'themeData']; 93 | const propCompareClassic = (name: string) => { 94 | return !isEqual(orgConfig[name as ClassicKeys], config[name as ClassicKeys]); 95 | }; 96 | different = different || propsClassic.some(propCompareClassic); 97 | return different; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/README.md: -------------------------------------------------------------------------------- 1 | # Monaco Editor and Monaco Languageclient Wrapper 2 | 3 | This packages provides a wrapped `monaco-editor` with or without language support (main package export). The `monaco-languageclient` can be activated to connect to a language server either via jsonrpc over a websocket to an external server process or via language server protocol for browser where the language server runs in a web worker. 4 | 5 | ## Getting Started 6 | 7 | We recommend using [Volta](https://volta.sh/) to ensure your node & npm are on known good versions. 8 | 9 | If you have node.js LTS available, then from the root of the project run: 10 | 11 | ```bash 12 | npm i 13 | npm run build 14 | ``` 15 | 16 | This will clean, compile and build a bundle of the `monaco-editor-wrapper`, which you can reference in your own projects. For examples, you can see the top-level [README](../../README.md#getting-started) with details on running a local dev instance. 17 | 18 | ## Configuration 19 | 20 | With release >2.0.0, the configuration approach is completely revised. 21 | 22 | The `UserConfig` now contains everything and is passed to the `start` function of the wrapper along with the HTML element `monaco-editor` is bound to. 23 | 24 | [@codingame/monaco-vscode-api](https://github.com/CodinGame/monaco-vscode-api) implements the VSCode api and redirects calls to `monaco-editor`. It allows to add serivccs that are usually only available in VSCode and not with pure `monaco-editor`. 25 | `UserConfig` allows two possible configuration modes: 26 | 27 | - **Classic**: Configure `monaco-editor` as you would when using it directly, [see](./src/editorAppClassic.ts) 28 | - **Extended**: Configure `monaco-editor` like a VSCode extension, [see](./src/editorAppExtended.ts) 29 | 30 | [This](https://github.com/CodinGame/monaco-vscode-api#monaco-standalone-services) is the list of services defined by [@codingame/monaco-vscode-api](https://github.com/CodinGame/monaco-vscode-api). 31 | The following services are enabled by default in both editor modes: 32 | 33 | - layout 34 | - environment 35 | - extension 36 | - files 37 | - quickAccess 38 | - languages 39 | - model 40 | - configuration 41 | 42 | **Extended** mode adds the following and thereby disables monarch grammars: 43 | 44 | - theme 45 | - textmate 46 | 47 | If you want any further services than the ones initialized by default, you should use the **extended** mode as some service (like *theme* and *textmate*) are incompatible with the **classic** mode. 48 | 49 | Monarch grammars and themes can only be used in **classic** mode and textmate grammars and themes can only be used in **extended** mode. 50 | 51 | ## Usage 52 | 53 | Monaco Editor with TypeScript language support in web worker and relying on classic mode: 54 | 55 | ```ts 56 | import { MonacoEditorLanguageClientWrapper, UserConfig } from 'monaco-editor-wrapper'; 57 | import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution.js'; 58 | import 'monaco-editor/esm/vs/language/typescript/monaco.contribution.js'; 59 | 60 | // helper function for loading monaco-editor's own workers 61 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 62 | buildWorkerDefinition('./node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); 63 | 64 | // no top-level await 65 | const run = async () => { 66 | const wrapper = new MonacoEditorLanguageClientWrapper(); 67 | const code: `function sayHello(): string { 68 | return "Hello"; 69 | };`, 70 | const userConfig = { 71 | wrapperConfig: { 72 | editorAppConfig: { 73 | $type: 'classic', 74 | languageId: 'typescript', 75 | code, 76 | useDiffEditor: false, 77 | } 78 | } 79 | }; 80 | 81 | const htmlElement = document.getElementById('monaco-editor-root'); 82 | await wrapper.initAndStart(userConfig, htmlElement); 83 | } 84 | ``` 85 | 86 | ## Examples 87 | 88 | These are the examples specifically for `monaco-editor-wrapper` you find in the repository: 89 | 90 | - TypeScript editor worker using classic mode, [see](../examples/wrapper_ts.html) 91 | - Language client & web socket language server example using extended mode [see](../examples/wrapper_ws.html) It requires the json language server to run. Use `start:server:json` from [here](../examples/package.json) 92 | - Multiple editors using classic mode [see](../examples/wrapper_adv.html) 93 | - Langium statemachine language client and web worker based language server using extended mode [see](../examples/wrapper_statemachine.html) 94 | - Langium grammar language client and web worker based language server allowing to choose classic or extended mode [see](../examples/wrapper_langium.html) 95 | -------------------------------------------------------------------------------- /packages/examples/src/reactPython.tsx: -------------------------------------------------------------------------------- 1 | import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; 2 | import { whenReady as whenReadyPython } from '@codingame/monaco-vscode-python-default-extension'; 3 | import React from 'react'; 4 | import ReactDOM from 'react-dom/client'; 5 | import { MonacoEditorReactComp } from '@typefox/monaco-editor-react'; 6 | import { UserConfig } from 'monaco-editor-wrapper'; 7 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 8 | import { Uri, commands } from 'vscode'; 9 | import { MonacoLanguageClient } from 'monaco-languageclient'; 10 | 11 | buildWorkerDefinition('../../../../node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); 12 | 13 | /** 14 | * Code is intentionally incorrect - language server will pick this up on connection and highlight the error 15 | */ 16 | const code = `def main(): 17 | return pass`; 18 | 19 | const rootElem = document.getElementById('root')!; 20 | const userConfig: UserConfig = { 21 | languageClientConfig: { 22 | options: { 23 | name: 'Python Language Server Example', 24 | $type: 'WebSocket', 25 | host: 'localhost', 26 | port: 30001, 27 | path: 'pyright', 28 | extraParams: { 29 | authorization: 'UserAuth' 30 | }, 31 | secured: false, 32 | startOptions: { 33 | onCall: (languageClient?: MonacoLanguageClient ) => { 34 | setTimeout(()=>{ 35 | ['pyright.restartserver', 'pyright.organizeimports'].forEach((cmdName) => { 36 | commands.registerCommand(cmdName, (...args: unknown[]) => { 37 | languageClient?.sendRequest('workspace/executeCommand', { command: cmdName, arguments: args }); 38 | }); 39 | }); 40 | },250); 41 | }, 42 | reportStatus: true, 43 | } 44 | }, 45 | clientOptions: { 46 | documentSelector: ['python'], 47 | workspaceFolder: { 48 | index: 0, 49 | name: 'workspace', 50 | uri: Uri.parse('/workspace/') 51 | }, 52 | }, 53 | }, 54 | wrapperConfig: { 55 | serviceConfig: { 56 | userServices: { 57 | ...getKeybindingsServiceOverride() 58 | }, 59 | debugLogging: true 60 | }, 61 | editorAppConfig: { 62 | $type: 'extended', 63 | languageId: 'python', 64 | codeUri: '/workspace/python.py', 65 | awaitExtensionReadiness: [whenReadyPython], 66 | extensions: [{ 67 | config: { 68 | name: 'python-client', 69 | publisher: 'monaco-languageclient-project', 70 | version: '1.0.0', 71 | engines: { 72 | vscode: '^1.81.5' 73 | }, 74 | contributes: { 75 | languages: [{ 76 | id: 'python', 77 | extensions: ['.py', 'pyi'], 78 | aliases: ['python'], 79 | mimetypes: ['application/python'], 80 | }], 81 | commands: [{ 82 | command: 'pyright.restartserver', 83 | title: 'Pyright: Restart Server', 84 | category: 'Pyright' 85 | }, 86 | { 87 | command: 'pyright.organizeimports', 88 | title: 'Pyright: Organize Imports', 89 | category: 'Pyright' 90 | }], 91 | keybindings: [{ 92 | key: 'ctrl+k', 93 | command: 'pyright.restartserver', 94 | when: 'editorTextFocus' 95 | }] 96 | } 97 | } 98 | }], 99 | userConfiguration: { 100 | json: JSON.stringify({'workbench.colorTheme': 'Default Dark Modern'}) 101 | }, 102 | useDiffEditor: false, 103 | code: code, 104 | } 105 | } 106 | }; 107 | 108 | const onTextChanged = (text: string, isDirty: boolean) => { 109 | console.log(`Dirty? ${isDirty} Content: ${text}`); 110 | }; 111 | 112 | const comp = ; 120 | 121 | const root = ReactDOM.createRoot(rootElem); 122 | root.render(comp); 123 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@typescript-eslint/parser', 6 | env: { 7 | node: true, 8 | browser: true, 9 | es2022: true 10 | }, 11 | extends: [ 12 | 'eslint:recommended', 13 | 'plugin:@typescript-eslint/recommended' 14 | ], 15 | overrides: [ 16 | ], 17 | parserOptions: { 18 | ecmaVersion: 2022, 19 | sourceType: 'module' 20 | }, 21 | plugins: [ 22 | '@typescript-eslint' 23 | ], 24 | ignorePatterns: [ 25 | '**/{node_modules,dist,lib,out,bin}', 26 | '.eslintrc.js' 27 | ], 28 | rules: { 29 | // List of [ESLint rules](https://eslint.org/docs/rules/) 30 | 'arrow-parens': ['off', 'as-needed'], // do not force arrow function parentheses 31 | 'constructor-super': 'error', // checks the correct use of super() in sub-classes 32 | 'dot-notation': 'error', // obj.a instead of obj['a'] when possible 33 | 'eqeqeq': 'error', // ban '==', don't use 'smart' option! 34 | 'guard-for-in': 'error', // needs obj.hasOwnProperty(key) checks 35 | 'new-parens': 'error', // new Error() instead of new Error 36 | 'no-bitwise': 'error', // bitwise operators &, | can be confused with &&, || 37 | 'no-caller': 'error', // ECMAScript deprecated arguments.caller and arguments.callee 38 | 'no-cond-assign': 'error', // assignments if (a = '1') are error-prone 39 | 'no-debugger': 'error', // disallow debugger; statements 40 | 'no-eval': 'error', // eval is considered unsafe 41 | 'no-inner-declarations': 'off', // we need to have 'namespace' functions when using TS 'export =' 42 | 'no-labels': 'error', // GOTO is only used in BASIC ;) 43 | 'no-multiple-empty-lines': ['error', { 'max': 1 }], // two or more empty lines need to be fused to one 44 | 'no-new-wrappers': 'error', // there is no reason to wrap primitve values 45 | 'no-throw-literal': 'error', // only throw Error but no objects {} 46 | 'no-trailing-spaces': 'error', // trim end of lines 47 | 'no-unsafe-finally': 'error', // safe try/catch/finally behavior 48 | 'no-var': 'error', // use const and let instead of var 49 | 'space-before-function-paren': ['error', { // space in function decl: f() vs async () => {} 50 | 'anonymous': 'never', 51 | 'asyncArrow': 'always', 52 | 'named': 'never' 53 | }], 54 | 'semi': [2, 'always'], // Always use semicolons at end of statement 55 | 'quotes': [2, 'single', { 'avoidEscape': true }], // Prefer single quotes 56 | 'use-isnan': 'error', // isNaN(i) Number.isNaN(i) instead of i === NaN 57 | // List of [@typescript-eslint rules](https://github.com/typescript-eslint/typescript-eslint/tree/master/packages/eslint-plugin#supported-rules) 58 | '@typescript-eslint/adjacent-overload-signatures': 'error', // grouping same method names 59 | '@typescript-eslint/array-type': ['error', { // string[] instead of Array 60 | 'default': 'array-simple' 61 | }], 62 | '@typescript-eslint/ban-types': 'error', // bans types like String in favor of string 63 | '@typescript-eslint/indent': 'error', // consistent indentation 64 | '@typescript-eslint/no-explicit-any': 'error', // don't use :any type 65 | '@typescript-eslint/no-misused-new': 'error', // no constructors for interfaces or new for classes 66 | '@typescript-eslint/no-namespace': 'off', // disallow the use of custom TypeScript modules and namespaces 67 | '@typescript-eslint/no-non-null-assertion': 'off', // allow ! operator 68 | "@typescript-eslint/parameter-properties": "error", // no property definitions in class constructors 69 | '@typescript-eslint/no-unused-vars': ['error', { // disallow Unused Variables 70 | 'argsIgnorePattern': '^_' 71 | }], 72 | '@typescript-eslint/no-var-requires': 'error', // use import instead of require 73 | '@typescript-eslint/prefer-for-of': 'error', // prefer for-of loop over arrays 74 | '@typescript-eslint/prefer-namespace-keyword': 'error', // prefer namespace over module in TypeScript 75 | '@typescript-eslint/triple-slash-reference': 'error', // ban /// , prefer imports 76 | '@typescript-eslint/type-annotation-spacing': 'error' // consistent space around colon ':' 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/monaco-editor-react/README.md: -------------------------------------------------------------------------------- 1 | # React component for Monaco-Editor and Monaco Languageclient 2 | 3 | This packages provides a React component that it based on the [monaco-editor-wrapper](../monaco-editor-wrapper/). It behaves in nearly the same way as the monaco editor, with the primary difference being that you interact with it through a React component. 4 | 5 | The [monaco-languageclient](https://github.com/TypeFox/monaco-languageclient) can be activated to connect to a language server either via jsonrpc over a websocket to an exernal server process, or via the Language Server Protocol for the browser where the language server runs in a web worker. 6 | 7 | ## Getting Started 8 | 9 | We recommend using [Volta](https://volta.sh/) to ensure your node & npm are on known good versions. 10 | 11 | If you have node.js LTS available, then from the root of the project run: 12 | 13 | ```bash 14 | npm i 15 | npm run build 16 | ``` 17 | 18 | This will clean, compile and build a bundle of the monaco-editor-react component, which you can reference in your own projects. 19 | 20 | ## Usage 21 | 22 | You can import the monaco react component for easy use in an existing React project. Below you can see a quick example of a fully functional implementation in TypeScript. The react component uses the same `UserConfig` approach which is then applied to `monaco-editor-wrapper`. 23 | 24 | ```typescript 25 | import { MonacoEditorReactComp } from '@typefox/monaco-editor-react'; 26 | import { UserConfig } from 'monaco-editor-wrapper'; 27 | 28 | import 'monaco-editor/esm/vs/basic-languages/typescript/typescript.contribution.js'; 29 | import 'monaco-editor/esm/vs/language/typescript/monaco.contribution.js'; 30 | 31 | const userConfig: UserConfig = { 32 | htmlElement: document.getElementById('monaco-editor-root') as HTMLElement, 33 | wrapperConfig: { 34 | editorAppConfig: { 35 | $type: 'classic', 36 | languageId: 'typescript', 37 | code: `function sayHello(): string { 38 | return "Hello"; 39 | };`, 40 | useDiffEditor: false, 41 | theme: 'vs-dark' 42 | } 43 | } 44 | }; 45 | 46 | const comp = ; 53 | ``` 54 | 55 | ### Bundled Usage 56 | 57 | For special cases you might want the component to be processed in advance. For these cases we provide a pre-bundled version that you can reference instead, built using `npm run build:bundle`. This can be helpful if you're working within some other framework besides React (Hugo for example). 58 | 59 | ```ts 60 | import { MonacoEditorReactComp } from '@typefox/monaco-editor-react/bundle'; 61 | ``` 62 | 63 | ## Examples 64 | 65 | These are the examples specifically for `@typefox/monaco-editor-react` that you can find in the repository: 66 | 67 | - TypeScript editor worker using classic configuration [see](../examples/react_ts.html) 68 | - Langium statemachine language client and web worker based language server using the exact same user configuration as [wrapper statemachine](../examples/wrapper_statemachine.html), [see](../examples/react_statemachine.html) 69 | - Langium grammar language client and web worker based language server using vscode-api configuration [see](../examples/react_langium.html) 70 | 71 | ## Invoking Custom Commands 72 | 73 | *An experimental feature.* 74 | 75 | If you have hooked up this component to talk with a language server, then you also may want to invoke custom LSP commands. This can be helpful when you want to perform specific actions on the internal representation of your language, or when you want to expose some details about your language for use in your React application. This could include generator functionality, such that other parts of your application can interact with your language without knowledge of the language server's internals. 76 | 77 | Custom commands can be invoked by getting a reference to your Monaco component. This *breaks* the standard encapsulation that React is built on, so no guarantees this won't cause other issues with your React app. 78 | 79 | ```ts 80 | // based on the official React example for refs: 81 | // https://reactjs.org/docs/refs-and-the-dom.html#creating-refs 82 | 83 | class MyComponent extends React.Component { 84 | constructor(props) { 85 | super(props); 86 | this.myRef = React.createRef(); 87 | } 88 | 89 | render() { 90 | return ; 91 | } 92 | } 93 | ``` 94 | 95 | You can then access the `current` property of the ref to get a reference to your component. This can then be used to invoke the `executeCommands` function present in the component. 96 | 97 | ```ts 98 | this.myRef.current.executeCommand('myCustomCommand', args...); 99 | ``` 100 | 101 | This will return an instance of `Thenable`, which should contain the returned data of executing your custom command. As you can imagine, this is incredibly helpful for getting internal access for specific language handling, but without needing details about the internals of your language server to do it. 102 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/src/editorAppExtended.ts: -------------------------------------------------------------------------------- 1 | import type * as vscode from 'vscode'; 2 | import { IDisposable, editor } from 'monaco-editor'; 3 | import getThemeServiceOverride from '@codingame/monaco-vscode-theme-service-override'; 4 | import getTextmateServiceOverride from '@codingame/monaco-vscode-textmate-service-override'; 5 | import { whenReady as whenReadyTheme } from '@codingame/monaco-vscode-theme-defaults-default-extension'; 6 | import { EditorAppBase, EditorAppConfigBase, ModelUpdateType, isEqual, isModelUpdateRequired } from './editorAppBase.js'; 7 | import { registerExtension, IExtensionManifest, ExtensionHostKind } from 'vscode/extensions'; 8 | import { UserConfig } from './wrapper.js'; 9 | import { verifyUrlorCreateDataUrl } from './utils.js'; 10 | import { Logger } from './logger.js'; 11 | 12 | export type ExtensionConfig = { 13 | config: IExtensionManifest | object; 14 | filesOrContents?: Map; 15 | }; 16 | 17 | export type UserConfiguration = { 18 | json?: string; 19 | } 20 | 21 | export type EditorAppConfigExtended = EditorAppConfigBase & { 22 | $type: 'extended'; 23 | extensions?: ExtensionConfig[]; 24 | userConfiguration?: UserConfiguration; 25 | }; 26 | 27 | export type RegisterExtensionResult = { 28 | id: string; 29 | dispose(): Promise; 30 | whenReady(): Promise; 31 | } 32 | 33 | interface RegisterLocalExtensionResult extends RegisterExtensionResult { 34 | registerFileUrl: (path: string, url: string) => IDisposable; 35 | } 36 | 37 | export type RegisterLocalProcessExtensionResult = RegisterLocalExtensionResult & { 38 | getApi(): Promise; 39 | setAsDefaultApi(): Promise; 40 | }; 41 | 42 | /** 43 | * The vscode-apo monaco-editor app uses vscode user and extension configuration for monaco-editor. 44 | */ 45 | export class EditorAppExtended extends EditorAppBase { 46 | 47 | private config: EditorAppConfigExtended; 48 | private extensionRegisterResults: Map = new Map(); 49 | private logger: Logger | undefined; 50 | 51 | constructor(id: string, userConfig: UserConfig, logger?: Logger) { 52 | super(id); 53 | this.logger = logger; 54 | const userAppConfig = userConfig.wrapperConfig.editorAppConfig as EditorAppConfigExtended; 55 | this.config = this.buildConfig(userAppConfig) as EditorAppConfigExtended; 56 | this.config.extensions = userAppConfig.extensions ?? undefined; 57 | this.config.userConfiguration = userAppConfig.userConfiguration ?? undefined; 58 | } 59 | 60 | getConfig(): EditorAppConfigExtended { 61 | return this.config; 62 | } 63 | 64 | getExtensionRegisterResult(extensionName: string) { 65 | return this.extensionRegisterResults.get(extensionName); 66 | } 67 | 68 | override specifyServices(): editor.IEditorOverrideServices { 69 | return { 70 | ...getThemeServiceOverride(), 71 | ...getTextmateServiceOverride() 72 | }; 73 | } 74 | 75 | override async init() { 76 | // await all extensions that should be ready beforehand 77 | // always await theme extension 78 | const awaitReadiness = (this.config.awaitExtensionReadiness ?? []).concat(whenReadyTheme); 79 | await this.awaitReadiness(awaitReadiness); 80 | 81 | if (this.config.extensions) { 82 | const allPromises: Array> = []; 83 | for (const extensionConfig of this.config.extensions) { 84 | const manifest = extensionConfig.config as IExtensionManifest; 85 | const extRegResult = registerExtension(manifest, ExtensionHostKind.LocalProcess); 86 | this.extensionRegisterResults.set(manifest.name, extRegResult); 87 | if (extensionConfig.filesOrContents && Object.hasOwn(extRegResult, 'registerFileUrl')) { 88 | for (const entry of extensionConfig.filesOrContents) { 89 | (extRegResult as RegisterLocalExtensionResult).registerFileUrl(entry[0], verifyUrlorCreateDataUrl(entry[1])); 90 | } 91 | } 92 | allPromises.push(extRegResult.whenReady()); 93 | } 94 | await Promise.all(allPromises); 95 | } 96 | 97 | // buildConfig ensures userConfiguration is available 98 | await this.updateUserConfiguration(this.config.userConfiguration?.json); 99 | this.logger?.info('Init of Extended App was completed.'); 100 | } 101 | 102 | disposeApp(): void { 103 | this.disposeEditor(); 104 | this.disposeDiffEditor(); 105 | this.extensionRegisterResults.forEach((k) => k?.dispose()); 106 | } 107 | 108 | isAppConfigDifferent(orgConfig: EditorAppConfigExtended, config: EditorAppConfigExtended, includeModelData: boolean): boolean { 109 | let different = false; 110 | if (includeModelData) { 111 | different = isModelUpdateRequired(orgConfig, config) !== ModelUpdateType.NONE; 112 | } 113 | const propsExtended = ['useDiffEditor', 'domReadOnly', 'readOnly', 'awaitExtensionReadiness', 'overrideAutomaticLayout', 'editorOptions', 'diffEditorOptions', 'userConfiguration', 'extensions']; 114 | type ExtendedKeys = keyof typeof orgConfig; 115 | const propCompareExtended = (name: string) => { 116 | return !isEqual(orgConfig[name as ExtendedKeys], config[name as ExtendedKeys]); 117 | }; 118 | different = different || propsExtended.some(propCompareExtended); 119 | return different; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/test/languageClientWrapper.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from 'vitest'; 2 | import { createBaseConfig, createWrapper } from './helper.js'; 3 | 4 | describe('Test LanguageClientWrapper', () => { 5 | 6 | test('Not Running after construction', async () => { 7 | const wrapper = await createWrapper(createBaseConfig('extended')); 8 | 9 | const languageClientWrapper = wrapper.getLanguageClientWrapper(); 10 | expect(languageClientWrapper).toBeDefined(); 11 | 12 | expect(languageClientWrapper!.haveLanguageClient()).toBeFalsy(); 13 | expect(languageClientWrapper!.haveLanguageClientConfig()).toBeFalsy(); 14 | expect(languageClientWrapper!.isStarted()).toBeFalsy(); 15 | }); 16 | 17 | test('Constructor: no config', async () => { 18 | const wrapper = await createWrapper(createBaseConfig('extended')); 19 | 20 | const languageClientWrapper = wrapper.getLanguageClientWrapper(); 21 | expect(languageClientWrapper).toBeDefined(); 22 | 23 | console.log(window.MonacoEnvironment); 24 | expect(async () => { 25 | await languageClientWrapper!.start(); 26 | }).rejects.toEqual({ 27 | message: 'languageClientWrapper (undefined): Unable to start monaco-languageclient. No configuration was provided.', 28 | error: 'No error was provided.' 29 | }); 30 | }); 31 | 32 | test('Dispose: direct worker is cleaned up afterwards', async () => { 33 | 34 | /** 35 | * Helper to generate a quick worker from a function blob 36 | */ 37 | function createWorkerFromFunction(fn: () => void): Worker { 38 | return new Worker(URL.createObjectURL( 39 | new Blob([`(${fn.toString()})()`], { type: 'application/javascript' }) 40 | )); 41 | } 42 | 43 | // create a web worker to pass to the wrapper 44 | const worker = createWorkerFromFunction(() => { 45 | console.info('Hello'); 46 | }); 47 | 48 | // setup the wrapper 49 | const config = createBaseConfig('extended'); 50 | config.languageClientConfig = { 51 | options: { 52 | $type: 'WorkerDirect', 53 | worker 54 | } 55 | }; 56 | const wrapper = await createWrapper(config); 57 | 58 | const languageClientWrapper = wrapper.getLanguageClientWrapper(); 59 | expect(languageClientWrapper).toBeDefined(); 60 | 61 | // start up & verify (don't wait for start to finish, just roll past it, we only care about the worker) 62 | languageClientWrapper!.start(); 63 | expect(languageClientWrapper!.getWorker()).toBeTruthy(); 64 | 65 | // dispose & verify 66 | languageClientWrapper!.disposeLanguageClient(); 67 | expect(languageClientWrapper!.getWorker()).toBeDefined(); 68 | // no further way to verify post-terminate, but the worker should be disposed once no longer present 69 | }); 70 | 71 | test('Constructor: config', async () => { 72 | const config = createBaseConfig('extended'); 73 | config.languageClientConfig = { 74 | options: { 75 | $type: 'WebSocketUrl', 76 | url: 'ws://localhost:12345/Tester' 77 | } 78 | }; 79 | const wrapper = await createWrapper(config); 80 | 81 | const languageClientWrapper = wrapper.getLanguageClientWrapper(); 82 | expect(languageClientWrapper).toBeDefined(); 83 | 84 | expect(languageClientWrapper!.haveLanguageClientConfig()).toBeTruthy(); 85 | }); 86 | 87 | test('Start: unreachable url', async () => { 88 | const config = createBaseConfig('extended'); 89 | config.languageClientConfig = { 90 | options: { 91 | $type: 'WebSocketUrl', 92 | url: 'ws://localhost:12345/Tester', 93 | name: 'test-unreachable' 94 | } 95 | }; 96 | const wrapper = await createWrapper(config); 97 | 98 | const languageClientWrapper = wrapper.getLanguageClientWrapper(); 99 | expect(languageClientWrapper).toBeDefined(); 100 | 101 | expect(languageClientWrapper!.haveLanguageClientConfig()).toBeTruthy(); 102 | await expect(languageClientWrapper!.start()).rejects.toEqual({ 103 | message: 'languageClientWrapper (test-unreachable): Websocket connection failed.', 104 | error: 'No error was provided.' 105 | }); 106 | }); 107 | 108 | test('Only unreachable worker url', async () => { 109 | const prom = new Promise((_resolve, reject) => { 110 | const worker = new Worker('aBogusUrl'); 111 | 112 | worker.onerror = () => { 113 | reject('error'); 114 | }; 115 | }); 116 | await expect(prom).rejects.toEqual('error'); 117 | }); 118 | 119 | test('Start: unreachable worker url', async () => { 120 | const config = createBaseConfig('extended'); 121 | config.languageClientConfig = { 122 | options: { 123 | $type: 'WorkerConfig', 124 | url: new URL('http://localhost:20101'), 125 | type: 'classic' 126 | } 127 | }; 128 | const wrapper = await createWrapper(config); 129 | 130 | const languageClientWrapper = wrapper.getLanguageClientWrapper(); 131 | expect(languageClientWrapper).toBeDefined(); 132 | 133 | expect(languageClientWrapper!.haveLanguageClientConfig()).toBeTruthy(); 134 | await expect(languageClientWrapper!.start()).rejects.toEqual({ 135 | message: 'languageClientWrapper (unnamed): Illegal worker configuration detected. Potentially the url is wrong.', 136 | error: 'No error was provided.' 137 | }); 138 | }); 139 | 140 | }); 141 | -------------------------------------------------------------------------------- /packages/examples/src/wrapperAdvanced.ts: -------------------------------------------------------------------------------- 1 | import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; 2 | import 'monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.js'; 3 | import 'monaco-editor/esm/vs/language/typescript/monaco.contribution.js'; 4 | import { EditorAppConfigClassic, LanguageClientError, MonacoEditorLanguageClientWrapper, UserConfig } from 'monaco-editor-wrapper'; 5 | import { buildWorkerDefinition } from 'monaco-editor-workers'; 6 | 7 | buildWorkerDefinition('../../../node_modules/monaco-editor-workers/dist/workers', import.meta.url, false); 8 | 9 | const wrapper42 = new MonacoEditorLanguageClientWrapper(); 10 | const wrapper43 = new MonacoEditorLanguageClientWrapper(); 11 | const wrapper44 = new MonacoEditorLanguageClientWrapper(); 12 | 13 | const wrapper42Config: UserConfig = { 14 | id: '42', 15 | wrapperConfig: { 16 | serviceConfig: { 17 | userServices: { 18 | ...getKeybindingsServiceOverride() 19 | }, 20 | debugLogging: true 21 | }, 22 | editorAppConfig: { 23 | $type: 'classic', 24 | languageId: 'text/plain', 25 | useDiffEditor: true, 26 | codeOriginal: `This line is equal. 27 | This number is different 2002 28 | Misspeelled! 29 | Same again.`, 30 | code: `This line is equal. 31 | This number is different 2022 32 | Misspelled! 33 | Same again.` 34 | } 35 | }, 36 | languageClientConfig: { 37 | options: { 38 | $type: 'WebSocket', 39 | name: 'wrapper42 language client', 40 | host: 'localhost', 41 | port: 30000, 42 | path: 'sampleServer', 43 | secured: false 44 | } 45 | } 46 | }; 47 | 48 | const wrapper43Config: UserConfig = { 49 | id: '43', 50 | wrapperConfig: { 51 | serviceConfig: { 52 | userServices: { 53 | ...getKeybindingsServiceOverride() 54 | }, 55 | debugLogging: true 56 | }, 57 | editorAppConfig: { 58 | $type: 'classic', 59 | languageId: 'text/plain', 60 | useDiffEditor: true, 61 | codeOriginal: 'This line is equal.\nThis number is different 3022.\nMisspelled!Same again.', 62 | code: 'This line is equal.\nThis number is different 3002.\nMisspelled!Same again.', 63 | editorOptions: { 64 | lineNumbers: 'off' 65 | }, 66 | diffEditorOptions: { 67 | lineNumbers: 'off' 68 | } 69 | } 70 | } 71 | }; 72 | 73 | const wrapper44Config: UserConfig = { 74 | id: '44', 75 | wrapperConfig: { 76 | serviceConfig: { 77 | userServices: { 78 | ...getKeybindingsServiceOverride() 79 | }, 80 | debugLogging: true 81 | }, 82 | editorAppConfig: { 83 | $type: 'classic', 84 | languageId: 'javascript', 85 | useDiffEditor: false, 86 | theme: 'vs-dark', 87 | code: `function logMe() { 88 | console.log('Hello monaco-editor-wrapper!'); 89 | };`, 90 | editorOptions: { 91 | minimap: { 92 | enabled: true 93 | } 94 | } 95 | } 96 | } 97 | }; 98 | 99 | const startWrapper42 = async () => { 100 | await wrapper42.initAndStart(wrapper42Config, document.getElementById('monaco-editor-root-42')); 101 | console.log('wrapper42 was started.'); 102 | }; 103 | 104 | const startWrapper43 = async () => { 105 | await wrapper43.initAndStart(wrapper43Config, document.getElementById('monaco-editor-root-43')); 106 | console.log('wrapper43 was started.'); 107 | }; 108 | const startWrapper44 = async () => { 109 | await wrapper44.initAndStart(wrapper44Config, document.getElementById('monaco-editor-root-44')); 110 | console.log('wrapper44 was started.'); 111 | 112 | }; 113 | 114 | const sleepOne = (milliseconds: number) => { 115 | setTimeout(async () => { 116 | alert(`Updating editors after ${milliseconds}ms`); 117 | 118 | await wrapper42.dispose(); 119 | wrapper42Config.languageClientConfig = undefined; 120 | const appConfig42 = wrapper42Config.wrapperConfig.editorAppConfig as EditorAppConfigClassic; 121 | appConfig42.languageId = 'javascript'; 122 | appConfig42.useDiffEditor = false; 123 | appConfig42.code = `function logMe() { 124 | console.log('Hello swap editors!'); 125 | };`; 126 | const w42Start = wrapper42.initAndStart(wrapper42Config, document.getElementById('monaco-editor-root-42')); 127 | 128 | const w43Start = wrapper43.updateDiffModel({ 129 | languageId: 'javascript', 130 | code: 'text 5678', 131 | codeOriginal: 'text 1234' 132 | }); 133 | 134 | await wrapper44.dispose(); 135 | const appConfig44 = wrapper44Config.wrapperConfig.editorAppConfig as EditorAppConfigClassic; 136 | appConfig44.languageId = 'text/plain'; 137 | appConfig44.useDiffEditor = true; 138 | appConfig44.codeOriginal = 'oh la la la!'; 139 | appConfig44.code = 'oh lo lo lo!'; 140 | // This affects all editors globally and is only effective 141 | // if it is not in contrast to one configured later 142 | appConfig44.theme = 'vs-light'; 143 | const w44Start = wrapper44.initAndStart(wrapper44Config, document.getElementById('monaco-editor-root-44')); 144 | 145 | await w42Start; 146 | console.log('Restarted wrapper42.'); 147 | await w43Start; 148 | console.log('Updated diffmodel of wrapper43.'); 149 | await w44Start; 150 | console.log('Restarted wrapper44.'); 151 | }, milliseconds); 152 | }; 153 | 154 | const sleepTwo = (milliseconds: number) => { 155 | setTimeout(async () => { 156 | alert(`Updating last editor after ${milliseconds}ms`); 157 | 158 | await wrapper44.dispose(); 159 | const appConfig44 = wrapper44Config.wrapperConfig.editorAppConfig as EditorAppConfigClassic; 160 | appConfig44.useDiffEditor = false; 161 | appConfig44.theme = 'vs-dark'; 162 | await wrapper44.initAndStart(wrapper44Config, document.getElementById('monaco-editor-root-44')); 163 | console.log('Restarted wrapper44.'); 164 | }, milliseconds); 165 | }; 166 | 167 | try { 168 | await startWrapper43(); 169 | await startWrapper44(); 170 | try { 171 | await startWrapper42(); 172 | } catch (e) { 173 | console.log(`Catched expected connection error: ${(e as LanguageClientError).message}`); 174 | } 175 | 176 | // change the editors config, content or swap normal and diff editors after five seconds 177 | sleepOne(5000); 178 | 179 | // change last editor to regular mode 180 | sleepTwo(10000); 181 | } catch (e) { 182 | console.error(e); 183 | } 184 | 185 | -------------------------------------------------------------------------------- /packages/examples/src/langium/content/example.langium: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Copyright 2021 TypeFox GmbH 3 | * This program and the accompanying materials are made available under the 4 | * terms of the MIT License, which is available in the project root. 5 | ******************************************************************************/ 6 | grammar LangiumGrammar 7 | 8 | entry Grammar: 9 | ( 10 | isDeclared?='grammar' name=ID ('with' usedGrammars+=[Grammar:ID] (',' usedGrammars+=[Grammar:ID])*)? 11 | (definesHiddenTokens?='hidden' '(' (hiddenTokens+=[AbstractRule:ID] (',' hiddenTokens+=[AbstractRule:ID])*)? ')')? 12 | )? 13 | imports+=GrammarImport* 14 | (rules+=AbstractRule | interfaces+=Interface | types+=Type)+; 15 | 16 | Interface: 17 | 'interface' name=ID 18 | ('extends' superTypes+=[AbstractType:ID] (',' superTypes+=[AbstractType:ID])*)? 19 | SchemaType; 20 | 21 | fragment SchemaType: 22 | '{' attributes+=TypeAttribute* '}' ';'?; 23 | 24 | TypeAttribute: 25 | name=FeatureName (isOptional?='?')? ':' type=TypeDefinition ';'?; 26 | 27 | TypeDefinition: UnionType; 28 | 29 | UnionType infers TypeDefinition: 30 | ArrayType ({infer UnionType.types+=current} ('|' types+=ArrayType)+)?; 31 | 32 | ArrayType infers TypeDefinition: 33 | ReferenceType ({infer ArrayType.elementType=current} '[' ']')? ; 34 | 35 | ReferenceType infers TypeDefinition: 36 | SimpleType | 37 | {infer ReferenceType} '@' referenceType=SimpleType; 38 | 39 | SimpleType infers TypeDefinition: 40 | '(' TypeDefinition ')' | 41 | {infer SimpleType} (typeRef=[AbstractType:ID] | primitiveType=PrimitiveType | stringType=STRING); 42 | 43 | PrimitiveType returns string: 44 | 'string' | 'number' | 'boolean' | 'Date' | 'bigint'; 45 | 46 | type AbstractType = Interface | Type | Action | ParserRule; 47 | 48 | Type: 49 | 'type' name=ID '=' type=TypeDefinition ';'?; 50 | 51 | AbstractRule: 52 | ParserRule | TerminalRule; 53 | 54 | GrammarImport: 55 | 'import' path=STRING ';'?; 56 | 57 | ParserRule: 58 | (entry?='entry' | fragment?='fragment')? 59 | RuleNameAndParams 60 | (wildcard?='*' | ('returns' (returnType=[AbstractType:ID] | dataType=PrimitiveType)) | inferredType=InferredType)? 61 | (definesHiddenTokens?='hidden' '(' (hiddenTokens+=[AbstractRule:ID] (',' hiddenTokens+=[AbstractRule:ID])*)? ')')? ':' 62 | definition=Alternatives ';'; 63 | 64 | InferredType: 65 | ( 'infer' | 'infers') name=ID; 66 | 67 | fragment RuleNameAndParams: 68 | name=ID ('<' (parameters+=Parameter (',' parameters+=Parameter)*)? '>')?; 69 | 70 | Parameter: 71 | name=ID; 72 | 73 | Alternatives infers AbstractElement: 74 | ConditionalBranch ({infer Alternatives.elements+=current} ('|' elements+=ConditionalBranch)+)?; 75 | 76 | ConditionalBranch infers AbstractElement: 77 | UnorderedGroup 78 | | {infer Group} '<' guardCondition=Disjunction '>' (elements+=AbstractToken)+; 79 | 80 | UnorderedGroup infers AbstractElement: 81 | Group ({infer UnorderedGroup.elements+=current} ('&' elements+=Group)+)?; 82 | 83 | Group infers AbstractElement: 84 | AbstractToken ({infer Group.elements+=current} elements+=AbstractToken+)?; 85 | 86 | AbstractToken infers AbstractElement: 87 | AbstractTokenWithCardinality | 88 | Action; 89 | 90 | AbstractTokenWithCardinality infers AbstractElement: 91 | (Assignment | AbstractTerminal) cardinality=('?'|'*'|'+')?; 92 | 93 | Action infers AbstractElement: 94 | {infer Action} '{' (type=[AbstractType:ID] | inferredType=InferredType) ('.' feature=FeatureName operator=('='|'+=') 'current')? '}'; 95 | 96 | AbstractTerminal infers AbstractElement: 97 | Keyword | 98 | RuleCall | 99 | ParenthesizedElement | 100 | PredicatedKeyword | 101 | PredicatedRuleCall | 102 | PredicatedGroup; 103 | 104 | Keyword: 105 | value=STRING; 106 | 107 | RuleCall: 108 | rule=[AbstractRule:ID] ('<' arguments+=NamedArgument (',' arguments+=NamedArgument)* '>')?; 109 | 110 | NamedArgument: 111 | ( parameter=[Parameter:ID] calledByName?='=')? 112 | ( value=Disjunction ); 113 | 114 | LiteralCondition: 115 | true?='true' | 'false'; 116 | 117 | Disjunction infers Condition: 118 | Conjunction ({infer Disjunction.left=current} '|' right=Conjunction)*; 119 | 120 | Conjunction infers Condition: 121 | Negation ({infer Conjunction.left=current} '&' right=Negation)*; 122 | 123 | Negation infers Condition: 124 | Atom | {infer Negation} '!' value=Negation; 125 | 126 | Atom infers Condition: 127 | ParameterReference | ParenthesizedCondition | LiteralCondition; 128 | 129 | ParenthesizedCondition infers Condition: 130 | '(' Disjunction ')'; 131 | 132 | ParameterReference: 133 | parameter=[Parameter:ID]; 134 | 135 | PredicatedKeyword infers Keyword: 136 | ('=>' | '->') value=STRING; 137 | 138 | PredicatedRuleCall infers RuleCall: 139 | ('=>' | '->') rule=[AbstractRule:ID] ('<' arguments+=NamedArgument (',' arguments+=NamedArgument)* '>')?; 140 | 141 | Assignment infers AbstractElement: 142 | {infer Assignment} ('=>' | '->')? feature=FeatureName operator=('+='|'='|'?=') terminal=AssignableTerminal; 143 | 144 | AssignableTerminal infers AbstractElement: 145 | Keyword | RuleCall | ParenthesizedAssignableElement | CrossReference; 146 | 147 | ParenthesizedAssignableElement infers AbstractElement: 148 | '(' AssignableAlternatives ')'; 149 | 150 | AssignableAlternatives infers AbstractElement: 151 | AssignableTerminal ({infer Alternatives.elements+=current} ('|' elements+=AssignableTerminal)+)?; 152 | 153 | CrossReference infers AbstractElement: 154 | {infer CrossReference} '[' type=[AbstractType] ((deprecatedSyntax?='|' | ':') terminal=CrossReferenceableTerminal )? ']'; 155 | 156 | CrossReferenceableTerminal infers AbstractElement: 157 | Keyword | RuleCall; 158 | 159 | ParenthesizedElement infers AbstractElement: 160 | '(' Alternatives ')'; 161 | 162 | PredicatedGroup infers Group: 163 | ('=>' | '->') '(' elements+=Alternatives ')'; 164 | 165 | ReturnType: 166 | name=(PrimitiveType | ID); 167 | 168 | TerminalRule: 169 | hidden?='hidden'? 'terminal' (fragment?='fragment' name=ID | name=ID ('returns' type=ReturnType)?) ':' 170 | definition=TerminalAlternatives 171 | ';'; 172 | 173 | TerminalAlternatives infers AbstractElement: 174 | TerminalGroup ({infer TerminalAlternatives.elements+=current} '|' elements+=TerminalGroup)*; 175 | 176 | TerminalGroup infers AbstractElement: 177 | TerminalToken ({infer TerminalGroup.elements+=current} elements+=TerminalToken+)?; 178 | 179 | TerminalToken infers AbstractElement: 180 | TerminalTokenElement cardinality=('?'|'*'|'+')?; 181 | 182 | TerminalTokenElement infers AbstractElement: 183 | CharacterRange | TerminalRuleCall | ParenthesizedTerminalElement | NegatedToken | UntilToken | RegexToken | Wildcard; 184 | 185 | ParenthesizedTerminalElement infers AbstractElement: 186 | '(' (lookahead=('?='|'?!'))? TerminalAlternatives ')'; 187 | 188 | TerminalRuleCall infers AbstractElement: 189 | {infer TerminalRuleCall} rule=[TerminalRule:ID]; 190 | 191 | NegatedToken infers AbstractElement: 192 | {infer NegatedToken} '!' terminal=TerminalTokenElement; 193 | 194 | UntilToken infers AbstractElement: 195 | {infer UntilToken} '->' terminal=TerminalTokenElement; 196 | 197 | RegexToken infers AbstractElement: 198 | {infer RegexToken} regex=RegexLiteral; 199 | 200 | Wildcard infers AbstractElement: 201 | {infer Wildcard} '.'; 202 | 203 | CharacterRange infers AbstractElement: 204 | {infer CharacterRange} left=Keyword ('..' right=Keyword)?; 205 | 206 | FeatureName returns string: 207 | 'current' | 'entry' | 'extends' | 'false' | 'fragment' | 'grammar' | 'hidden' | 'import' | 'interface' | 'returns' | 'terminal' | 'true' | 'type' | 'infer' | 'infers' | 'with' | PrimitiveType | ID; 208 | 209 | terminal ID: /\^?[_a-zA-Z][\w_]*/; 210 | terminal STRING: /"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/; 211 | terminal RegexLiteral returns string: /\/(?![*+?])(?:[^\r\n\[/\\]|\\.|\[(?:[^\r\n\]\\]|\\.)*\])+\/[a-z]*/; 212 | 213 | hidden terminal WS: /\s+/; 214 | hidden terminal ML_COMMENT: /\/\*[\s\S]*?\*\//; 215 | hidden terminal SL_COMMENT: /\/\/[^\n\r]*/; 216 | -------------------------------------------------------------------------------- /packages/monaco-editor-react/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { EditorAppClassic, EditorAppExtended, MonacoEditorLanguageClientWrapper, UserConfig, WorkerConfigDirect, WorkerConfigOptions } from 'monaco-editor-wrapper'; 2 | import { IDisposable } from 'monaco-editor'; 3 | import * as vscode from 'vscode'; 4 | import React, { CSSProperties } from 'react'; 5 | 6 | export type MonacoEditorProps = { 7 | style?: CSSProperties; 8 | className?: string; 9 | userConfig: UserConfig, 10 | onTextChanged?: (text: string, isDirty: boolean) => void; 11 | onLoad?: () => void; 12 | } 13 | 14 | export class MonacoEditorReactComp extends React.Component { 15 | 16 | private wrapper: MonacoEditorLanguageClientWrapper = new MonacoEditorLanguageClientWrapper(); 17 | private containerElement?: HTMLDivElement; 18 | private _subscription: IDisposable | null = null; 19 | private isRestarting?: Promise; 20 | private started: (value: void | PromiseLike) => void; 21 | 22 | constructor(props: T) { 23 | super(props); 24 | this.containerElement = undefined; 25 | } 26 | 27 | override async componentDidMount() { 28 | await this.handleReinit(); 29 | } 30 | 31 | protected async handleReinit() { 32 | await this.destroyMonaco(); 33 | await this.initMonaco(); 34 | await this.startMonaco(); 35 | } 36 | 37 | override async componentDidUpdate(prevProps: T) { 38 | const { userConfig } = this.props; 39 | const { wrapper } = this; 40 | 41 | const mustReInit = this.isReInitRequired(prevProps); 42 | 43 | if (mustReInit) { 44 | await this.handleReinit(); 45 | } else { 46 | // the function now ensure a model update is only required if something else than the code changed 47 | this.wrapper.updateModel(userConfig.wrapperConfig.editorAppConfig); 48 | 49 | const config = userConfig.wrapperConfig.editorAppConfig; 50 | const prevConfig = prevProps.userConfig.wrapperConfig.editorAppConfig; 51 | if (prevConfig.$type === 'classic' && config.$type === 'classic') { 52 | if (prevConfig.editorOptions !== config.editorOptions) { 53 | (wrapper.getMonacoEditorApp() as EditorAppClassic).updateMonacoEditorOptions(config.editorOptions ?? {}); 54 | } 55 | } 56 | } 57 | } 58 | 59 | protected isReInitRequired(prevProps: T) { 60 | const { className, userConfig } = this.props; 61 | const { wrapper } = this; 62 | 63 | if (prevProps.className !== className && this.containerElement) { 64 | this.containerElement.className = className ?? ''; 65 | } 66 | 67 | let mustReInit = false; 68 | const config = userConfig.wrapperConfig.editorAppConfig; 69 | const prevConfig = prevProps.userConfig.wrapperConfig.editorAppConfig; 70 | const prevWorkerOptions = prevProps.userConfig.languageClientConfig?.options; 71 | const currentWorkerOptions = userConfig.languageClientConfig?.options; 72 | const prevIsWorker = (prevWorkerOptions?.$type === 'WorkerDirect'); 73 | const currentIsWorker = (currentWorkerOptions?.$type === 'WorkerDirect'); 74 | const prevIsWorkerConfig = (prevWorkerOptions?.$type === 'WorkerConfig'); 75 | const currentIsWorkerConfig = (currentWorkerOptions?.$type === 'WorkerConfig'); 76 | 77 | // check if both are configs and the workers are both undefined 78 | if (prevIsWorkerConfig && prevIsWorker === undefined && currentIsWorkerConfig && currentIsWorker === undefined) { 79 | mustReInit = (prevWorkerOptions as WorkerConfigOptions).url !== (currentWorkerOptions as WorkerConfigOptions).url; 80 | // check if both are workers and configs are both undefined 81 | } else if (prevIsWorkerConfig === undefined && prevIsWorker && currentIsWorkerConfig === undefined && currentIsWorker) { 82 | mustReInit = (prevWorkerOptions as WorkerConfigDirect).worker !== (currentWorkerOptions as WorkerConfigDirect).worker; 83 | // previous was worker and current config is not or the other way around 84 | } else if (prevIsWorker && currentIsWorkerConfig || prevIsWorkerConfig && currentIsWorker) { 85 | mustReInit = true; 86 | } 87 | 88 | if (prevConfig.$type === 'classic' && config.$type === 'classic') { 89 | mustReInit = (wrapper?.getMonacoEditorApp() as EditorAppClassic).isAppConfigDifferent(prevConfig, config, false) === true; 90 | } else if (prevConfig.$type === 'extended' && config.$type === 'extended') { 91 | mustReInit = (wrapper?.getMonacoEditorApp() as EditorAppExtended).isAppConfigDifferent(prevConfig, config, false) === true; 92 | } 93 | 94 | return mustReInit; 95 | } 96 | 97 | override componentWillUnmount() { 98 | this.destroyMonaco(); 99 | } 100 | 101 | protected assignRef = (component: HTMLDivElement) => { 102 | this.containerElement = component; 103 | }; 104 | 105 | protected async destroyMonaco(): Promise { 106 | if (this.wrapper) { 107 | if (this.isRestarting) { 108 | await this.isRestarting; 109 | } 110 | try { 111 | await this.wrapper.dispose(); 112 | } catch { 113 | // The language client may throw an error during disposal. 114 | // This should not prevent us from continue working. 115 | } 116 | } 117 | if (this._subscription) { 118 | this._subscription.dispose(); 119 | } 120 | } 121 | 122 | protected async initMonaco() { 123 | const { 124 | userConfig 125 | } = this.props; 126 | 127 | // block "destroyMonaco" until start is complete 128 | this.isRestarting = new Promise((resolve) => { 129 | this.started = resolve; 130 | }); 131 | await this.wrapper.init(userConfig); 132 | } 133 | 134 | protected async startMonaco() { 135 | const { 136 | className, 137 | onLoad, 138 | } = this.props; 139 | 140 | if (this.containerElement) { 141 | this.containerElement.className = className ?? ''; 142 | 143 | await this.wrapper.start(this.containerElement); 144 | this.started(); 145 | this.isRestarting = undefined; 146 | 147 | // once awaiting isStarting is done onLoad is called if available 148 | onLoad?.(); 149 | 150 | this.handleOnTextChanged(); 151 | } 152 | } 153 | 154 | private handleOnTextChanged() { 155 | const { 156 | userConfig, 157 | onTextChanged 158 | } = this.props; 159 | if (!onTextChanged) return; 160 | 161 | const model = this.wrapper.getModel(); 162 | if (model) { 163 | const verifyModelContent = () => { 164 | const modelText = model.getValue(); 165 | onTextChanged(modelText, modelText !== userConfig.wrapperConfig.editorAppConfig.code); 166 | }; 167 | 168 | this._subscription = model.onDidChangeContent(() => { 169 | verifyModelContent(); 170 | }); 171 | // do it initially 172 | verifyModelContent(); 173 | } 174 | } 175 | 176 | updateLayout(): void { 177 | this.wrapper.updateLayout(); 178 | } 179 | 180 | getEditorWrapper() { 181 | return this.wrapper; 182 | } 183 | 184 | /** 185 | * Executes a custom LSP command by name with args, and returns the result 186 | * @param cmd Command to execute 187 | * @param args Arguments to pass along with this command 188 | * @returns The result of executing this command in the language server 189 | */ 190 | executeCommand(cmd: string, ...args: unknown[]): Thenable { 191 | return vscode.commands.executeCommand(cmd, ...args); 192 | } 193 | 194 | override render() { 195 | return ( 196 |
201 | ); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/src/wrapper.ts: -------------------------------------------------------------------------------- 1 | import { editor, Uri } from 'monaco-editor'; 2 | import getConfigurationServiceOverride from '@codingame/monaco-vscode-configuration-service-override'; 3 | import { InitializeServiceConfig, MonacoLanguageClient, mergeServices } from 'monaco-languageclient'; 4 | import { EditorAppExtended, EditorAppConfigExtended } from './editorAppExtended.js'; 5 | import { EditorAppClassic, EditorAppConfigClassic } from './editorAppClassic.js'; 6 | import { ModelUpdate } from './editorAppBase.js'; 7 | import { LanguageClientConfig, LanguageClientWrapper } from './languageClientWrapper.js'; 8 | import { Logger, LoggerConfig } from './logger.js'; 9 | 10 | export type WrapperConfig = { 11 | serviceConfig?: InitializeServiceConfig; 12 | editorAppConfig: EditorAppConfigExtended | EditorAppConfigClassic; 13 | }; 14 | 15 | export type UserConfig = { 16 | id?: string; 17 | loggerConfig?: LoggerConfig; 18 | wrapperConfig: WrapperConfig; 19 | languageClientConfig?: LanguageClientConfig; 20 | } 21 | 22 | /** 23 | * This class is responsible for the overall ochestration. 24 | * It inits, start and disposes the editor apps and the language client (if configured) and provides 25 | * access to all required components. 26 | */ 27 | export class MonacoEditorLanguageClientWrapper { 28 | 29 | private id: string; 30 | 31 | private editorApp: EditorAppClassic | EditorAppExtended | undefined; 32 | private languageClientWrapper?: LanguageClientWrapper; 33 | private logger: Logger; 34 | private initDone = false; 35 | 36 | /** 37 | * Perform an isolated initialization of the user services and the languageclient wrapper (if used). 38 | */ 39 | async init(userConfig: UserConfig) { 40 | if (this.initDone) { 41 | throw new Error('init was already performed. Please call dispose first if you want to re-start.'); 42 | } 43 | if (userConfig.wrapperConfig.editorAppConfig.useDiffEditor && !userConfig.wrapperConfig.editorAppConfig.codeOriginal) { 44 | throw new Error('Use diff editor was used without a valid config.'); 45 | } 46 | // Always dispose old instances before start 47 | this.editorApp?.disposeApp(); 48 | 49 | this.id = userConfig.id ?? Math.floor(Math.random() * 101).toString(); 50 | this.logger = new Logger(userConfig.loggerConfig); 51 | const serviceConfig: InitializeServiceConfig = userConfig.wrapperConfig.serviceConfig ?? {}; 52 | if (userConfig.wrapperConfig.editorAppConfig.$type === 'classic') { 53 | this.editorApp = new EditorAppClassic(this.id, userConfig, this.logger); 54 | } else { 55 | this.editorApp = new EditorAppExtended(this.id, userConfig, this.logger); 56 | } 57 | 58 | // editorApps init their own service thats why they have to be created first 59 | this.configureServices(serviceConfig); 60 | 61 | this.languageClientWrapper = new LanguageClientWrapper(); 62 | await this.languageClientWrapper.init({ 63 | languageId: this.editorApp.getConfig().languageId, 64 | serviceConfig, 65 | languageClientConfig: userConfig.languageClientConfig, 66 | logger: this.logger 67 | }); 68 | 69 | this.initDone = true; 70 | } 71 | 72 | /** 73 | * Child classes are allow to override the services configuration implementation. 74 | */ 75 | protected configureServices(serviceConfig: InitializeServiceConfig) { 76 | // always set required services if not configured 77 | serviceConfig.userServices = serviceConfig.userServices ?? {}; 78 | const configureService = serviceConfig.userServices.configurationService ?? undefined; 79 | const workspaceConfig = serviceConfig.workspaceConfig ?? undefined; 80 | 81 | if (!configureService) { 82 | const mlcDefautServices = { 83 | ...getConfigurationServiceOverride() 84 | }; 85 | mergeServices(mlcDefautServices, serviceConfig.userServices); 86 | 87 | if (workspaceConfig) { 88 | throw new Error('You provided a workspaceConfig without using the configurationServiceOverride'); 89 | } 90 | } 91 | // adding the default workspace config if not provided 92 | if (!workspaceConfig) { 93 | serviceConfig.workspaceConfig = { 94 | workspaceProvider: { 95 | trusted: true, 96 | workspace: { 97 | workspaceUri: Uri.file('/workspace') 98 | }, 99 | async open() { 100 | return false; 101 | } 102 | } 103 | }; 104 | } 105 | mergeServices(this.editorApp?.specifyServices() ?? {}, serviceConfig.userServices); 106 | 107 | // overrule debug log flag 108 | serviceConfig.debugLogging = this.logger.isEnabled() && (serviceConfig.debugLogging || this.logger.isDebugEnabled()); 109 | } 110 | 111 | /** 112 | * Performs a full user configuration and the languageclient wrapper (if used) init and then start the application. 113 | */ 114 | async initAndStart(userConfig: UserConfig, htmlElement: HTMLElement | null) { 115 | await this.init(userConfig); 116 | await this.start(htmlElement); 117 | } 118 | 119 | /** 120 | * Does not perform any user configuration or other application init and just starts the application. 121 | */ 122 | async start(htmlElement: HTMLElement | null) { 123 | if (!this.initDone) { 124 | throw new Error('No init was performed. Please call init() before start()'); 125 | } 126 | if (!htmlElement) { 127 | throw new Error('No HTMLElement provided for monaco-editor.'); 128 | } 129 | 130 | this.logger.info(`Starting monaco-editor (${this.id})`); 131 | await this.editorApp?.init(); 132 | await this.editorApp?.createEditors(htmlElement); 133 | 134 | if (this.languageClientWrapper?.haveLanguageClientConfig()) { 135 | await this.languageClientWrapper.start(); 136 | } 137 | } 138 | 139 | isStarted(): boolean { 140 | // fast-fail 141 | if (!this.editorApp?.haveEditor()) { 142 | return false; 143 | } 144 | 145 | if (this.languageClientWrapper?.haveLanguageClient()) { 146 | return this.languageClientWrapper.isStarted(); 147 | } 148 | return true; 149 | } 150 | 151 | getMonacoEditorApp() { 152 | return this.editorApp; 153 | } 154 | 155 | getEditor(): editor.IStandaloneCodeEditor | undefined { 156 | return this.editorApp?.getEditor(); 157 | } 158 | 159 | getDiffEditor(): editor.IStandaloneDiffEditor | undefined { 160 | return this.editorApp?.getDiffEditor(); 161 | } 162 | 163 | getLanguageClientWrapper(): LanguageClientWrapper | undefined { 164 | return this.languageClientWrapper; 165 | } 166 | 167 | getLanguageClient(): MonacoLanguageClient | undefined { 168 | return this.languageClientWrapper?.getLanguageClient(); 169 | } 170 | 171 | getModel(original?: boolean): editor.ITextModel | undefined { 172 | return this.editorApp?.getModel(original); 173 | } 174 | 175 | getWorker(): Worker | undefined { 176 | return this.languageClientWrapper?.getWorker(); 177 | } 178 | 179 | async updateModel(modelUpdate: ModelUpdate): Promise { 180 | await this.editorApp?.updateModel(modelUpdate); 181 | } 182 | 183 | async updateDiffModel(modelUpdate: ModelUpdate): Promise { 184 | await this.editorApp?.updateDiffModel(modelUpdate); 185 | } 186 | 187 | public reportStatus() { 188 | const status: string[] = []; 189 | status.push('Wrapper status:'); 190 | status.push(`Editor: ${this.editorApp?.getEditor()}`); 191 | status.push(`DiffEditor: ${this.editorApp?.getDiffEditor()}`); 192 | return status; 193 | } 194 | 195 | /** 196 | * Disposes all application and editor resources, plus the languageclient (if used). 197 | */ 198 | async dispose(): Promise { 199 | this.editorApp?.disposeApp(); 200 | 201 | if (this.languageClientWrapper?.haveLanguageClient()) { 202 | await this.languageClientWrapper.disposeLanguageClient(false); 203 | this.editorApp = undefined; 204 | await Promise.resolve('Monaco editor and languageclient completed disposed.'); 205 | } 206 | else { 207 | await Promise.resolve('Monaco editor has been disposed.'); 208 | } 209 | this.initDone = false; 210 | } 211 | 212 | updateLayout() { 213 | this.editorApp?.updateLayout(); 214 | } 215 | 216 | } 217 | -------------------------------------------------------------------------------- /packages/examples/src/langium/config/langium.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Langium", 3 | "scopeName": "source.langium", 4 | "fileTypes": [ 5 | "langium" 6 | ], 7 | "patterns": [ 8 | { 9 | "include": "#regex" 10 | }, 11 | { 12 | "include": "#comments" 13 | }, 14 | { 15 | "name": "keyword.control.langium", 16 | "match": "\\b(current|entry|extends|fragment|grammar|hidden|import|infer|infers|interface|returns|terminal|type|with)\\b" 17 | }, 18 | { 19 | "name": "constant.language.langium", 20 | "match": "\\b(?i:true|false)\\b" 21 | }, 22 | { 23 | "name": "keyword.symbol.langium", 24 | "match": "(\\{|\\}|\\:|\\]|\\[|\\(|\\)|(\\??|\\+?)\\=|->|\\=>|<|>|\\,|\\*|\\+|\\@|\\||\\&|\\?|\\!|\\;)" 25 | }, 26 | { 27 | "name": "string.quoted.double.langium", 28 | "begin": "\"", 29 | "end": "\"", 30 | "patterns": [ 31 | { 32 | "include": "#string-character-escape" 33 | } 34 | ] 35 | }, 36 | { 37 | "name": "string.quoted.single.langium", 38 | "begin": "'", 39 | "end": "'", 40 | "patterns": [ 41 | { 42 | "include": "#string-character-escape" 43 | } 44 | ] 45 | } 46 | ], 47 | "repository": { 48 | "comments": { 49 | "patterns": [ 50 | { 51 | "name": "comment.block.langium", 52 | "begin": "/\\*", 53 | "beginCaptures": { 54 | "0": { 55 | "name": "punctuation.definition.comment.langium" 56 | } 57 | }, 58 | "end": "\\*/", 59 | "endCaptures": { 60 | "0": { 61 | "name": "punctuation.definition.comment.langium" 62 | } 63 | } 64 | }, 65 | { 66 | "begin": "(^\\s+)?(?=//)", 67 | "beginCaptures": { 68 | "1": { 69 | "name": "punctuation.whitespace.comment.leading.cs" 70 | } 71 | }, 72 | "end": "(?=$)", 73 | "name": "comment.line.langium" 74 | } 75 | ] 76 | }, 77 | "string-character-escape": { 78 | "name": "constant.character.escape.langium", 79 | "match": "\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|u\\{[0-9A-Fa-f]+\\}|[0-2][0-7]{0,2}|3[0-6][0-7]?|37[0-7]?|[4-7][0-7]?|.|$)" 80 | }, 81 | "regex": { 82 | "patterns": [ 83 | { 84 | "name": "string.regexp.ts", 85 | "begin": "(?|&&|\\|\\||\\*\\/)\\s*(\\/)(?![\\/*])(?=(?:[^\\/\\\\\\[\\()]|\\\\.|\\[([^\\]\\\\]|\\\\.)+\\]|\\(([^\\)\\\\]|\\\\.)+\\))+\\/([a-z]+|(?![\\/\\*])|(?=\\/\\*))(?!\\s*[a-zA-Z0-9_$]))", 86 | "beginCaptures": { 87 | "1": { 88 | "name": "punctuation.definition.string.begin.ts" 89 | } 90 | }, 91 | "end": "(/)([a-z]*)", 92 | "endCaptures": { 93 | "1": { 94 | "name": "punctuation.definition.string.end.ts" 95 | }, 96 | "2": { 97 | "name": "keyword.other.ts" 98 | } 99 | }, 100 | "patterns": [ 101 | { 102 | "include": "#regexp" 103 | } 104 | ] 105 | }, 106 | { 107 | "name": "string.regexp.ts", 108 | "begin": "((?", 139 | "captures": { 140 | "0": { 141 | "name": "keyword.other.back-reference.regexp" 142 | }, 143 | "1": { 144 | "name": "variable.other.regexp" 145 | } 146 | } 147 | }, 148 | { 149 | "name": "keyword.operator.quantifier.regexp", 150 | "match": "[?+*]|\\{(\\d+,\\d+|\\d+,|,\\d+|\\d+)\\}\\??" 151 | }, 152 | { 153 | "name": "keyword.operator.or.regexp", 154 | "match": "\\|" 155 | }, 156 | { 157 | "name": "meta.group.assertion.regexp", 158 | "begin": "(\\()((\\?=)|(\\?!)|(\\?<=)|(\\?))?", 194 | "beginCaptures": { 195 | "0": { 196 | "name": "punctuation.definition.group.regexp" 197 | }, 198 | "1": { 199 | "name": "punctuation.definition.group.no-capture.regexp" 200 | }, 201 | "2": { 202 | "name": "variable.other.regexp" 203 | } 204 | }, 205 | "end": "\\)", 206 | "endCaptures": { 207 | "0": { 208 | "name": "punctuation.definition.group.regexp" 209 | } 210 | }, 211 | "patterns": [ 212 | { 213 | "include": "#regexp" 214 | } 215 | ] 216 | }, 217 | { 218 | "name": "constant.other.character-class.set.regexp", 219 | "begin": "(\\[)(\\^)?", 220 | "beginCaptures": { 221 | "1": { 222 | "name": "punctuation.definition.character-class.regexp" 223 | }, 224 | "2": { 225 | "name": "keyword.operator.negation.regexp" 226 | } 227 | }, 228 | "end": "(\\])", 229 | "endCaptures": { 230 | "1": { 231 | "name": "punctuation.definition.character-class.regexp" 232 | } 233 | }, 234 | "patterns": [ 235 | { 236 | "name": "constant.other.character-class.range.regexp", 237 | "match": "(?:.|(\\\\(?:[0-7]{3}|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}))|(\\\\c[A-Z])|(\\\\.))\\-(?:[^\\]\\\\]|(\\\\(?:[0-7]{3}|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}))|(\\\\c[A-Z])|(\\\\.))", 238 | "captures": { 239 | "1": { 240 | "name": "constant.character.numeric.regexp" 241 | }, 242 | "2": { 243 | "name": "constant.character.control.regexp" 244 | }, 245 | "3": { 246 | "name": "constant.character.escape.backslash.regexp" 247 | }, 248 | "4": { 249 | "name": "constant.character.numeric.regexp" 250 | }, 251 | "5": { 252 | "name": "constant.character.control.regexp" 253 | }, 254 | "6": { 255 | "name": "constant.character.escape.backslash.regexp" 256 | } 257 | } 258 | }, 259 | { 260 | "include": "#regex-character-class" 261 | } 262 | ] 263 | }, 264 | { 265 | "include": "#regex-character-class" 266 | } 267 | ] 268 | }, 269 | "regex-character-class": { 270 | "patterns": [ 271 | { 272 | "name": "constant.other.character-class.regexp", 273 | "match": "\\\\[wWsSdDtrnvf]|\\." 274 | }, 275 | { 276 | "name": "constant.character.numeric.regexp", 277 | "match": "\\\\([0-7]{3}|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4})" 278 | }, 279 | { 280 | "name": "constant.character.control.regexp", 281 | "match": "\\\\c[A-Z]" 282 | }, 283 | { 284 | "name": "constant.character.escape.backslash.regexp", 285 | "match": "\\\\." 286 | } 287 | ] 288 | } 289 | } 290 | } -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | All notable changes to npm module [monaco-editor-wrapper](https://www.npmjs.com/package/monaco-editor-wrapper) are documented in this file. 4 | 5 | ## [3.6.0] - 2024-01-04 6 | 7 | - Updated to `monaco-languageclient@7.3.0` and `@codingame/monaco-vscode-api@1.85.0` / `@codingame/monaco-editor-treemended@1.85.0` (=`monaco-editor@0.45.0`). 8 | - How to modify client's capabilities? [#61](https://github.com/TypeFox/monaco-components/issues/61) 9 | - It is now possible to provide and fully override boh monaco-languageclient's clientOptions and connectionProvider 10 | 11 | ## [3.5.0] - 2023-11-07 12 | 13 | - Updated to `monaco-languageclient@7.2.0` and `monaco-vscode-api@1.83.16`. 14 | - Introduce capability to use a `MessagePort` as end-point for a languageclient 15 | - Use vitest v1 for tests 16 | - Move `initServices` from `MonacoEditorLanguageClientWrapper` to `LanguageClientWrapper` 17 | 18 | ## [3.4.0] - 2023-11-27 19 | 20 | - Updated to `monaco-languageclient@7.1.0` and `monaco-vscode-api@1.83.12`. 21 | - BREAKING: The postinstall step is removed. `monaco-languageclient` no longer patches an existing `monaco-editor` instead the package `@codingame/monaco-editor-treemended` is used. This requires that projects using this lib to enforce the correct `monaco-editor` with overrides (npm/pnpm) or resolutions (yarn) in the `package.json`. 22 | - Please see the [following explanation](https://github.com/TypeFox/monaco-languageclient/blob/main/README.md#new-with-v7-treemended-monaco-editor) 23 | - BREAKING: If you want to use `getConfigurationServiceOverride` you need to provide a `workspaceConfig` along the `userServices` in `initServices`. 24 | - Make subclassing MonacoEditorReactComp more easy [#58](https://github.com/TypeFox/monaco-components/issues/58) 25 | - Allow to init and start separately [#59](https://github.com/TypeFox/monaco-components/issues/59) 26 | - BREAKING: `start` no longer calls `init`. You need to call both or you use `initAndStart`. 27 | 28 | ## [3.3.0] - 2023-10-17 29 | 30 | - Properly separate and define classic and extended editor [#54](https://github.com/TypeFox/monaco-components/pull/54) 31 | - Renamed `EditorAppVscodeApi` to `EditorAppExtended` and `EditorAppConfigVscodeApi` to `EditorAppConfigExtended` 32 | - BREAKING: `$type` of `EditorAppConfigExtended` was changed from `vscodeApi` to `extended` 33 | - Updated to `monaco-languageclient@6.6.0` and `@codingame/monaco-vscode-api@1.83.2` and `monaco-editor@0.44.0` 34 | - Include all direct dependencies that the code uses in the `package.json`. 35 | 36 | ## [3.2.5] - 2023-10-13 37 | 38 | - New Problem in vite [#55](https://github.com/TypeFox/monaco-components/issues/55) 39 | - Fixed wrong imports 40 | 41 | ## [3.2.4] - 2023-10-05 42 | 43 | - Fixed/Implemented multiple `extensionRegisterResults` handling. 44 | 45 | ## [3.2.3] - 2023-10-04 46 | 47 | - Updated to `monaco-languageclient@6.5.1`. 48 | 49 | ## [3.2.2] - 2023-10-04 50 | 51 | - Fixed broken dependency definition 52 | 53 | ## [3.2.1] - 2023-09-29 54 | 55 | - Fixed `awaitExtensionReadiness` was not added to the base configuration during init. 56 | 57 | ## [3.2.0] - 2023-09-29 58 | 59 | - Updated to `monaco-languageclient@6.5.0`. Service init now relies on specific imports from `monaco-vscode-api` or user defined services. 60 | - Bundle sizes and content are reduced as unneeded dynamic imports are no longer contained. 61 | - Only keep user services in`initServices`. It requires to specifically import and use services provided by [monaco-vscode-api](https://github.com/CodinGame/monaco-vscode-api#monaco-standalone-services) 62 | - All *enable...* and *configure* type parameters have been removed from `monaco-languagclient` 63 | - languages and model services are always added by `monaco-languagclient` 64 | - layout, environment, extension, files and quickAccess servies are always added by `monaco-vscode-api` 65 | - Additional services need to be added to the package dependencies and imported and configured as shown in the [examples](https://github.com/TypeFox/monaco-languageclient#examples) 66 | - htmlElement is no longer part of UserConfig. Must be passed at start [#51](https://github.com/TypeFox/monaco-components/pull/51) 67 | - The HTMLElement must now be passed at `wrapper.start`. It is no longer part of the UserConfig. 68 | 69 | ## [3.1.0] - 2023-09-21 70 | 71 | - Make the extension register result accessible [#48](https://github.com/TypeFox/monaco-components/pull/48) 72 | - Improve configuration change detection [#47](https://github.com/TypeFox/monaco-components/pull/47) 73 | - semantic highlighting works with classic editor [#45](https://github.com/TypeFox/monaco-components/pull/45) 74 | 75 | ## [3.0.1] - 2023-09-07 76 | 77 | - Introduce `logger.ts` which allows to centrally enable / disable console logging of the library 78 | - Updated to `monaco-languageclient` `6.4.6` using `monaco-vscode-api` `1.81.7` 79 | - Ensure LanguageClientWrapper Cleans up Worker [#42](https://github.com/TypeFox/monaco-components/pull/42) 80 | 81 | ## [3.0.0] - 2023-08-31 82 | 83 | - New example and config changes [#37](https://github.com/TypeFox/monaco-components/pull/37) 84 | - languageClientWrapper: Reject start with unreachable web socket or web worker url [#34](https://github.com/TypeFox/monaco-components/pull/34) 85 | - Improve naming and improve api usage [#31](https://github.com/TypeFox/monaco-components/pull/31) 86 | - createUrl now allows web socket urls without port and path [#30](https://github.com/TypeFox/monaco-components/pull/30) 87 | - Updated to `monaco-languageclient` `6.4.5` using `monaco-vscode-api` `1.81.5` and `monaco-editor` `0.41.0` 88 | - languageClientWrapper: Reject start with unreachable web socket or web worker url [#34](https://github.com/TypeFox/monaco-components/pull/34) 89 | - Re-introduce `addMonacoStyles` via `monaco-editor-wrapper/styles` 90 | 91 | ## [2.1.1] - 2023-07-27 92 | 93 | - Allow to pass a uri via editor config and model update [#29](https://github.com/TypeFox/monaco-components/pull/29) 94 | 95 | ## [2.1.0] - 2023-06-16 96 | 97 | - Make worker handling more flexible [#27](https://github.com/TypeFox/monaco-components/pull/27) 98 | - Updated to `monaco-languageclient` `6.2.0` using `monaco-vscode-api` `1.79.3` and `monaco-editor` `0.39.0` 99 | 100 | ## [2.0.1] - 2023-06-12 101 | 102 | - Updated to `monaco-languageclient` `6.1.0` using `monaco-vscode-api` `1.79.1` and `monaco-editor` `0.38.0` 103 | 104 | ## [2.0.0] - 2023-06-02 105 | 106 | - Move away from "property" based configuration. `UserConfig` drives the complete monaco-editor configuration 107 | - Use global configuration object that is passed to the wrapper on start 108 | - The `monaco-editor-wrapper` and the new `@typefox/monaco-editor-react` component use the same configuration 109 | - The underlying monaco-editor can be configured in two ways now (wrapperConfig): 110 | - Classic: As before, but with one config object 111 | - Extension like: Using the extension based mechanism supplied by `monaco-vscode-api` 112 | - `monaco-languageclient` no longer exposes its own service. Now, we fully rely on services supplied by `monaco-vscode-api` 113 | - This means even if you decide to configure monaco-editor the classical way, you still require some basic services. This configuration is made inside `MonacoEditorLanguageClientWrapper`. Potential serviceConfig supplied when using vscode-api extension config is taken into account and combined then. 114 | - Re-configuration without full editor restart: 115 | - Updating the text model(s) is possible 116 | - Updating the monaco-editor options is possible 117 | - Restarting the languageclient is possible independently 118 | - Everything else requires a restart of the editor! 119 | 120 | ## [1.6.1] - 2023-03-23 121 | 122 | - Enable to update/restart the language client [#18](https://github.com/TypeFox/monaco-components/pull/18) 123 | - Add language client initialization options [#17](https://github.com/TypeFox/monaco-components/pull/17) 124 | 125 | ## [1.6.0] - 2022-12-21 126 | 127 | - Fix error in `disposeLanguageClient` preventing proper editor disposal 128 | - Expose `MessageTransports` configuration for accessing `MessageReader` and `MessageWriter` 129 | - Polish wrapper examples and add web socket example 130 | 131 | ## [1.5.0] - 2022-12-09 132 | 133 | - Remove `swapEditors` function. `startEditor` disposes old (diff)editor and starts a freshly configured one. 134 | 135 | ## [1.4.1] - 2022-12-01 136 | 137 | - Update to `monaco-languageclient@4.0.3` 138 | 139 | ## [1.4.0] - 2022-12-01 140 | 141 | - Export `vscode` (monaco-vscode-api) and `monaco` and remove getters 142 | - `automaticLayout` is configured as default 143 | - Fixed full configuration of editor and diff editor via `monacoEditorOptions` and `monacoDiffEditorOptions` 144 | - Changed the compile target and module to ES2022. 145 | - Update to `monaco-languageclient@4.0.2` 146 | - Update to `vscode-ws-jsonrpc@2.0.1` 147 | 148 | ## [1.3.2] - 2022-11-25 149 | 150 | - Merged css and ttf helper functions. Now ttf is included in css removing unknown path errors. 151 | 152 | ## [1.3.1] - 2022-11-03 153 | 154 | - Added get function to access `monaco-vscode-api` via `getVscode()` 155 | 156 | ## [1.3.0] - 2022-10-28 157 | 158 | - Bundling issues with imported workers from wrapper #[14](https://github.com/TypeFox/monaco-components/issues/14) 159 | - The new default is that no additional language support is contained. You can use another export to obtain them. The same applies to the bundles: 160 | - `monaco-editor-wrapper/allLanguages` 161 | - `monaco-editor-wrapper/bundle` 162 | - `monaco-editor-wrapper/bundle/allLanguages` 163 | 164 | ## [1.2.0] - 2022-09-22 165 | 166 | - Fix model URI path #[13](https://github.com/TypeFox/monaco-components/pull/13) 167 | - Added inmemory uri to diff editor as well 168 | - Re-worked the start/dispose/restart of the editor 169 | - Ensure model uris are unique for different languages and across multiple editor instances 170 | 171 | ## [1.1.0] - 2022-09-20 172 | 173 | - Allows to set `MessageReader` and `MessageWriter` for the web worker. This opens the possibility to emit and intercept messages. 174 | - It is now possible to configure and use a full language extension configuration 175 | - Added get functions to access to monaco, editor, diffEditor and languageClient or quickly get the editor content: 176 | - `getMonaco()` 177 | - `getEditor()` 178 | - `getDiffEditor()` 179 | - `getLanguageClient()` 180 | - `getMainCode()` 181 | - `getDiffCode()` 182 | 183 | ## [1.0.0] - 2022-09-08 184 | 185 | - Separated `monaco-editor-wrapper` from `monaco-editor-comp` 186 | -------------------------------------------------------------------------------- /packages/monaco-editor-wrapper/src/editorAppBase.ts: -------------------------------------------------------------------------------- 1 | import { editor, Uri } from 'monaco-editor'; 2 | import { createConfiguredEditor, createConfiguredDiffEditor, createModelReference, ITextFileEditorModel } from 'vscode/monaco'; 3 | import { IReference } from '@codingame/monaco-vscode-editor-service-override'; 4 | import { updateUserConfiguration as vscodeUpdateUserConfiguration } from '@codingame/monaco-vscode-configuration-service-override'; 5 | 6 | export type ModelUpdate = { 7 | languageId: string; 8 | code?: string; 9 | codeUri?: string; 10 | codeOriginal?: string; 11 | codeOriginalUri?: string; 12 | } 13 | 14 | export type EditorAppType = 'extended' | 'classic'; 15 | 16 | export type EditorAppConfigBase = ModelUpdate & { 17 | $type: EditorAppType; 18 | useDiffEditor: boolean; 19 | domReadOnly?: boolean; 20 | readOnly?: boolean; 21 | awaitExtensionReadiness?: Array<() => Promise>; 22 | overrideAutomaticLayout?: boolean; 23 | editorOptions?: editor.IStandaloneEditorConstructionOptions; 24 | diffEditorOptions?: editor.IStandaloneDiffEditorConstructionOptions; 25 | } 26 | 27 | export enum ModelUpdateType { 28 | NONE, 29 | CODE, 30 | MODEL 31 | } 32 | 33 | /** 34 | * This is the base class for both Monaco Ediotor Apps: 35 | * - EditorAppClassic 36 | * - EditorAppExtended 37 | * 38 | * It provides the generic functionality for both implementations. 39 | */ 40 | export abstract class EditorAppBase { 41 | 42 | private id: string; 43 | 44 | private editor: editor.IStandaloneCodeEditor | undefined; 45 | private diffEditor: editor.IStandaloneDiffEditor | undefined; 46 | 47 | private modelRef: IReference | undefined; 48 | private modelOriginalRef: IReference | undefined; 49 | 50 | constructor(id: string) { 51 | this.id = id; 52 | } 53 | 54 | protected buildConfig(userAppConfig: EditorAppConfigBase): EditorAppConfigBase { 55 | const config: EditorAppConfigBase = { 56 | $type: userAppConfig.$type, 57 | languageId: userAppConfig.languageId, 58 | code: userAppConfig.code ?? '', 59 | codeOriginal: userAppConfig.codeOriginal ?? '', 60 | useDiffEditor: userAppConfig.useDiffEditor === true, 61 | codeUri: userAppConfig.codeUri ?? undefined, 62 | codeOriginalUri: userAppConfig.codeOriginalUri ?? undefined, 63 | readOnly: userAppConfig.readOnly ?? false, 64 | domReadOnly: userAppConfig.domReadOnly ?? false, 65 | overrideAutomaticLayout: userAppConfig.overrideAutomaticLayout ?? true, 66 | awaitExtensionReadiness: userAppConfig.awaitExtensionReadiness ?? undefined, 67 | }; 68 | config.editorOptions = { 69 | ...userAppConfig.editorOptions, 70 | automaticLayout: userAppConfig.overrideAutomaticLayout ?? true 71 | }; 72 | config.diffEditorOptions = { 73 | ...userAppConfig.diffEditorOptions, 74 | automaticLayout: userAppConfig.overrideAutomaticLayout ?? true 75 | }; 76 | return config; 77 | } 78 | 79 | haveEditor() { 80 | return this.editor !== undefined || this.diffEditor !== undefined; 81 | } 82 | 83 | getEditor(): editor.IStandaloneCodeEditor | undefined { 84 | return this.editor; 85 | } 86 | 87 | getDiffEditor(): editor.IStandaloneDiffEditor | undefined { 88 | return this.diffEditor; 89 | } 90 | 91 | async createEditors(container: HTMLElement): Promise { 92 | if (this.getConfig().useDiffEditor) { 93 | this.diffEditor = createConfiguredDiffEditor(container, this.getConfig().diffEditorOptions); 94 | await this.updateDiffEditorModel(); 95 | } else { 96 | this.editor = createConfiguredEditor(container, this.getConfig().editorOptions); 97 | await this.updateEditorModel(); 98 | } 99 | } 100 | 101 | protected disposeEditor() { 102 | if (this.editor) { 103 | this.modelRef?.dispose(); 104 | this.editor.dispose(); 105 | this.editor = undefined; 106 | } 107 | } 108 | 109 | protected disposeDiffEditor() { 110 | if (this.diffEditor) { 111 | this.modelRef?.dispose(); 112 | this.modelOriginalRef?.dispose(); 113 | this.diffEditor.dispose(); 114 | this.diffEditor = undefined; 115 | } 116 | } 117 | 118 | getModel(original?: boolean): editor.ITextModel | undefined { 119 | if (this.getConfig().useDiffEditor) { 120 | return ((original === true) ? this.modelOriginalRef?.object.textEditorModel : this.modelRef?.object.textEditorModel) ?? undefined; 121 | } else { 122 | return this.modelRef?.object.textEditorModel ?? undefined; 123 | } 124 | } 125 | 126 | async updateModel(modelUpdate: ModelUpdate): Promise { 127 | if (!this.editor) { 128 | return Promise.reject(new Error('You cannot update the editor model, because the regular editor is not configured.')); 129 | } 130 | 131 | const modelUpdateType = isModelUpdateRequired(this.getConfig(), modelUpdate); 132 | 133 | if (modelUpdateType === ModelUpdateType.CODE) { 134 | this.updateAppConfig(modelUpdate); 135 | if (this.getConfig().useDiffEditor) { 136 | this.diffEditor?.getModifiedEditor().setValue(modelUpdate.code ?? ''); 137 | this.diffEditor?.getOriginalEditor().setValue(modelUpdate.codeOriginal ?? ''); 138 | } else { 139 | this.editor.setValue(modelUpdate.code ?? ''); 140 | } 141 | } else if (modelUpdateType === ModelUpdateType.MODEL) { 142 | this.updateAppConfig(modelUpdate); 143 | await this.updateEditorModel(); 144 | } 145 | return Promise.resolve(); 146 | } 147 | 148 | private async updateEditorModel(): Promise { 149 | const config = this.getConfig(); 150 | this.modelRef?.dispose(); 151 | 152 | const uri: Uri = this.getEditorUri('code'); 153 | this.modelRef = await createModelReference(uri, config.code) as unknown as IReference; 154 | this.modelRef.object.setLanguageId(config.languageId); 155 | if (this.editor) { 156 | this.editor.setModel(this.modelRef.object.textEditorModel); 157 | } 158 | } 159 | 160 | async updateDiffModel(modelUpdate: ModelUpdate): Promise { 161 | if (!this.diffEditor) { 162 | return Promise.reject(new Error('You cannot update the diff editor models, because the diffEditor is not configured.')); 163 | } 164 | if (isModelUpdateRequired(this.getConfig(), modelUpdate)) { 165 | this.updateAppConfig(modelUpdate); 166 | await this.updateDiffEditorModel(); 167 | } 168 | return Promise.resolve(); 169 | } 170 | 171 | private async updateDiffEditorModel(): Promise { 172 | const config = this.getConfig(); 173 | this.modelRef?.dispose(); 174 | this.modelOriginalRef?.dispose(); 175 | 176 | const uri: Uri = this.getEditorUri('code'); 177 | const uriOriginal: Uri = this.getEditorUri('codeOriginal'); 178 | 179 | const promises = []; 180 | promises.push(createModelReference(uri, config.code)); 181 | promises.push(createModelReference(uriOriginal, config.codeOriginal)); 182 | 183 | const refs = await Promise.all(promises); 184 | this.modelRef = refs[0] as unknown as IReference; 185 | this.modelRef.object.setLanguageId(config.languageId); 186 | this.modelOriginalRef = refs[1] as unknown as IReference; 187 | this.modelOriginalRef.object.setLanguageId(config.languageId); 188 | 189 | if (this.diffEditor && this.modelRef.object.textEditorModel !== null && this.modelOriginalRef.object.textEditorModel !== null) { 190 | this.diffEditor?.setModel({ 191 | original: this.modelOriginalRef!.object!.textEditorModel, 192 | modified: this.modelRef!.object!.textEditorModel 193 | }); 194 | } 195 | } 196 | 197 | private updateAppConfig(modelUpdate: ModelUpdate) { 198 | const config = this.getConfig(); 199 | config.languageId = modelUpdate.languageId; 200 | config.code = modelUpdate.code; 201 | config.codeUri = modelUpdate.codeUri; 202 | config.codeOriginal = modelUpdate.codeOriginal; 203 | config.codeOriginalUri = modelUpdate.codeOriginalUri; 204 | } 205 | 206 | getEditorUri(uriType: 'code' | 'codeOriginal') { 207 | const config = this.getConfig(); 208 | const uri = uriType === 'code' ? config.codeUri : config.codeOriginalUri; 209 | if (uri) { 210 | return Uri.parse(uri); 211 | } else { 212 | return Uri.parse(`/workspace/model${uriType === 'codeOriginal' ? 'Original' : ''}${this.id}.${config.languageId}`); 213 | } 214 | } 215 | 216 | updateLayout() { 217 | if (this.getConfig().useDiffEditor) { 218 | this.diffEditor?.layout(); 219 | } else { 220 | this.editor?.layout(); 221 | } 222 | } 223 | 224 | async awaitReadiness(awaitExtensionReadiness?: Array<() => Promise>) { 225 | if (awaitExtensionReadiness) { 226 | const allPromises: Array> = []; 227 | for (const awaitReadiness of awaitExtensionReadiness) { 228 | allPromises.push(awaitReadiness()); 229 | } 230 | return Promise.all(allPromises); 231 | } 232 | return Promise.resolve(); 233 | } 234 | 235 | updateMonacoEditorOptions(options: editor.IEditorOptions & editor.IGlobalEditorOptions) { 236 | this.getEditor()?.updateOptions(options); 237 | } 238 | 239 | async updateUserConfiguration(json?: string) { 240 | if (json) { 241 | return vscodeUpdateUserConfiguration(json); 242 | } 243 | return Promise.resolve(); 244 | } 245 | 246 | abstract init(): Promise; 247 | abstract specifyServices(): editor.IEditorOverrideServices; 248 | abstract getConfig(): EditorAppConfigBase; 249 | abstract disposeApp(): void; 250 | abstract isAppConfigDifferent(orgConfig: EditorAppConfigBase, config: EditorAppConfigBase, includeModelData: boolean): boolean; 251 | } 252 | 253 | export const isCodeUpdateRequired = (config: EditorAppConfigBase, modelUpdate: ModelUpdate) => { 254 | const updateRequired = (modelUpdate.code !== undefined && modelUpdate.code !== config.code) || modelUpdate.codeOriginal !== config.codeOriginal; 255 | return updateRequired ? ModelUpdateType.CODE : ModelUpdateType.NONE; 256 | }; 257 | 258 | export const isModelUpdateRequired = (config: EditorAppConfigBase, modelUpdate: ModelUpdate): ModelUpdateType => { 259 | const codeUpdate = isCodeUpdateRequired(config, modelUpdate); 260 | 261 | type ModelUpdateKeys = keyof typeof modelUpdate; 262 | const propsModelUpdate = ['languageId', 'codeUri', 'codeOriginalUri']; 263 | const propCompare = (name: string) => { 264 | return config[name as ModelUpdateKeys] !== modelUpdate[name as ModelUpdateKeys]; 265 | }; 266 | const updateRequired = propsModelUpdate.some(propCompare); 267 | return updateRequired ? ModelUpdateType.MODEL : codeUpdate; 268 | }; 269 | 270 | /** 271 | * The check for equality relies on JSON.stringify for instances of type Object. 272 | * Everything else is directly compared. 273 | * In this context, the check for equality is sufficient. 274 | */ 275 | export const isEqual = (obj1: unknown, obj2: unknown) => { 276 | if (obj1 instanceof Object && obj2 instanceof Object) { 277 | return JSON.stringify(obj1) === JSON.stringify(obj2); 278 | } else { 279 | return obj1 === obj2; 280 | } 281 | }; 282 | --------------------------------------------------------------------------------