diff --git a/docs/guides/index.md b/docs/guides/index.md index 92d62b3f..9768c8e6 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -7,6 +7,7 @@ This section covers the general concepts and usage patterns for the Monaco Langu - **[Getting Started](./getting-started.md)** - Your first Monaco Language Client integration with a minimal working example - **[Configuration](./configuration.md)** - Understanding configuration options and how to customize your setup - **[Examples](./examples.md)** - Simple, practical examples demonstrating common integration patterns +- **[Langium Integration Guides](./langium/index.md)** - Guides for integrating & interacting with language servers from Langium-based DSLs - **[Troubleshooting](./troubleshooting.md)** - Common issues and how to resolve them ## Quick Overview @@ -74,5 +75,6 @@ Generally you should start with Extended Mode unless you have specific constrain - **New to the Monaco Language Client?** Start with [Getting Started](./getting-started.md) - **Need specific configuration help?** Check [Configuration](./configuration.md) - **Jump straight to Practical Usage?** Browse [Examples](./examples.md) +- **Looking to integrate a Langium DSL?** Read the [Langium Integration Guides](./langium/index.md) - **Upgrading from v9?** Follow the [Migration Guide](../migration.md) - **Facing issues?** Look into [Troubleshooting](./troubleshooting.md) diff --git a/docs/guides/langium/custom-notifications-requests.md b/docs/guides/langium/custom-notifications-requests.md new file mode 100644 index 00000000..401b01f9 --- /dev/null +++ b/docs/guides/langium/custom-notifications-requests.md @@ -0,0 +1,329 @@ +# Custom Notifications and Requests with Langium Language Servers + +What's great about the Language Server Protocol is that it defines a standard set of notifications and requests (diagnostics, completion, hover, etc.). These are fantastic, as they provide a common understanding of standard features that can be made available by most language servers. However, many language servers need to do a little bit more. Custom notifications and requests let us extend the communication between our Langium language server and the Monaco client for language-specific functionality. This comes up commonly when: + +- additional results are returned by the language server post-build or validation +- generated artifacts need to be produced on-demand +- client-side information about the workspace needs to be passed to the LS, which wouldn't be available otherwise + +With those cases in mind, this guide covers two patterns: + +- **Notifications**: one-way, fire-and-forget messages suited for continuous, cheap updates. These can originate from the language client or the language server +- **Requests**: round-trip messages with a response, suited for more expensive or on-demand operations + +The following sections will explain notifications and requests by using a general pattern first, and then a concrete MiniLogo implementation. + +## Prerequisites + +Before starting this guide, make sure you have: + +- Completed the [Running a Langium Language Server in the Browser](./running-langium-ls-in-browser.md) guide — you should have a working Langium LS running as a Web Worker, connected to Monaco in Extended Mode +- A Langium project with working generation or other output you want to consume on the client + +We'll continue using [MiniLogo](https://github.com/TypeFox/langium-minilogo) as the running example. MiniLogo is already set up with a generator that produces drawing commands from validated programs, and a request handler that can invoke generation on a given MiniLogo program on demand. You can see the working [MiniLogo example](../../../packages/examples/src/langium/langium-dsl/minilogo/) in this repository for the complete client-side integration. + +## Notifications (Server to Client) + +Notifications are one-way messages — the sender fires them and doesn't wait for a response. They're ideal for pushing continuous updates from the language server to the client whenever something changes, like re-generated output after every document edit (for example, generated artifacts). + +### How It Works + +It's pretty simple actually. Notifications can be sent at any point once a connection is established with a client, but typically it makes sense to send notifications in response to something about the language server's processing. Commonly this means sending notifications with an additional payload after a document passes a certain build phase, such as post-validation (where we can determine if the document is OK and free of issues). + +1. The **language server** registers an `onBuildPhase` listener that fires after documents are validated (or after another build phase such as parsed or linked). +2. Inside that listener, the server sends a custom notification with whatever payload we need +3. The **client** listens for that notification type and acts on the payload + +### Generic Pattern + +#### Server Side + +In our Langium language server's browser entry point (`main-browser.ts`), we hook into the document builder's build phase to send notifications after validation (or another step) completes. We do this after `startLanguageServer` has been called, using the `shared` services: + +```ts +import { DocumentState, type LangiumDocument } from 'langium'; +import { + Diagnostic, + NotificationType +} from 'vscode-languageserver/browser'; + +// define the notification type and its payload shape +type DocumentChangePayload = { + uri: string; + content: string; + diagnostics: Diagnostic[]; +}; +const documentChangeNotification = new NotificationType( + 'browser/DocumentChange' +); + +// listen for documents that have completed validation +shared.workspace.DocumentBuilder.onBuildPhase( + DocumentState.Validated, + (documents: LangiumDocument[]) => { + for (const document of documents) { + // build whatever payload your language needs + // can be any json serializable object + const payload = buildPayload(document); + + // send the notification to the client + connection.sendNotification(documentChangeNotification, payload); + } + } +); +``` + +The notification type string (`'browser/DocumentChange'`) is arbitrary, it just needs to match between server and client. The payload can be any JSON-serializable object. Additionally, notifications can be sent in response to anything else we want to respond to, not just a build phase callback. + +#### Client Side + +On the client, we retrieve the `MonacoLanguageClient` instance from the `LanguageClientWrapper` and register a notification listener. We should do this after the language client has started up and is ready to receive a notification: + +```ts +// after lcWrapper.start() has completed... +const client = lcWrapper.getLanguageClient(); +if (!client) { + throw new Error('Unable to obtain language client'); +} + +// listen for custom notifications from the language server +client.onNotification('browser/DocumentChange', (params) => { + console.log('Document changed:', params.uri); + handleDocumentChange(params); +}); +``` + +The notification type string _must_ match the one used on the server side, whatever that may be. + +### MiniLogo Version + +#### Server Side + +MiniLogo's `onBuildPhase` listener generates drawing commands from a validated AST and sends them as the notification content. When there are errors, it sends an empty command list so the client can clear its output. The following is a pretty accurate outline of the `main-browser.ts` that does this. + +```ts +import { DocumentState, type LangiumDocument } from 'langium'; +import { Diagnostic, NotificationType } from 'vscode-languageserver/browser'; +import { Model } from './generated/ast.js'; +import { generateStatements } from '../generator/generator.js'; +import type { Command } from './minilogo-actions.js'; + +type DocumentChangePayload = { + uri: string; + content: string; + diagnostics: Diagnostic[]; +}; +const documentChangeNotification = new NotificationType( + 'browser/DocumentChange' +); + +// MiniLogo is the Langium-generated services object from createMiniLogoServices() +const jsonSerializer = MiniLogo.serializer.JsonSerializer; + +shared.workspace.DocumentBuilder.onBuildPhase( + DocumentState.Validated, + (documents: LangiumDocument[]) => { + for (const document of documents) { + const model = document.parseResult.value as Model; + let commands: Command[] = []; + + // only generate commands when there are no errors + const hasErrors = document.diagnostics?.some((d) => d.severity === 1) ?? false; + if (!hasErrors) { + commands = generateStatements(model.stmts); + } + + // attach the generated commands to the model for serialization + (model as unknown as { $commands: Command[] }).$commands = commands; + + // send a notification with a model + commands attached + connection.sendNotification(documentChangeNotification, { + uri: document.uri.toString(), + content: jsonSerializer.serialize(model, { + sourceText: true, + textRegions: true + }), + diagnostics: document.diagnostics ?? [] + }); + } + } +); +``` + +#### Client Side + +Since the MiniLogo language server sends a notification on _every_ document change, the handler can retrieve the generated commands and render an image using those drawing commands. There's also a bit of debouncing to avoid excessive re-renders while the user is actively making changes: + +```ts +const client = lcWrapper.getLanguageClient(); +if (!client) { + throw new Error('Unable to obtain language client'); +} + +let pendingTimeout: ReturnType | null = null; +let running = false; + +client.onNotification('browser/DocumentChange', (params) => { + // skip if we're still processing the previous update + if (running) { + return; + } + + // clear any pending timeout to reset the debounce window + if (pendingTimeout) { + clearTimeout(pendingTimeout); + } + + // wait for edits to settle before processing + pendingTimeout = setTimeout(async () => { + running = true; + try { + const commands = JSON.parse(params.content).$commands; + // render the drawing commands (canvas, console, SVG, etc.) + await renderMiniLogoCommands(commands); + } finally { + running = false; + } + }, 200); +}); +``` + +This waits 200ms after the last change before proceeding to draw, and the `running` flag prevents overlapping renders. If our notification handler is cheap (e.g. logging), we can skip the debounce entirely and process every notification directly. This genuinely has some helpful implications when building up custom behavior on a language server. + +## Requests (Client to Server) + +Now that we've gone over Notifications, we can hop over to Requests. + +Requests are round-trip messages — the client sends a request and awaits a response from the server. They're suited for operations that are costly or otherwise should be triggered in response to some action (a button click, a menu command) rather than happening continuously. + +### How It Works + +The flow is quite similar to how notifications are set up, with only a couple minor differences. + +1. The **language server** registers a _handler_ for a custom command +2. The **client** sends a _request_ (typically via `workspace/executeCommand`) when the user triggers an action +3. The server processes the request and returns some result +4. The client receives the result and acts on it + +### Generic Pattern + +#### Server Side + +In our Langium language server, we need to register a custom command handler. There are a couple ways of doing this. One is by adding a custom command handler directly via the connection object. Alternatively, Langium provides an `ExecuteCommandHandler` service we can register commands with. Both approaches are valid, the second is more idiomatic Langium, but we'll be using the first since it's simpler to go over in this tutorial (and the latter is essentially an abstraction on top of this approach). + +So, going the direct request handler route, we typically do this after creating the language services and before invoking `startLanguageServer` in our `main-browser.ts` entry point for the LS: + +```ts +// register a handler for workspace/executeCommand requests +connection.onRequest('workspace/executeCommand', async (params) => { + if (params.command === 'myLanguage/generateOutput') { + const input = params.arguments?.[0]; + const result = await processInput(input); + return result; + } + + // return undefined for commands you don't handle, + // so other handlers or Langium defaults can process them + return undefined; +}); +``` + +A couple things to note from this snippet: + +- The command name (`'myLanguage/generateOutput'`) is arbitrary (similar to the notification), but a namespaced prefix can help to avoid collisions with other existing commands. +- `workspace/executeCommand` is a standard LSP request method. The handler technically intercepts _all_ executeCommand requests, so we dispatch on `params.command` and return `undefined` for anything we're not interested in handling. The Langium service equivalent already handles this part for us. + +#### Client Side + +On the client, we can use the language client's `sendRequest` method. This returns a promise that resolves with the server's response, simple as that: + +```ts +const client = lcWrapper.getLanguageClient(); +if (!client) { + throw new Error('Unable to obtain language client'); +} + +const result = await client.sendRequest('workspace/executeCommand', { + command: 'myLanguage/generateOutput', + arguments: ['some input data'] +}); + +console.log('Server responded:', result); +``` + +The command name and arguments need to match what the server expects, same as for notifications. + +### MiniLogo Version + +#### Server Side + +On the MiniLogo side, it registers a command handler that accepts a MiniLogo program as input, parses, validates it, and then returns the generated drawing commands: + +```ts +connection.onRequest('workspace/executeCommand', async (params) => { + if (params.command === 'minilogo/generateCommands') { + const program = params.arguments?.[0] as string; + // parse, validate, and generate commands for the given program + const commands = await generateFromSource(program); + return { commands }; + } + return undefined; +}); +``` + +This allows the client to request generation for arbitrary programs, not just the one currently in the editor. +It also allows the client to request generation on demand, such as when the user clicks a button (more on that in a second). + +#### Client Side + +A practical use case is a "Generate" button that sends a default (or the current) MiniLogo program to the server and renders the result: + +```ts +// simple program that draws a white diamond +const defaultProgram = `def diamond() { + move(100, 0) + pen(down) + move(100, 100) + move(-100, 100) + move(-100, -100) + move(100, -100) + pen(up) +} +color(white) +diamond() +`; + +// some quick event listener code to respond to button clicks +document.getElementById('generate-button')?.addEventListener('click', async () => { + const result = await client.sendRequest('workspace/executeCommand', { + command: 'minilogo/generateCommands', + arguments: [defaultProgram] + }); + + if (result?.commands) { + renderMiniLogoCommands(result.commands); + } +}); +``` + +This assumes `client` was obtained from `lcWrapper.getLanguageClient()` during startup (following the pattern above). Overall, this approach works pretty well for actions that are expensive or should only happen on an as-needed basis, rather than in response to every document change. + +## Notifications vs. Requests + +The following is a quick table to summarize when it makes sense to use notifications or requests, in terms of their tradeoffs. + +| | Notifications | Requests | +|---|---|---| +| **Direction** | One-way | Round trip | +| **Response** | None (fire-and-forget) | Awaited response | +| **Use case** | Continuous, semi-frequent updates | On-demand, as needed | +| **Example** | Push logs or generated output | Generate output from a program on click | + +Both patterns can go in either direction, i.e clients can send notifications, and servers can send requests. LSP itself uses both directions heavily (ex. `textDocument/didOpen` is a client-to-server notification, `window/showMessageRequest` is a server-to-client request). Depending on what your're trying to set up, you may want to flip things around. + +## Next Steps + +- See the working [MiniLogo example](../../../packages/examples/src/langium/langium-dsl/minilogo/) in this repository, which uses the [`langium-minilogo`](https://github.com/TypeFox/langium-minilogo) package for a pre-built language server with notification and request support +- See the [statemachine example](../../../packages/examples/src/langium/statemachine/) for another complete implementation +- Explore the [vscode-languageclient API](https://github.com/microsoft/vscode-languageserver-node) for the full set of notification and request methods available on `MonacoLanguageClient` +- Check the [Configuration Guide](../configuration.md) for more on connection types and language client options diff --git a/docs/guides/langium/index.md b/docs/guides/langium/index.md new file mode 100644 index 00000000..2739bedf --- /dev/null +++ b/docs/guides/langium/index.md @@ -0,0 +1,10 @@ +# Langium Guides + +The following guides are for integrating Langium into a `monaco-languageclient` application in the web. Specifically, these guides focus on getting the language server hooked up to provide support in the editor, as well as walking through how to setup notification & request dispatch/handling logic. If you're interested in setting up a Langium-based DSL in the web, then these are the guides for you! + +These guides assume you have already read the [Getting Started](../getting-started.md) guide here, as well as the [Langium Getting Started Guides](https://langium.org/docs/introduction/) (or equivalent understanding), starting from the introduction and into the tutorials a bit, and that you already have a working Langium language. + +For a concrete, runnable example of all the concepts covered in these guides, see the [MiniLogo example](../../../packages/examples/src/langium/langium-dsl/minilogo/) in this repository. It demonstrates a complete Langium DSL running in the browser with Monaco, using the [`langium-minilogo`](https://github.com/TypeFox/langium-minilogo) package. + +- [Running a Langium DSL in the Browser](/docs/guides/langium/running-langium-ls-in-browser.md) goes over how to get a Langium-based DSL working in the web with the monaco-languageclient. Effectively this goes over how to prepare & bundle a Langium-based DSL's language server, so it can be run in a web worker & connected to. +- [Custom Notifications and Requests with Langium Language Servers](./custom-notifications-requests.md) goes over how to interface with your language server to handle and send custom notifications & requests. This is useful for doing things like generation in the web. diff --git a/docs/guides/langium/running-langium-ls-in-browser.md b/docs/guides/langium/running-langium-ls-in-browser.md new file mode 100644 index 00000000..413722a4 --- /dev/null +++ b/docs/guides/langium/running-langium-ls-in-browser.md @@ -0,0 +1,473 @@ +# Running a Langium Language Server in the Browser + +This guide walks through getting a Langium-based language server (LS) running in the browser as a Web Worker, and connected to Monaco via the `monaco-languageclient`. Each of these sections explains the general pattern first, and then follows with a concrete MiniLogo implementation to help cement the idea. + +## Before we get started... + +We need to make sure we've got the following first: + +- An existing Langium project with a working language server (see the [Langium docs](https://langium.org/docs/introduction/) if you need to create one) +- Completed the [Getting Started](../getting-started.md) guide here, with an understanding of the basics of working with the `monaco-languageclient` +- Familiarity with the [Configuration](../configuration.md) guide, especially Extended Mode, as we'll be actively leveraging that here + +We'll use a toy language called [MiniLogo](https://github.com/TypeFox/langium-minilogo) as the running example throughout this guide. MiniLogo is a Logo-like language (think penUp and penDown) with a variant built in Langium for demonstration purposes. We'll assume MiniLogo is already set up and working as a Langium project. These guides are structured so you can follow along with your own language and generalize the observations here, with a concrete application so things aren't _too_ abstract. + +## Overview + +At a high level, running a Langium language server in the browser involves three pieces: + +1. A **browser entry point**: usually named `main-browser.ts` in a Langium project that starts the language server using browser-compatible message readers/writers, sets up a browser-compatible FS abstraction, and ensures that no system-level dependencies sneak in +2. **Bundled worker**: the browser entry point bundled as a self-contained Web Worker, this should be a completely standalone language server +3. **Monaco client**: a `monaco-languageclient` application that loads the worker and connects to it + +## 1. Browser Entry Point + +Oftentimes, a language server uses stdin & stdout for the communication channel. In the browser, we need to replace those with `BrowserMessageReader` and `BrowserMessageWriter`. These allow us to perform the same communication over the Worker's message port. We also need to use an `EmptyFileSystem` instead of Node-backed file system, since we won't be working with a concrete file system in the browser. + +### Generic Pattern + +Create a file `src/language-server/main-browser.ts` in your Langium project: + +```ts +import { EmptyFileSystem } from 'langium'; +import { startLanguageServer } from 'langium/lsp'; +import { + BrowserMessageReader, + BrowserMessageWriter, + createConnection +} from 'vscode-languageserver/browser'; +// your services import will differ based on your language +import { createMyLanguageServices } from './my-language-module.js'; + +declare const self: DedicatedWorkerGlobalScope; + +// browser-specific setup: use message reader/writer instead of stdin/stdout +const messageReader = new BrowserMessageReader(self); +const messageWriter = new BrowserMessageWriter(self); + +const connection = createConnection(messageReader, messageWriter); + +// inject shared and language-specific services with EmptyFileSystem +const { shared } = createMyLanguageServices({ connection, ...EmptyFileSystem }); + +// start the language server +startLanguageServer(shared); +``` + +Again, there are some key differences from a Node-based entry point worth keeping in mind: + +- `BrowserMessageReader` / `BrowserMessageWriter` replace the Node stream-based readers/writers +- `EmptyFileSystem` replaces the Node file system, as our files will live in memory on the client side +- `DedicatedWorkerGlobalScope` is used to access the message port for communication with the main thread + +We'll also need to ensure our `tsconfig.json` includes the `WebWorker` lib so TypeScript can understand where the worker global scope comes from: + +```json +{ + "compilerOptions": { + "lib": ["ESNext", "DOM", "WebWorker"] + } +} +``` + +### MiniLogo Version + +For MiniLogo, the browser entry point looks the same structurally, only the service import differs: + +```ts +// ...same imports from before + +import { createMiniLogoServices } from './minilogo-module.js'; + +// ... same reader/write setup + connection setup + +const { shared } = createMiniLogoServices({ connection, ...EmptyFileSystem }); + +startLanguageServer(shared); +``` + +## 2. Bundling the Worker + +The browser entry point needs to be bundled into a single JavaScript file that can be loaded as a Web Worker. The bundle also needs to be free of any Node.js-specific modules (`fs`, `path`, `child_process`, etc.), since those won't be available in a browser context. + +### Using esbuild + +Next we'll want to add a build script to our Langium project's `package.json`, so we can easily produce a LS bundle on-demand. We can play with the options as need-be to suit our needs, but we should ensure that we at least bundle as esm: + +```json +{ + "scripts": { + "build:worker": "esbuild --minify ./out/language-server/main-browser.js --bundle --format=esm --outfile=./out/my-language-server-worker.js" + } +} +``` + +The outfile is somewhat arbitrary, and can be placed wherever it makes sense. We can also set up an `esbuild.mjs` file to orchestrate the same build steps, or perform this in addition to a pre-existing build configuration. + +As noted before, we're using `--format=esm` to produce an ES module worker. This is recommended and aligns with how `monaco-languageclient` load workers. + +### Using Vite + +If our client application uses Vite, we can also consume the LS without needing to pre-bundle it. Vite can bundle the worker inline when we reference it with `import.meta.url`: + +```ts +const worker = new Worker( + new URL('./path/to/main-browser.ts', import.meta.url), + { type: 'module', name: 'MyLanguageServer' } +); +``` + +Vite will automatically bundle the worker entry point and its dependencies at build time. This approach is actually used by most of the examples in this repository as well, so there's plenty of reference material here. + +### A note about MiniLogo + +The MiniLogo example in this repository takes a slightly different approach. We consume a **pre-built** language server worker from the [`langium-minilogo`](https://github.com/TypeFox/langium-minilogo) npm package rather than building from source. The package provides a `ls-web` export endpoint that gives us a pre-bundled ESM language server ready to load as a Web Worker: + +```ts +const worker = new Worker( + new URL('langium-minilogo/ls-web', import.meta.url), + { type: 'module', name: 'MiniLogo Language Server' } +); +``` + +This allows us to depend on the LS as a standalone artifact without needing to bundle it ourselves. However, rest assured the process outlined above is how that bundle is produced. You can check out the [langium-minilogo](https://github.com/TypeFox/langium-minilogo) project to see exactly how it's done, and you can see the working [MiniLogo example](../../../packages/examples/src/langium/langium-dsl/minilogo/) in this repository for the complete client-side integration. + +## 3. Monaco Client Configuration + +Alright, now that we have our LS ready, we can start setting up the Monaco editor side. We'll set up the `monaco-languageclient` in **Extended Mode**, which provides us with full VS Code-like functionality including TextMate grammars, keybindings, and service overrides, as outlined in [prior tutorials](../configuration.md). + +The client setup involves three parts: + +1. **`MonacoVscodeApiConfig`**: configures the VS Code API layer, theme, extensions, and editor workers +2. **`LanguageClientConfig`**: connects to the language server worker +3. **`EditorAppConfig`**: defines the initial code content to display + +### Loading the Worker + +First, we can create the Web Worker by pointing at our bundled language server, wherever that may be: + +```ts +const worker = new Worker( + new URL('./worker/my-language-server-bundle.js', import.meta.url), + { type: 'module', name: 'MyLanguageServer' } +); +``` + +In a Vite project, we can reference the source file directly as well, as noted in the prior section. + +Then we need to set up our message readers/writers for the language client to use. This matches with the transport layer we've built into our browser-based language server bundle. + +```ts +import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageclient/browser'; + +const reader = new BrowserMessageReader(worker); +const writer = new BrowserMessageWriter(worker); +``` + +### Syntax Highlighting with TextMate + +In Extended Mode (which we're using here), we use TextMate grammars for syntax highlighting (note that Monarch grammars are only supported in Classic Mode). We can register a TextMate grammar and language configuration like so: + +```ts +import type { MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; + +// load grammar and configuration +// in this case as raw strings, but they can be added in literally as well +import languageConfig from './config/language-configuration.json?raw'; +import textmateGrammar from './syntaxes/my-language.tmLanguage.json?raw'; + +// register the paths for config & textmate grammar +const extensionFilesOrContents = new Map(); +extensionFilesOrContents.set('/my-language-configuration.json', languageConfig); +extensionFilesOrContents.set('/my-language-grammar.json', textmateGrammar); + +const vscodeApiConfig: MonacoVscodeApiConfig = { + $type: 'extended', + // ... other config (see full example below) + extensions: [{ + config: { + name: 'my-language-example', + publisher: 'my-org', + version: '1.0.0', + engines: { vscode: '*' }, + contributes: { + languages: [{ + id: 'my-language', + extensions: ['.mylang'], + aliases: ['MyLanguage'], + // should match the path above + configuration: '/my-language-configuration.json' + }], + grammars: [{ + language: 'my-language', + scopeName: 'source.my-language', + // should match the path above + path: '/my-language-grammar.json' + }] + } + }, + filesOrContents: extensionFilesOrContents + }] +}; +``` + +In case we don't have one, Langium can generate a TextMate grammar for us. We just need to add the following to our `langium-config.json`, and run `npm run langium:generate` to get a default textmate grammar for our language. + +```json +{ + "textMate": { + "out": "syntaxes/my-language.tmLanguage.json" + } +} +``` + +Then run `npm run langium:generate` to produce a textmate grammar file. The generated grammar covers basic token types, and we can customize it further for improved highlighting. + +### Language Client Configuration + +Next, connect the language client to the worker using the `WorkerDirect` mode: + +```ts +import type { LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; + +const languageClientConfig: LanguageClientConfig = { + languageId: 'my-language', + clientOptions: { + documentSelector: ['my-language'] + }, + connection: { + options: { + $type: 'WorkerDirect', + worker + }, + messageTransports: { reader, writer } + } +}; +``` + +The `languageId` needs to match the language `id` we registered in the extension configuration above. At this point it can be helpful to pull it out into a constant to avoid mismatches. + +### Full Client Example + +And now we've got everything we need for the client. Here's the complete setup bringing all the pieces together. This follows the same patterns used by the statemachine and langium-dsl examples as well: + +```ts +import { LogLevel } from '@codingame/monaco-vscode-api'; +import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; +import { EditorApp, type EditorAppConfig } from 'monaco-languageclient/editorApp'; +import { LanguageClientWrapper, type LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import { + MonacoVscodeApiWrapper, + type MonacoVscodeApiConfig +} from 'monaco-languageclient/vscodeApiWrapper'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; +import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageclient/browser'; + +// load grammar and configuration as raw strings (vite ?raw import) +import languageConfig from './config/language-configuration.json?raw'; +import textmateGrammar from './syntaxes/my-language.tmLanguage.json?raw'; + +async function startEditor() { + // 1. create the language server worker + const worker = new Worker( + new URL('./worker/my-language-server.js', import.meta.url), + { type: 'module', name: 'MyLanguageServer' } + ); + const reader = new BrowserMessageReader(worker); + const writer = new BrowserMessageWriter(worker); + + // 2. register TextMate grammar as a virtual extension + const extensionFilesOrContents = new Map(); + extensionFilesOrContents.set('/my-language-configuration.json', languageConfig); + extensionFilesOrContents.set('/my-language-grammar.json', textmateGrammar); + + const languageId = 'my-language'; + + // 3. configure the VS Code API layer + const vscodeApiConfig: MonacoVscodeApiConfig = { + $type: 'extended', + viewsConfig: { + $type: 'EditorService', + htmlContainer: document.getElementById('monaco-editor-root')! + }, + logLevel: LogLevel.Debug, + serviceOverrides: { + ...getKeybindingsServiceOverride() + }, + monacoWorkerFactory: configureDefaultWorkerFactory, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.guides.bracketPairsHorizontal': 'active', + 'editor.wordBasedSuggestions': 'off', + 'editor.experimental.asyncTokenization': true + }) + }, + extensions: [{ + config: { + name: 'my-language-example', + publisher: 'my-org', + version: '1.0.0', + engines: { vscode: '*' }, + contributes: { + languages: [{ + id: languageId, + extensions: ['.mylang'], + aliases: ['MyLanguage'], + configuration: '/my-language-configuration.json' + }], + grammars: [{ + language: languageId, + scopeName: 'source.my-language', + path: '/my-language-grammar.json' + }] + } + }, + filesOrContents: extensionFilesOrContents + }] + }; + + // 4. configure the language client + const languageClientConfig: LanguageClientConfig = { + languageId, + clientOptions: { + documentSelector: [languageId] + }, + connection: { + options: { + $type: 'WorkerDirect', + worker + }, + messageTransports: { reader, writer } + } + }; + + // 5. configure the editor content + const editorAppConfig: EditorAppConfig = { + codeResources: { + modified: { + text: `// your default code here`, + uri: '/workspace/example.mylang' + } + } + }; + + // 6. start everything in order: API wrapper -> language client -> editor + const apiWrapper = new MonacoVscodeApiWrapper(vscodeApiConfig); + await apiWrapper.start(); + + const lcWrapper = new LanguageClientWrapper(languageClientConfig); + await lcWrapper.start(); + + const editorApp = new EditorApp(editorAppConfig); + await editorApp.start(document.getElementById('monaco-editor-root')!); + + console.log('Editor with language server is ready!'); +} + +startEditor().catch(console.error); +``` + +You'll likely have additional logic between steps, or organized in a structurally distinct fashion, but the setup order is important. + +### A note on the setup order: + +The order in which we initialize the wrapper, client, and editor app itself is important to note. + +1. **`MonacoVscodeApiWrapper.start()`**: initializes the VS Code API layer, registers extensions, sets up editor workers and themes. This needs to happen first so that everything under the hood is ready to go (especially the LS). +2. **`LanguageClientWrapper.start()`**: connects to the language server worker (which should be running now). The VS Code API also needs to be initialized before the language client can register itself. +3. **`EditorApp.start()`**: this creates the Monaco editor instance and loads the initial content. The editor is the last part because it relies on the API layer and language services being ready. + +We _can_ start up the editor app without the aforementioned steps, we'll just be missing the language support & other VS Code related functionality. + +### MiniLogo Client + +The MiniLogo client follows this pattern outlined above. The main differences are language-specific: the language ID is `minilogo`, the file extension is `.minilogo`, the TextMate grammar is generated from the MiniLogo Langium grammar, and the default editor content is a MiniLogo program: + +```ts +const editorAppConfig: EditorAppConfig = { + codeResources: { + modified: { + text: `def test() { + move(100, 0) + pen(down) + move(100, 100) + move(-100, 100) + move(-100, -100) + move(100, -100) + pen(up) +} +color(white) +test() +`, + uri: '/workspace/example.minilogo' + } + } +}; +``` + +## 4. HTML Setup + +Right, so now that we've outlined all the client logic set up, we need a home for it all. +To do that, we can set up a regular HTML page that provides the container element for Monaco to attach to. + +```html + + + + + My Language Editor + + + +
+ + + +``` + +If we're using Vite, this HTML file can be served directly as our index page. + +## 5. Running It + +With everything in place, we can generally run through the following steps from A-Z to get our setup working. + +1. **Build the Langium project** so the language server source is compiled to JS +2. **Bundle the worker** (if not using Vite's inline bundling) via our choice of bundler (esbuild in our example above) +3. **Start the dev server** (e.g., `npm run dev` with Vite) +4. **Open the page** in our browser + +We should see a Monaco editor with: + +- **Syntax highlighting** from the TextMate grammar +- **Diagnostics** showing up inline from the language server +- **Code completion** for our language's keywords, references, etc. +- **Hover information** if our language server provides it +- Anything else we're providing via our LS + +If the editor loads but language features aren't working, double check the browser console for errors. + +A few common issues you might run into: + +- **Worker failed to load**: verify the worker URL/path is correct and the bundle was built successfully +- **No syntax highlighting**: check that the TextMate grammar is registered correctly in the extension config, and that the language ID matches between the extension, language client, and editor content URI +- **Node.js modules in the bundle**: if the worker bundle fails, make sure the browser entry point doesn't import `fs`, `path`, or other Node-specific modules + +For more help, see the [Troubleshooting Guide](../troubleshooting.md). + +## Next Steps + +- Continue to the next guide on [Custom Notifications and Requests](./custom-notifications-requests.md) to learn how to extend communication with our language server beyond standard LSP +- See the working [MiniLogo example](../../../packages/examples/src/langium/langium-dsl/minilogo/) for a complete implementation that consumes a pre-built language server via the `langium-minilogo` package +- See the [statemachine example](../../../packages/examples/src/langium/statemachine/) and [langium-dsl example](../../../packages/examples/src/langium/langium-dsl/) for additional implementations that follow the build-from-source pattern diff --git a/index.html b/index.html index 4e9a693d..3fbd4c04 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,8 @@

Langium

Langium Grammar DSL (Language Server in Worker): [ Example Page ]
+ Langium MiniLogo (Language Server in Worker): [ Example Page ] +
Langium Statemachine (Language Server in Worker): [ Example Page | React Example Page ]
diff --git a/package-lock.json b/package-lock.json index c9ba3aff..bc337a42 100644 --- a/package-lock.json +++ b/package-lock.json @@ -166,6 +166,16 @@ "integrity": "sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==", "license": "Apache-2.0" }, + "node_modules/@codingame/esbuild-import-meta-url-plugin": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@codingame/esbuild-import-meta-url-plugin/-/esbuild-import-meta-url-plugin-1.0.3.tgz", + "integrity": "sha512-SAIOsWZteIWYAk04BCqQ+ugu8KiJm8EplQbMvxJl905uZv3r+21+XjtGg/zzrbxlVAY1cP+hGAG7z7sBPmy63w==", + "license": "ISC", + "dependencies": { + "esbuild": ">=0.19.x", + "import-meta-resolve": "^4.0.0" + } + }, "node_modules/@codingame/monaco-vscode-api": { "version": "34.0.1", "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-api/-/monaco-vscode-api-34.0.1.tgz", @@ -268,6 +278,108 @@ "@codingame/monaco-vscode-api": "34.0.1" } }, + "node_modules/@codingame/monaco-vscode-extension-api": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extension-api/-/monaco-vscode-extension-api-25.1.2.tgz", + "integrity": "sha512-SJW/YOhjo+9MXEyzMwQMUWdJVR3Llc6pTq5JQqs6Y30v73gTrpLqtzbd9FNdCuQR8S6bUk5ScH8GL4QrVuL5FA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-extensions-service-override": "25.1.2" + } + }, + "node_modules/@codingame/monaco-vscode-extension-api/node_modules/@codingame/monaco-vscode-api": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-api/-/monaco-vscode-api-25.1.2.tgz", + "integrity": "sha512-K04QcQA+Zb0KXucBAK/BGCT5dldiwIqdUbBQq7yuLvBLbof3cP1WSUuxasMHGYwM0MWyzIAsDtyAYMS7is8ZuA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-base-service-override": "25.1.2", + "@codingame/monaco-vscode-environment-service-override": "25.1.2", + "@codingame/monaco-vscode-extensions-service-override": "25.1.2", + "@codingame/monaco-vscode-files-service-override": "25.1.2", + "@codingame/monaco-vscode-host-service-override": "25.1.2", + "@codingame/monaco-vscode-layout-service-override": "25.1.2", + "@codingame/monaco-vscode-quickaccess-service-override": "25.1.2", + "@vscode/iconv-lite-umd": "0.7.1", + "dompurify": "3.3.1", + "jschardet": "3.1.4", + "marked": "14.0.0" + } + }, + "node_modules/@codingame/monaco-vscode-extension-api/node_modules/@codingame/monaco-vscode-base-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-base-service-override/-/monaco-vscode-base-service-override-25.1.2.tgz", + "integrity": "sha512-OwYs6h1ATUAeMmX+Q1c8esTG7GLMqniBs+fLEr1/9b/ciY485ArKo5UvrUxVPDtRNy/7F06vRW9IUCq9iKP14w==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/@codingame/monaco-vscode-extension-api/node_modules/@codingame/monaco-vscode-environment-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-environment-service-override/-/monaco-vscode-environment-service-override-25.1.2.tgz", + "integrity": "sha512-8GoD3lk0CN0dIMZOrZNS/i8RCaF1YSQ6nmrf+rqneOSHG9S382EnsZZD69d4+i7JnoeyttO7Kr9KH8WOhRV6OA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/@codingame/monaco-vscode-extension-api/node_modules/@codingame/monaco-vscode-extensions-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extensions-service-override/-/monaco-vscode-extensions-service-override-25.1.2.tgz", + "integrity": "sha512-rTTZW2biPxcg+JumhVf2L+38C5ptvNNxiJlwz39VfXFEh6qOHtAsIMy7vIXa0uGg5/y8DNp0SnOQJP/RKhLYZA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-files-service-override": "25.1.2" + } + }, + "node_modules/@codingame/monaco-vscode-extension-api/node_modules/@codingame/monaco-vscode-files-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-files-service-override/-/monaco-vscode-files-service-override-25.1.2.tgz", + "integrity": "sha512-TenLLAFIwY7keZFF8e3beUn7OVfnNINR5Noi4PVrjeeTcy6FuNH6Jghdul2JwpRAkvyJLdFMvomE2jlT6F03jQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/@codingame/monaco-vscode-extension-api/node_modules/@codingame/monaco-vscode-host-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-host-service-override/-/monaco-vscode-host-service-override-25.1.2.tgz", + "integrity": "sha512-lgaalpA9CUQW7i0bBwgBOK0DQNDvOo3QO3p6Rz6yVsHpgA4iMqq2d11dBDUKvuQSwIHPRu8CMHCqhQk/BQN/YA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/@codingame/monaco-vscode-extension-api/node_modules/@codingame/monaco-vscode-layout-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-layout-service-override/-/monaco-vscode-layout-service-override-25.1.2.tgz", + "integrity": "sha512-SxBGcMK3RgkGtUn7ZDl7dCoyNW0CWFQ/bfSRYUY06A0IA4JNS5jq1lhof57d0WXewm+5l8w1Spr/vMsfx1c9ig==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/@codingame/monaco-vscode-extension-api/node_modules/@codingame/monaco-vscode-quickaccess-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-quickaccess-service-override/-/monaco-vscode-quickaccess-service-override-25.1.2.tgz", + "integrity": "sha512-7IIrXnwHiF3w9d9p9kspEUz/LCibMLUztmRpGdZQfFtWBJw043q7rk8V1O42KdXr1hVg9IR5vfffwjy9nbiiUg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/@codingame/monaco-vscode-extension-api/node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/@codingame/monaco-vscode-extensions-service-override": { "version": "34.0.1", "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extensions-service-override/-/monaco-vscode-extensions-service-override-34.0.1.tgz", @@ -954,7 +1066,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -971,7 +1082,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -988,7 +1098,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1005,7 +1114,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1022,7 +1130,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1039,7 +1146,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1056,7 +1162,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1073,7 +1178,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1090,7 +1194,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1107,7 +1210,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1124,7 +1226,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1141,7 +1242,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1158,7 +1258,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1175,7 +1274,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1192,7 +1290,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1209,7 +1306,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1226,7 +1322,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1243,7 +1338,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1260,7 +1354,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1277,7 +1370,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1294,7 +1386,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1311,7 +1402,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1328,7 +1418,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1345,7 +1434,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1362,7 +1450,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1379,7 +1466,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4584,7 +4670,6 @@ "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" @@ -4650,7 +4735,6 @@ "version": "15.0.0", "resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz", "integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==", - "dev": true, "license": "MIT", "engines": { "node": ">=22.12.0" @@ -4911,7 +4995,6 @@ "version": "0.28.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -5410,6 +5493,16 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "license": "MIT" }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -5715,6 +5808,578 @@ "npm": ">=10.2.3" } }, + "node_modules/langium-minilogo": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/langium-minilogo/-/langium-minilogo-4.0.0.tgz", + "integrity": "sha512-6efa5xH0URoq7ewdn54dEAYuIAoVwYyjtM8pInOliTwmcKzq9AjMPTPuAE021Ku5y4qb1aaNg0BSJPI66Tfe8g==", + "dependencies": { + "@codingame/esbuild-import-meta-url-plugin": "^1.0.3", + "chalk": "5.6.2", + "commander": "~15.0.0", + "langium": "~4.3.0", + "monaco-languageclient": "~10.7.0", + "vscode-languageclient": "~10.0.0", + "vscode-languageserver": "~10.0.0" + }, + "bin": { + "minilogo-cli": "bin/minilogo.js" + }, + "engines": { + "node": ">=24", + "vscode": "^1.120.0" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-api": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-api/-/monaco-vscode-api-25.1.2.tgz", + "integrity": "sha512-K04QcQA+Zb0KXucBAK/BGCT5dldiwIqdUbBQq7yuLvBLbof3cP1WSUuxasMHGYwM0MWyzIAsDtyAYMS7is8ZuA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-base-service-override": "25.1.2", + "@codingame/monaco-vscode-environment-service-override": "25.1.2", + "@codingame/monaco-vscode-extensions-service-override": "25.1.2", + "@codingame/monaco-vscode-files-service-override": "25.1.2", + "@codingame/monaco-vscode-host-service-override": "25.1.2", + "@codingame/monaco-vscode-layout-service-override": "25.1.2", + "@codingame/monaco-vscode-quickaccess-service-override": "25.1.2", + "@vscode/iconv-lite-umd": "0.7.1", + "dompurify": "3.3.1", + "jschardet": "3.1.4", + "marked": "14.0.0" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-base-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-base-service-override/-/monaco-vscode-base-service-override-25.1.2.tgz", + "integrity": "sha512-OwYs6h1ATUAeMmX+Q1c8esTG7GLMqniBs+fLEr1/9b/ciY485ArKo5UvrUxVPDtRNy/7F06vRW9IUCq9iKP14w==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-bulk-edit-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-bulk-edit-service-override/-/monaco-vscode-bulk-edit-service-override-25.1.2.tgz", + "integrity": "sha512-+EfSzjiFakCf0IIJKPZrHVGioq5N8GBsp51bXuKBR5J/B58cUaJY0Dc12PNTSpgAusAGOppUIOSBqUk4F/7IaQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-configuration-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-configuration-service-override/-/monaco-vscode-configuration-service-override-25.1.2.tgz", + "integrity": "sha512-oeoZ3WtM42zHA1IWHrx9UGEfE+TixE+G8Bl9M9bjgFj1EROnkB5yOfELwRYPo4WOEtcK1C5nvIvWIj/hL9MaLg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-files-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-editor-api": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-editor-api/-/monaco-vscode-editor-api-25.1.2.tgz", + "integrity": "sha512-dVXoBLRN8vyFHsLY6iYISaNetZ3ispXLut0qL+jvN0e0CEFkUv1F/3EAE7myptrJSS/N1AptrRIxATT3lwFP+Q==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-editor-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-editor-service-override/-/monaco-vscode-editor-service-override-25.1.2.tgz", + "integrity": "sha512-EadvDCyWdgxOPmaIvbcVVDNjTUYuKdjYWwKbPbbcTs9t4z1/DjdE7mV3ZdT6aGh5m6zkEEUOi143l27Y5eRt+Q==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-environment-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-environment-service-override/-/monaco-vscode-environment-service-override-25.1.2.tgz", + "integrity": "sha512-8GoD3lk0CN0dIMZOrZNS/i8RCaF1YSQ6nmrf+rqneOSHG9S382EnsZZD69d4+i7JnoeyttO7Kr9KH8WOhRV6OA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-extensions-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extensions-service-override/-/monaco-vscode-extensions-service-override-25.1.2.tgz", + "integrity": "sha512-rTTZW2biPxcg+JumhVf2L+38C5ptvNNxiJlwz39VfXFEh6qOHtAsIMy7vIXa0uGg5/y8DNp0SnOQJP/RKhLYZA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-files-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-files-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-files-service-override/-/monaco-vscode-files-service-override-25.1.2.tgz", + "integrity": "sha512-TenLLAFIwY7keZFF8e3beUn7OVfnNINR5Noi4PVrjeeTcy6FuNH6Jghdul2JwpRAkvyJLdFMvomE2jlT6F03jQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-host-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-host-service-override/-/monaco-vscode-host-service-override-25.1.2.tgz", + "integrity": "sha512-lgaalpA9CUQW7i0bBwgBOK0DQNDvOo3QO3p6Rz6yVsHpgA4iMqq2d11dBDUKvuQSwIHPRu8CMHCqhQk/BQN/YA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-keybindings-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-keybindings-service-override/-/monaco-vscode-keybindings-service-override-25.1.2.tgz", + "integrity": "sha512-cp/gGyTvCTAzCYnQm0HJykXJRB0Huz8Lvq60lj5LutgWcb8S3w6dOB2Houm8dHoeUm/jOko8SQNIP8hzWN92Zw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-files-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-cs": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-cs/-/monaco-vscode-language-pack-cs-25.1.2.tgz", + "integrity": "sha512-v0cB2uAOCwj135aGIf0arGV+DNW32lbWh04bv8ctTxcWRt1Pr2kTQ1pjfE8ynKgxabPfAk8E25/CerKSYOmZ+A==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-de": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-de/-/monaco-vscode-language-pack-de-25.1.2.tgz", + "integrity": "sha512-xA3WOt1w5jlAOnyx4PBwx+qV3vx8C8/zie29qjYbgJMxGKDkb0HfpuKUwywDA2uUMI2wJZS+PnNG00zPDoLIrw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-es": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-es/-/monaco-vscode-language-pack-es-25.1.2.tgz", + "integrity": "sha512-1/upuO9lRJilZ3sRr0QLTpz55KYRaBWDe8wtPvghOFYOHyWgW8A4VhUQxa6L9SJgY1JkypUAm0U8WcMX2G4LnQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-fr": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-fr/-/monaco-vscode-language-pack-fr-25.1.2.tgz", + "integrity": "sha512-iq+xx+tv1QIMmFD0eBhFRMF4xMAsVf/HyA1WogqBofteCWeAvRE9HUjZ5JzHz7jXBPe3dLP1LOM0r0GrJZs4fQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-it": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-it/-/monaco-vscode-language-pack-it-25.1.2.tgz", + "integrity": "sha512-FajWCML9OR8ppLnJ0mcg+sFHEhYJl8zhb3/DHnd+pNysw8dLfetXoSWjaPnwPPpwiQgkNN1UsToZHOU9czVifQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-ja": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-ja/-/monaco-vscode-language-pack-ja-25.1.2.tgz", + "integrity": "sha512-NwKh0BnPgUrJkxsm0X6vY4ftnd9DjxkcnQqK+bohta6UOzm09J1EjZ6QD42fjWngxrp/xiegtrYQ9NA2q6VpoA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-ko": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-ko/-/monaco-vscode-language-pack-ko-25.1.2.tgz", + "integrity": "sha512-fvaisgfcg8YaAwnyPcGmQDLwkwqzamLQUyx9HmnwDpXw0YANzd058Kwn6bz+Vfn9MjwuMNT0nllD0qQMnpdyew==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-pl": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-pl/-/monaco-vscode-language-pack-pl-25.1.2.tgz", + "integrity": "sha512-9hDRyzFJkDia5rO9QE262JgxwP/cnalFisLFo7FQcw57ZhqzqXIdQIuwcKaHuAgzeQ6W2+A3KOLfTr3m7VZrXw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-pt-br": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-pt-br/-/monaco-vscode-language-pack-pt-br-25.1.2.tgz", + "integrity": "sha512-7fFnqOTAJGb5RuJ4uwh9sh0JmXALuHPGOl7iL9rZkcgIuVP5y6wVDUDXq5qjiRTNSFDs7Bzh463Ir5m5D6mJbA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-qps-ploc": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-qps-ploc/-/monaco-vscode-language-pack-qps-ploc-25.1.2.tgz", + "integrity": "sha512-IFjoqrSuPtIFWb+KlPT6PFWKszzNX+TCD9drgCV6AigvBO/xfGL3QwHB68l/DLbmDbohOz4Xdkutv20wuENAeA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-ru": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-ru/-/monaco-vscode-language-pack-ru-25.1.2.tgz", + "integrity": "sha512-0uDAeXO+GllKUPhJzP893rlDhlFV1IwCu/515rBdcyegt48iGm/xAgj26V90hNz8hmB6EuM/7d8MFeklbiIpYA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-tr": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-tr/-/monaco-vscode-language-pack-tr-25.1.2.tgz", + "integrity": "sha512-MJhHxDyJEiuVLQ9+jb8MnnN9lsbJOjJjMswVCeJ7v/Q/msAhq25QYUfn0DbOIzESJE1f7crffRb5e38XP8sYWA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-zh-hans": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-zh-hans/-/monaco-vscode-language-pack-zh-hans-25.1.2.tgz", + "integrity": "sha512-c7MMrhnSLb59NxpAa8nVy9aIbxy4gVYrCpDMq8W380LOaXTYb7nueTrw8QJ5QbJBNi2P2KZoGkn2BlONuBtJJg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-language-pack-zh-hant": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-language-pack-zh-hant/-/monaco-vscode-language-pack-zh-hant-25.1.2.tgz", + "integrity": "sha512-ARedFTM6JCluoPLJqkBcTJaQFdJNcN86OX6B8/NMApIPrnSIAfanMndpyilt8XjzUG6IH22cypR+DAlEjf48cA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-languages-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-languages-service-override/-/monaco-vscode-languages-service-override-25.1.2.tgz", + "integrity": "sha512-ipuS1V3NgXDkNrj0vBcgMBFnqo+19HVsZjjFGfPFH3x0uptP9aiWWK42wtDK3Qbu4teSjHL7WnSLrmw94rplWw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-files-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-layout-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-layout-service-override/-/monaco-vscode-layout-service-override-25.1.2.tgz", + "integrity": "sha512-SxBGcMK3RgkGtUn7ZDl7dCoyNW0CWFQ/bfSRYUY06A0IA4JNS5jq1lhof57d0WXewm+5l8w1Spr/vMsfx1c9ig==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-localization-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-localization-service-override/-/monaco-vscode-localization-service-override-25.1.2.tgz", + "integrity": "sha512-QLj62A8XDOIQW3KjsZlNxs+sfsNNHYxWMjQMwZu/y2Vw3IIHGly2Lpn4t4SFbeaBHJQJy4i5s7NpzlbF9MbEzQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-log-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-log-service-override/-/monaco-vscode-log-service-override-25.1.2.tgz", + "integrity": "sha512-OoileAUtPAJ0j3RW31DFSxtOipy0EcFq+iIXEdGvoRlsQPZJ3o9ayjf1JvCXpxUjJ3QkmvQVhXsWNUFREjEFLg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-environment-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-model-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-model-service-override/-/monaco-vscode-model-service-override-25.1.2.tgz", + "integrity": "sha512-MGz/eV1CxibLvnl6WzK6idUHJCXJOVepJvKM6Trkv5050vRe+f/o1TjCiG8PaznAypYqZvnwkTG0B7/OTizCpQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-monarch-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-monarch-service-override/-/monaco-vscode-monarch-service-override-25.1.2.tgz", + "integrity": "sha512-akyNHOJQRS7YHyk6kf0Encnkt+shlR+bIB84UJRUHFgSeF8s5gkDkQuFJph0YeUDWJWat+yBLUSZx2nHomdbHQ==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-quickaccess-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-quickaccess-service-override/-/monaco-vscode-quickaccess-service-override-25.1.2.tgz", + "integrity": "sha512-7IIrXnwHiF3w9d9p9kspEUz/LCibMLUztmRpGdZQfFtWBJw043q7rk8V1O42KdXr1hVg9IR5vfffwjy9nbiiUg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-textmate-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-textmate-service-override/-/monaco-vscode-textmate-service-override-25.1.2.tgz", + "integrity": "sha512-AL0FtSQBW+1vtoXYQvUqB2hfWojpK73Kq/n6KuNXxjLF/XBJ5FpeeZDfrBfwhWPPoHuBTsaFUCQy4L8xQgbVlA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-files-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-theme-defaults-default-extension": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-theme-defaults-default-extension/-/monaco-vscode-theme-defaults-default-extension-25.1.2.tgz", + "integrity": "sha512-0vTMFiC89YSDSmjFckuQBUKwRuFNtsILNO3k0PBiSLN/MW+VDItjJpiVLXC42+rUWlGgY2lYxOneGVa5slCV1w==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-theme-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-theme-service-override/-/monaco-vscode-theme-service-override-25.1.2.tgz", + "integrity": "sha512-hsTwl6YYTiheFuQMmCmiEGLIdIdgYaf8Z85XWyxe6YgPtDaYGnp0fGSOXKA9/bf0JtuynzoLKtUUfDupK/A7Tw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-files-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-view-banner-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-banner-service-override/-/monaco-vscode-view-banner-service-override-25.1.2.tgz", + "integrity": "sha512-zhujHd1PQ6rRXsC2OQGrx/282G2v3lpPFl9heDFGKzpdj5119SgcW+B9p/MwJ1qF3LJpuRRgefNiQtqC/KT1eA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-view-common-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-common-service-override/-/monaco-vscode-view-common-service-override-25.1.2.tgz", + "integrity": "sha512-4Po/YaHUvVf4VmhVCZmM2lc/flOptiWSM140bIRNpMcfH0VwihYg15CcDeu1Oc+6DaauzsG3u59GtEvlMmJ9Zw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-bulk-edit-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-view-status-bar-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-status-bar-service-override/-/monaco-vscode-view-status-bar-service-override-25.1.2.tgz", + "integrity": "sha512-Jp9ytLaWZ6evabTPtG3Mu3dFx+7WTIPz69BsGpl9PnU0kiSWUqQhPSob0Jz7E2qmMj0ZcNv2Wqvm6bMBu5OyrA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-view-title-bar-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-view-title-bar-service-override/-/monaco-vscode-view-title-bar-service-override-25.1.2.tgz", + "integrity": "sha512-NVYtTAFR35NV/Fx7tSlbASicvpAjK5A14fmxF7/LJJN8ZmzhA/P3Y+UzhqOQl6/VcPV4pAMU0Z7Sicgwbn37dw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-views-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-views-service-override/-/monaco-vscode-views-service-override-25.1.2.tgz", + "integrity": "sha512-LfzlztsvobdP5L5EvJ/rqSEgy5fEVmrkMqRteuhEtNGd4hnmdBoX8W7BNMBPff6d4NfCK74pGHJF57RyT4Iixg==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-keybindings-service-override": "25.1.2", + "@codingame/monaco-vscode-layout-service-override": "25.1.2", + "@codingame/monaco-vscode-quickaccess-service-override": "25.1.2", + "@codingame/monaco-vscode-view-common-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/@codingame/monaco-vscode-workbench-service-override": { + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-workbench-service-override/-/monaco-vscode-workbench-service-override-25.1.2.tgz", + "integrity": "sha512-2LMHr+na03FhOAaXpIGmamq9hf7e4wt2kULn8NqNZRd3i+0v1tx/TSSjGhsA5EkrNrFD7CMSoXayBq8tgpCq/A==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-keybindings-service-override": "25.1.2", + "@codingame/monaco-vscode-quickaccess-service-override": "25.1.2", + "@codingame/monaco-vscode-view-banner-service-override": "25.1.2", + "@codingame/monaco-vscode-view-common-service-override": "25.1.2", + "@codingame/monaco-vscode-view-status-bar-service-override": "25.1.2", + "@codingame/monaco-vscode-view-title-bar-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/langium-minilogo/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/langium-minilogo/node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/langium-minilogo/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/langium-minilogo/node_modules/monaco-languageclient": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/monaco-languageclient/-/monaco-languageclient-10.7.0.tgz", + "integrity": "sha512-oA5cOFixkF4bspVL2zMSn48LvlNR/Cu3vJ8MCVam3PdjobSULGgHtOASuZIi3FgWK42X1z8/6hrG0LCjvNu1Hw==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "^25.1.2", + "@codingame/monaco-vscode-configuration-service-override": "^25.1.2", + "@codingame/monaco-vscode-editor-api": "^25.1.2", + "@codingame/monaco-vscode-editor-service-override": "^25.1.2", + "@codingame/monaco-vscode-extension-api": "^25.1.2", + "@codingame/monaco-vscode-extensions-service-override": "^25.1.2", + "@codingame/monaco-vscode-language-pack-cs": "^25.1.2", + "@codingame/monaco-vscode-language-pack-de": "^25.1.2", + "@codingame/monaco-vscode-language-pack-es": "^25.1.2", + "@codingame/monaco-vscode-language-pack-fr": "^25.1.2", + "@codingame/monaco-vscode-language-pack-it": "^25.1.2", + "@codingame/monaco-vscode-language-pack-ja": "^25.1.2", + "@codingame/monaco-vscode-language-pack-ko": "^25.1.2", + "@codingame/monaco-vscode-language-pack-pl": "^25.1.2", + "@codingame/monaco-vscode-language-pack-pt-br": "^25.1.2", + "@codingame/monaco-vscode-language-pack-qps-ploc": "^25.1.2", + "@codingame/monaco-vscode-language-pack-ru": "^25.1.2", + "@codingame/monaco-vscode-language-pack-tr": "^25.1.2", + "@codingame/monaco-vscode-language-pack-zh-hans": "^25.1.2", + "@codingame/monaco-vscode-language-pack-zh-hant": "^25.1.2", + "@codingame/monaco-vscode-languages-service-override": "^25.1.2", + "@codingame/monaco-vscode-localization-service-override": "^25.1.2", + "@codingame/monaco-vscode-log-service-override": "^25.1.2", + "@codingame/monaco-vscode-model-service-override": "^25.1.2", + "@codingame/monaco-vscode-monarch-service-override": "^25.1.2", + "@codingame/monaco-vscode-textmate-service-override": "^25.1.2", + "@codingame/monaco-vscode-theme-defaults-default-extension": "^25.1.2", + "@codingame/monaco-vscode-theme-service-override": "^25.1.2", + "@codingame/monaco-vscode-views-service-override": "^25.1.2", + "@codingame/monaco-vscode-workbench-service-override": "^25.1.2", + "vscode": "npm:@codingame/monaco-vscode-extension-api@^25.1.2", + "vscode-languageclient": "~9.0.1", + "vscode-languageserver-protocol": "~3.17.5", + "vscode-ws-jsonrpc": "~3.5.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/langium-minilogo/node_modules/monaco-languageclient/node_modules/vscode-languageclient": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", + "license": "MIT", + "dependencies": { + "minimatch": "^5.1.0", + "semver": "^7.3.7", + "vscode-languageserver-protocol": "3.17.5" + }, + "engines": { + "vscode": "^1.82.0" + } + }, + "node_modules/langium-minilogo/node_modules/vscode": { + "name": "@codingame/monaco-vscode-extension-api", + "version": "25.1.2", + "resolved": "https://registry.npmjs.org/@codingame/monaco-vscode-extension-api/-/monaco-vscode-extension-api-25.1.2.tgz", + "integrity": "sha512-SJW/YOhjo+9MXEyzMwQMUWdJVR3Llc6pTq5JQqs6Y30v73gTrpLqtzbd9FNdCuQR8S6bUk5ScH8GL4QrVuL5FA==", + "license": "MIT", + "dependencies": { + "@codingame/monaco-vscode-api": "25.1.2", + "@codingame/monaco-vscode-extensions-service-override": "25.1.2" + } + }, + "node_modules/langium-minilogo/node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/langium-minilogo/node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/langium-minilogo/node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/langium-minilogo/node_modules/vscode-ws-jsonrpc": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/vscode-ws-jsonrpc/-/vscode-ws-jsonrpc-3.5.0.tgz", + "integrity": "sha512-13ZDy7Od4AfEPK2HIfY3DtyRi4FVsvFql1yobVJrpIoHOKGGJpIjVvIJpMxkrHzCZzWlYlg+WEu2hrYkCTvM0Q==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "~8.2.1" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/langium-minilogo/node_modules/vscode-ws-jsonrpc/node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", + "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/langium-railroad": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/langium-railroad/-/langium-railroad-4.3.0.tgz", @@ -8305,6 +8970,7 @@ "express": "~5.2.1", "jszip": "~3.10.1", "langium": "~4.3.0", + "langium-minilogo": "~4.0.0", "monaco-languageclient": "~11.0.0-next.0", "pyright": "~1.1.410", "react": "~19.2.7", diff --git a/packages/examples/ghp_minilogo.html b/packages/examples/ghp_minilogo.html new file mode 100644 index 00000000..07e43668 --- /dev/null +++ b/packages/examples/ghp_minilogo.html @@ -0,0 +1,17 @@ + + + + + + MiniLogo Client & Language Server (Worker) + + + + +
+ MiniLogo Client & Language Server (Worker) - [Back to Index] +
+
+ + + diff --git a/packages/examples/index.html b/packages/examples/index.html index 7098f146..b8d18029 100644 --- a/packages/examples/index.html +++ b/packages/examples/index.html @@ -17,6 +17,8 @@

Langium

Langium Grammar DSL (Language Server in Worker): [ Example Page ]
+ Langium MiniLogo (Language Server in Worker): [ Example Page ] +
Langium Statemachine (Language Server in Worker): [ Example Page | React Example Page ]
diff --git a/packages/examples/minilogo.html b/packages/examples/minilogo.html new file mode 100644 index 00000000..12dc1ccb --- /dev/null +++ b/packages/examples/minilogo.html @@ -0,0 +1,17 @@ + + + + + + MiniLogo Client & Language Server (Worker) + + + + +
+ MiniLogo Client & Language Server (Worker) - [Back to Index] +
+
+ + + diff --git a/packages/examples/package.json b/packages/examples/package.json index 2139d476..6f00d0e0 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -118,6 +118,7 @@ "express": "~5.2.1", "jszip": "~3.10.1", "langium": "~4.3.0", + "langium-minilogo": "~4.0.0", "monaco-languageclient": "~11.0.0-next.0", "pyright": "~1.1.410", "react": "~19.2.7", diff --git a/packages/examples/src/langium/langium-dsl/minilogo/config/minilogo.configuration.json b/packages/examples/src/langium/langium-dsl/minilogo/config/minilogo.configuration.json new file mode 100644 index 00000000..b5e3d7dc --- /dev/null +++ b/packages/examples/src/langium/langium-dsl/minilogo/config/minilogo.configuration.json @@ -0,0 +1,25 @@ +{ + "comments": { + "lineComment": "//", + "blockComment": ["/*", "*/"] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ] +} diff --git a/packages/examples/src/langium/langium-dsl/minilogo/config/minilogo.tmLanguage.json b/packages/examples/src/langium/langium-dsl/minilogo/config/minilogo.tmLanguage.json new file mode 100644 index 00000000..896bd1c9 --- /dev/null +++ b/packages/examples/src/langium/langium-dsl/minilogo/config/minilogo.tmLanguage.json @@ -0,0 +1,45 @@ +{ + "name": "minilogo", + "scopeName": "source.minilogo", + "fileTypes": [".minilogo"], + "patterns": [ + { + "include": "#comments" + }, + { + "name": "keyword.control.minilogo", + "match": "\\b(color|def|down|for|move|pen|to|up)\\b" + } + ], + "repository": { + "comments": { + "patterns": [ + { + "name": "comment.block.minilogo", + "begin": "/\\*", + "beginCaptures": { + "0": { + "name": "punctuation.definition.comment.minilogo" + } + }, + "end": "\\*/", + "endCaptures": { + "0": { + "name": "punctuation.definition.comment.minilogo" + } + } + }, + { + "begin": "//", + "beginCaptures": { + "1": { + "name": "punctuation.whitespace.comment.leading.minilogo" + } + }, + "end": "(?=$)", + "name": "comment.line.minilogo" + } + ] + } + } +} diff --git a/packages/examples/src/langium/langium-dsl/minilogo/config/minilogoConfig.ts b/packages/examples/src/langium/langium-dsl/minilogo/config/minilogoConfig.ts new file mode 100644 index 00000000..16ad0194 --- /dev/null +++ b/packages/examples/src/langium/langium-dsl/minilogo/config/minilogoConfig.ts @@ -0,0 +1,140 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { LogLevel } from '@codingame/monaco-vscode-api'; +import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; +import type { EditorAppConfig } from 'monaco-languageclient/editorApp'; +import type { LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import type { MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; +import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver-protocol/browser'; +import type { ExampleAppConfig } from '../../../../common/client/utils.js'; +import minilogoLanguageConfig from './minilogo.configuration.json?raw'; +import minilogoTextmateGrammar from './minilogo.tmLanguage.json?raw'; + +// sample MiniLogo content for the editor +const sampleContent = `// MiniLogo: draw a simple house + +def square(size) { + for i = 1 to 4 { + move(size, 0) + move(0, size) + } +} + +def triangle(size) { + move(size, 0) + move(-size / 2, size) + move(-size / 2, -size) +} + +// draw the house body +move(150, 100) +pen(down) +color(#654321) +square(100) +pen(up) + +// draw the roof +move(150, 200) +pen(down) +color(#CC0000) +triangle(100) +pen(up) +`; + +export const createMinilogoConfig = (params: { htmlContainer: HTMLElement }): ExampleAppConfig => { + const languageId = 'minilogo'; + + // create the worker from the langium-minilogo package's pre-built language server bundle + const worker = new Worker(new URL('langium-minilogo/ls-web', import.meta.url), { + type: 'module', + name: 'MiniLogo Language Server' + }); + const reader = new BrowserMessageReader(worker); + const writer = new BrowserMessageWriter(worker); + + const languageClientConfig: LanguageClientConfig = { + languageId, + clientOptions: { + documentSelector: [languageId] + }, + connection: { + options: { + $type: 'WorkerDirect', + worker + }, + messageTransports: { reader, writer } + } + }; + + const vscodeApiConfig: MonacoVscodeApiConfig = { + $type: 'extended', + viewsConfig: { + $type: 'EditorService', + htmlContainer: params.htmlContainer + }, + logLevel: LogLevel.Debug, + serviceOverrides: { + ...getKeybindingsServiceOverride() + }, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.guides.bracketPairsHorizontal': 'active', + 'editor.wordBasedSuggestions': 'off', + 'editor.experimental.asyncTokenization': true + }) + }, + monacoWorkerFactory: configureDefaultWorkerFactory, + // register the minilogo language with textmate grammar for syntax highlighting + extensions: [ + { + config: { + name: 'minilogo-language', + publisher: 'TypeFox', + version: '1.0.0', + engines: { vscode: '*' }, + contributes: { + languages: [ + { + id: languageId, + extensions: ['.minilogo'], + aliases: ['MiniLogo', 'minilogo'], + configuration: '/workspace/minilogo-configuration.json' + } + ], + grammars: [ + { + language: languageId, + scopeName: 'source.minilogo', + path: '/workspace/minilogo-grammar.json' + } + ] + } + }, + filesOrContents: new Map([ + ['/workspace/minilogo-configuration.json', minilogoLanguageConfig], + ['/workspace/minilogo-grammar.json', minilogoTextmateGrammar] + ]) + } + ] + }; + + const editorAppConfig: EditorAppConfig = { + codeResources: { + modified: { + text: sampleContent, + uri: '/workspace/example.minilogo' + } + } + }; + + return { + editorAppConfig, + vscodeApiConfig, + languageClientConfig + }; +}; diff --git a/packages/examples/src/langium/langium-dsl/minilogo/launcher.ts b/packages/examples/src/langium/langium-dsl/minilogo/launcher.ts new file mode 100644 index 00000000..ec009311 --- /dev/null +++ b/packages/examples/src/langium/langium-dsl/minilogo/launcher.ts @@ -0,0 +1,10 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { initLocaleLoader } from 'monaco-languageclient/vscodeApiLocales'; +await initLocaleLoader(); + +const { runMinilogo } = await import('./main.js'); +await runMinilogo(); diff --git a/packages/examples/src/langium/langium-dsl/minilogo/main.ts b/packages/examples/src/langium/langium-dsl/minilogo/main.ts new file mode 100644 index 00000000..1256bc88 --- /dev/null +++ b/packages/examples/src/langium/langium-dsl/minilogo/main.ts @@ -0,0 +1,44 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { EditorApp } from 'monaco-languageclient/editorApp'; +import { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; +import { LanguageClientWrapper } from 'monaco-languageclient/lcwrapper'; +import { createMinilogoConfig } from './config/minilogoConfig.js'; + +let editorApp: EditorApp | undefined; +let lcWrapper: LanguageClientWrapper | undefined; + +const startEditor = async () => { + if (editorApp?.isStarted() === true) { + console.warn('Editor was already started'); + return; + } + + const htmlContainer = document.getElementById('monaco-editor-root')!; + const appConfig = createMinilogoConfig({ htmlContainer }); + + // initialize the monaco-vscode api + const apiWrapper = new MonacoVscodeApiWrapper(appConfig.vscodeApiConfig); + await apiWrapper.start(); + + // start the language client + lcWrapper = new LanguageClientWrapper(appConfig.languageClientConfig); + await lcWrapper.start(); + + // start the editor + editorApp = new EditorApp(appConfig.editorAppConfig); + await editorApp.start(htmlContainer); + + console.log('MiniLogo editor with language client is ready'); +}; + +export const runMinilogo = async () => { + try { + await startEditor(); + } catch (e) { + console.error(e); + } +}; diff --git a/packages/examples/vite.production.base.ts b/packages/examples/vite.production.base.ts index 2b0618ed..51eca91f 100644 --- a/packages/examples/vite.production.base.ts +++ b/packages/examples/vite.production.base.ts @@ -15,6 +15,7 @@ export const buildBaseProductionConfig: () => UserConfig = () => { input: { index: path.resolve(__dirname, 'index.html'), langiumExtended: path.resolve(__dirname, 'ghp_langium_extended.html'), + minilogo: path.resolve(__dirname, 'ghp_minilogo.html'), statemachine: path.resolve(__dirname, 'ghp_statemachine.html'), clangd: path.resolve(__dirname, 'ghp_clangd.html'), appPlayground: path.resolve(__dirname, 'ghp_appPlayground.html'), diff --git a/vite.config.ts b/vite.config.ts index 909bf47d..9811044d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -30,7 +30,8 @@ export const definedViteConfig = defineConfig({ reactAppPlayground: path.resolve(__dirname, 'packages/examples/react_appPlayground.html'), reactStatemachine: path.resolve(__dirname, 'packages/examples/react_statemachine.html'), reactPython: path.resolve(__dirname, 'packages/examples/react_python.html'), - tsExtHost: path.resolve(__dirname, 'packages/examples/tsExtHost.html') + tsExtHost: path.resolve(__dirname, 'packages/examples/tsExtHost.html'), + minilogo: path.resolve(__dirname, 'packages/examples/minilogo.html') } } },