From cc5d0794bc730678c59ce23a2eb4d64e57c00ed8 Mon Sep 17 00:00:00 2001 From: Kai Salmen Date: Sat, 31 May 2025 22:36:41 +0200 Subject: [PATCH 1/2] Lifecycle adjustments - Remove monacp-editor-wrapper. Create EditorApp in client - Move languageclient management from wrapper to client - move all tests from wrapper to client - client: proper separation of sub-exports, rename tools to common - monaco-vscode-api independent config and init - clean up and restructure src and test - Update vitest and other dependencies - Update examples - Unify json, eclipse.jdt and groovy example - react component: fix re-render and global init - expand react tests - LanguageClient handling with react component --- .github/workflows/main.yml | 4 +- index.html | 17 +- package-lock.json | 36 +- package.json | 14 +- packages/client/package.json | 74 ++- packages/client/src/client.ts | 26 - .../client/src/{ => common}/commonTypes.ts | 4 +- .../client/src/{tools => common}/index.ts | 1 + .../client/src/{tools => common}/logging.ts | 0 .../client/src/{tools => common}/utils.ts | 17 +- packages/client/src/editorApp/config.ts | 67 ++ .../src => client/src/editorApp}/editorApp.ts | 303 ++++++---- .../src => client/src/editorApp}/index.ts | 2 +- packages/client/src/fs/definitions.ts | 2 +- .../src/fs/endpoints/defaultEndpoint.ts | 2 +- packages/client/src/index.ts | 31 +- packages/client/src/vscode/apiWrapper.ts | 333 ++++++++++ packages/client/src/vscode/config.ts | 55 ++ packages/client/src/vscode/index.ts | 6 +- .../src/vscode/locales.ts} | 66 ++ packages/client/src/vscode/services.ts | 249 -------- packages/client/src/vscode/utils.ts | 67 ++ .../src/vscode/viewsService.ts | 0 .../src/{vscode => worker}/fakeWorker.ts | 0 packages/client/src/worker/index.ts | 8 + .../client/src/{ => worker}/workerFactory.ts | 4 +- .../src/worker}/workerLoaders.ts | 2 +- .../vscode => client/src/wrapper}/index.ts | 5 +- packages/client/src/wrapper/lcconfig.ts | 32 + packages/client/src/wrapper/lcmanager.ts | 89 +++ .../lcwrapper.ts} | 46 +- .../index.test.ts => common/logging.test.ts} | 2 +- .../test/{tools => common}/utils.test.ts | 3 +- .../test/editorApp/config.test.ts} | 3 +- .../test/editorApp/editorApp-classic.test.ts | 348 +++++++++++ .../client/test/editorApp/editorApp.test.ts | 228 +++++++ .../test/support/helper-classic.ts | 19 +- packages/client/test/support/helper.ts | 50 +- packages/client/test/vscode/manager.test.ts | 127 ++++ packages/client/test/vscode/services.test.ts | 45 -- .../test/{ => worker}/workerFactory.test.ts | 2 +- .../client/test/worker/workerLoaders.test.ts | 71 +++ .../client/test/wrapper/lcmanager.test.ts | 64 ++ .../lcwrapper.test.ts} | 39 +- packages/examples/json.html | 2 +- packages/examples/package.json | 1 - .../resources/groovy/workspace/hello.groovy | 3 + .../resources/json/workspace/hello.json | 4 + packages/examples/src/appPlayground/common.ts | 6 +- packages/examples/src/appPlayground/config.ts | 134 ++-- .../examples/src/appPlayground/launcher.ts | 3 +- packages/examples/src/appPlayground/main.ts | 13 +- .../src/appPlayground/reactLauncher.ts | 3 +- .../examples/src/appPlayground/reactMain.tsx | 8 +- packages/examples/src/bare/client.ts | 31 +- packages/examples/src/browser/main.ts | 56 +- packages/examples/src/clangd/client/config.ts | 184 +++--- packages/examples/src/clangd/client/main.ts | 24 +- .../src/common/client/extendedClient.ts | 111 ++++ packages/examples/src/common/client/utils.ts | 9 +- .../examples/src/debugger/client/debugger.ts | 2 +- .../src/eclipse.jdt.ls/client/main.ts | 82 +-- .../examples/src/eclipse.jdt.ls/config.ts | 8 +- packages/examples/src/groovy/client/main.ts | 69 +-- packages/examples/src/groovy/config.ts | 8 +- packages/examples/src/json/client/client.ts | 14 + packages/examples/src/json/client/config.ts | 13 + .../examples/src/json/client/wrapperWs.ts | 90 --- .../langium-dsl/config/classicConfig.ts | 112 ++-- .../langium-dsl/config/extendedConfig.ts | 91 +-- .../src/langium/langium-dsl/wrapperLangium.ts | 40 +- .../config/wrapperStatemachineConfig.ts | 93 +-- .../src/langium/statemachine/launcher.ts | 2 +- .../src/langium/statemachine/main-react.tsx | 8 +- .../examples/src/langium/statemachine/main.ts | 70 ++- packages/examples/src/multi/config.ts | 6 +- .../examples/src/multi/twoLanguageClients.ts | 100 ++- packages/examples/src/node.ts | 6 +- packages/examples/src/python/client/config.ts | 200 +++--- packages/examples/src/python/client/main.ts | 31 +- .../src/python/client/reactPython.tsx | 21 +- packages/examples/src/ts/wrapperTs.ts | 97 +-- packages/examples/tsconfig.src.json | 3 - packages/examples/two_langauge_clients.html | 1 - packages/wrapper-react/README.md | 2 +- packages/wrapper-react/package.json | 1 - packages/wrapper-react/src/index.tsx | 202 +++++-- packages/wrapper-react/test/index.test.tsx | 571 ++++++++++++------ .../test/{ => support}/helper.ts | 32 +- packages/wrapper-react/tsconfig.src.json | 2 +- packages/wrapper-react/tsconfig.test.json | 2 - packages/wrapper/.gitignore | 2 - packages/wrapper/CHANGELOG.md | 427 ------------- packages/wrapper/LICENSE | 9 - packages/wrapper/README.md | 82 --- packages/wrapper/package.json | 119 ---- packages/wrapper/src/vscode/services.ts | 154 ----- packages/wrapper/src/wrapper.ts | 415 ------------- .../wrapper/test/editorApp-classic.test.ts | 75 --- packages/wrapper/test/editorApp.test.ts | 58 -- packages/wrapper/test/support/helper.ts | 63 -- packages/wrapper/test/vscode/services.test.ts | 55 -- .../test/workers/workerLoaders.test.ts | 87 --- packages/wrapper/test/wrapper-classic.test.ts | 304 ---------- packages/wrapper/test/wrapper.test.ts | 324 ---------- packages/wrapper/tsconfig.json | 12 - packages/wrapper/tsconfig.src.json | 15 - packages/wrapper/tsconfig.test.json | 15 - tsconfig.build.json | 24 +- verify/angular/package.json | 1 - verify/angular/src/app/app.component.ts | 2 +- .../monaco-angular-wrapper.component.ts | 2 +- verify/peerNpm/package.json | 1 + vite.config.ts | 64 +- vitest.config.ts | 38 +- 115 files changed, 3491 insertions(+), 3991 deletions(-) delete mode 100644 packages/client/src/client.ts rename packages/client/src/{ => common}/commonTypes.ts (93%) rename packages/client/src/{tools => common}/index.ts (91%) rename packages/client/src/{tools => common}/logging.ts (100%) rename packages/client/src/{tools => common}/utils.ts (72%) create mode 100644 packages/client/src/editorApp/config.ts rename packages/{wrapper/src => client/src/editorApp}/editorApp.ts (59%) rename packages/{wrapper/src => client/src/editorApp}/index.ts (92%) create mode 100644 packages/client/src/vscode/apiWrapper.ts create mode 100644 packages/client/src/vscode/config.ts rename packages/{wrapper/src/vscode/localeLoader.ts => client/src/vscode/locales.ts} (53%) delete mode 100644 packages/client/src/vscode/services.ts create mode 100644 packages/client/src/vscode/utils.ts rename packages/{wrapper => client}/src/vscode/viewsService.ts (100%) rename packages/client/src/{vscode => worker}/fakeWorker.ts (100%) create mode 100644 packages/client/src/worker/index.ts rename packages/client/src/{ => worker}/workerFactory.ts (93%) rename packages/{wrapper/src/workers => client/src/worker}/workerLoaders.ts (96%) rename packages/{wrapper/src/vscode => client/src/wrapper}/index.ts (77%) create mode 100644 packages/client/src/wrapper/lcconfig.ts create mode 100644 packages/client/src/wrapper/lcmanager.ts rename packages/client/src/{languageClientWrapper.ts => wrapper/lcwrapper.ts} (88%) rename packages/client/test/{tools/index.test.ts => common/logging.test.ts} (95%) rename packages/client/test/{tools => common}/utils.test.ts (96%) rename packages/{wrapper/test/utils.test.ts => client/test/editorApp/config.test.ts} (96%) create mode 100644 packages/client/test/editorApp/editorApp-classic.test.ts create mode 100644 packages/client/test/editorApp/editorApp.test.ts rename packages/{wrapper => client}/test/support/helper-classic.ts (83%) create mode 100644 packages/client/test/vscode/manager.test.ts delete mode 100644 packages/client/test/vscode/services.test.ts rename packages/client/test/{ => worker}/workerFactory.test.ts (96%) create mode 100644 packages/client/test/worker/workerLoaders.test.ts create mode 100644 packages/client/test/wrapper/lcmanager.test.ts rename packages/client/test/{languageClientWrapper.test.ts => wrapper/lcwrapper.test.ts} (83%) create mode 100644 packages/examples/resources/groovy/workspace/hello.groovy create mode 100644 packages/examples/resources/json/workspace/hello.json create mode 100644 packages/examples/src/common/client/extendedClient.ts create mode 100644 packages/examples/src/json/client/client.ts create mode 100644 packages/examples/src/json/client/config.ts delete mode 100644 packages/examples/src/json/client/wrapperWs.ts rename packages/wrapper-react/test/{ => support}/helper.ts (55%) delete mode 100644 packages/wrapper/.gitignore delete mode 100644 packages/wrapper/CHANGELOG.md delete mode 100644 packages/wrapper/LICENSE delete mode 100644 packages/wrapper/README.md delete mode 100644 packages/wrapper/package.json delete mode 100644 packages/wrapper/src/vscode/services.ts delete mode 100644 packages/wrapper/src/wrapper.ts delete mode 100644 packages/wrapper/test/editorApp-classic.test.ts delete mode 100644 packages/wrapper/test/editorApp.test.ts delete mode 100644 packages/wrapper/test/support/helper.ts delete mode 100644 packages/wrapper/test/vscode/services.test.ts delete mode 100644 packages/wrapper/test/workers/workerLoaders.test.ts delete mode 100644 packages/wrapper/test/wrapper-classic.test.ts delete mode 100644 packages/wrapper/test/wrapper.test.ts delete mode 100644 packages/wrapper/tsconfig.json delete mode 100644 packages/wrapper/tsconfig.src.json delete mode 100644 packages/wrapper/tsconfig.test.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 01834e198..b9d24a65a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,8 +24,6 @@ jobs: - name: Volta uses: volta-cli/action@v4 - with: - node-version: 20 - name: Use pnpm uses: pnpm/action-setup@v3 @@ -56,4 +54,4 @@ jobs: shell: bash run: | npm run test:install - npm run test:run + npm run test diff --git a/index.html b/index.html index c127f1038..657f18964 100644 --- a/index.html +++ b/index.html @@ -36,7 +36,7 @@

Langium

Langium Grammar DSL: Extended Mode - Classic Mode
- Langium Statemachine Client & Language Server (Worker) + Langium Statemachine Client & Language Server (Worker) (React Version)

Localizations: cs - @@ -58,7 +58,7 @@

Langium

Python

Please execute npm run start:example:server:python beforehand.
Debugger requires docker. Please execute docker compose -f ./packages/examples/resources/debugger/docker-compose.yml up -d beforehand.
- Python Language Client & Pyright Language Server (Web Socket)
+ Python Language Client & Pyright Language Server (Web Socket) (React Version)

Java / Eclipse JDS LS

Requires docker. Please execute docker compose -f ./packages/examples/resources/eclipse.jdt.ls/docker-compose.yml up -d beforehand:
@@ -80,23 +80,12 @@

Multiple Languageclients


Application Playground

- Application Playground + Application Playground (React Version)

TypeScript

TypeScript Extension Host Worker
-

Monaco Editor React

- Please execute npm run start:example:server:python beforehand.
- Debugger requires docker. Please execute docker compose -f ./packages/examples/resources/debugger/docker-compose.yml up -d beforehand.
- React: Application Playground -
- React: Langium Statemachine Language Client & Language Server (Worker) -

- Please execute npm run start:example:server:python beforehand:
- React: Python Language Client & Language Server (Web Socket) -
-

Verification

Angular

Please start cd verify/angular && npm run verify beforehand:
diff --git a/package-lock.json b/package-lock.json index 3f865af00..dd7b425a0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8391,10 +8391,6 @@ "@codingame/monaco-vscode-api": "19.1.4" } }, - "node_modules/monaco-editor-wrapper": { - "resolved": "packages/wrapper", - "link": true - }, "node_modules/monaco-languageclient": { "resolved": "packages/client", "link": true @@ -11050,14 +11046,41 @@ "@codingame/monaco-vscode-editor-service-override": "~19.1.4", "@codingame/monaco-vscode-extension-api": "~19.1.4", "@codingame/monaco-vscode-extensions-service-override": "~19.1.4", + "@codingame/monaco-vscode-language-pack-cs": "~19.1.4", + "@codingame/monaco-vscode-language-pack-de": "~19.1.4", + "@codingame/monaco-vscode-language-pack-es": "~19.1.4", + "@codingame/monaco-vscode-language-pack-fr": "~19.1.4", + "@codingame/monaco-vscode-language-pack-it": "~19.1.4", + "@codingame/monaco-vscode-language-pack-ja": "~19.1.4", + "@codingame/monaco-vscode-language-pack-ko": "~19.1.4", + "@codingame/monaco-vscode-language-pack-pl": "~19.1.4", + "@codingame/monaco-vscode-language-pack-pt-br": "~19.1.4", + "@codingame/monaco-vscode-language-pack-qps-ploc": "~19.1.4", + "@codingame/monaco-vscode-language-pack-ru": "~19.1.4", + "@codingame/monaco-vscode-language-pack-tr": "~19.1.4", + "@codingame/monaco-vscode-language-pack-zh-hans": "~19.1.4", + "@codingame/monaco-vscode-language-pack-zh-hant": "~19.1.4", "@codingame/monaco-vscode-languages-service-override": "~19.1.4", "@codingame/monaco-vscode-localization-service-override": "~19.1.4", "@codingame/monaco-vscode-log-service-override": "~19.1.4", "@codingame/monaco-vscode-model-service-override": "~19.1.4", - "vscode": "npm:@codingame/monaco-vscode-extension-api@~19.1.4", + "@codingame/monaco-vscode-monarch-service-override": "~19.1.4", + "@codingame/monaco-vscode-textmate-service-override": "~19.1.4", + "@codingame/monaco-vscode-theme-defaults-default-extension": "~19.1.4", + "@codingame/monaco-vscode-theme-service-override": "~19.1.4", + "@codingame/monaco-vscode-views-service-override": "~19.1.4", + "@codingame/monaco-vscode-workbench-service-override": "~19.1.4", "vscode-languageclient": "~9.0.1", + "vscode-languageserver-protocol": "~3.17.5", "vscode-ws-jsonrpc": "~3.5.0" }, + "devDependencies": { + "@codingame/monaco-vscode-standalone-css-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-html-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-json-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-languages": "~19.1.4", + "@codingame/monaco-vscode-standalone-typescript-language-features": "~19.1.4" + }, "engines": { "node": ">=20.10.0", "npm": ">=10.2.3" @@ -11104,7 +11127,6 @@ "express": "~5.1.0", "jszip": "~3.10.1", "langium": "~4.0.0", - "monaco-editor-wrapper": "~7.0.0-next.0", "monaco-languageclient": "~10.0.0-next.0", "pyright": "~1.1.403", "react": "~19.1.1", @@ -11149,6 +11171,7 @@ "packages/wrapper": { "name": "monaco-editor-wrapper", "version": "7.0.0-next.0", + "extraneous": true, "license": "MIT", "dependencies": { "@codingame/monaco-vscode-api": "~19.1.4", @@ -11200,7 +11223,6 @@ "license": "MIT", "dependencies": { "@codingame/monaco-vscode-editor-api": "~19.1.4", - "monaco-editor-wrapper": "~7.0.0-next.0", "react": ">=18.0.0 || <20.0.0" }, "engines": { diff --git a/package.json b/package.json index c99be20a1..5fba9d714 100644 --- a/package.json +++ b/package.json @@ -47,9 +47,9 @@ "compile": "npm run compile --workspaces", "watch:clean": "tsc --build tsconfig.build.json --clean", "watch": "tsc --build tsconfig.build.json --watch --verbose", - "watch:tsgo": "tsgo -p tsconfig.build.json --watch", - "build:tsc": "tsc -p tsconfig.build.json --verbose", - "build:tsgo": "tsgo -p tsconfig.build.json --verbose", + "watch:tsgo": "tsgo --build tsconfig.build.json --watch --verbose", + "build:tsc": "tsc --build tsconfig.build.json --verbose", + "build:tsgo": "tsgo --build tsconfig.build.json --verbose", "lint": "eslint", "production:build": "npm run production:build --workspace packages/examples", "production:preview:build": "npm run production:preview:build --workspace packages/examples", @@ -68,13 +68,13 @@ "build:examples": "npm run build --workspace packages/examples", "start:example:server:json": "npm run start:server:json --workspace packages/examples", "start:example:server:python": "npm run start:server:python --workspace packages/examples", - "release:prepare": "npm run reset:repo && npm ci && npm run build && npm run lint && npm run test:run", + "release:prepare": "npm run reset:repo && npm ci && npm run build && npm run lint && npm run test", "reset:chrome": "shx rm -fr ./.chrome", "reset:repo": "git clean -f -d -x --exclude=.chrome", "test": "npm run test:install && npm run test:direct", - "test:direct": "vitest --config vitest.config.ts", - "test:run": "npm run test:install && npm run test:direct:run", - "test:direct:run": "vitest --config vitest.config.ts --run", + "test:direct:watch": "vitest --config vitest.config.ts", + "test:watch": "npm run test:install && npm run test:direct:watch", + "test:direct": "vitest --config vitest.config.ts --run", "test:debug": "npm run test:playwright -- --inspect-brk=20222 --browser --no-file-parallelism", "test:install": "playwright install --with-deps chromium" }, diff --git a/packages/client/package.json b/packages/client/package.json index ad74f7649..6c3f55237 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -24,25 +24,33 @@ "types": "./lib/index.d.ts", "default": "./lib/index.js" }, - "./tools": { - "types": "./lib/tools/index.d.ts", - "default": "./lib/tools/index.js" + "./common": { + "types": "./lib/common/index.d.ts", + "default": "./lib/common/index.js" }, - "./vscode/services": { + "./vscodeApiWrapper": { "types": "./lib/vscode/index.d.ts", "default": "./lib/vscode/index.js" }, + "./vscodeApiLocales": { + "types": "./lib/vscode/locales.d.ts", + "default": "./lib/vscode/locales.js" + }, "./fs": { "types": "./lib/fs/index.d.ts", "default": "./lib/fs/index.js" }, "./workerFactory": { - "types": "./lib/workerFactory.d.ts", - "default": "./lib/workerFactory.js" + "types": "./lib/worker/index.d.ts", + "default": "./lib/worker/index.js" + }, + "./lcwrapper": { + "types": "./lib/wrapper/index.d.ts", + "default": "./lib/wrapper/index.js" }, - "./wrapper": { - "types": "./lib/languageClientWrapper.d.ts", - "default": "./lib/languageClientWrapper.js" + "./editorApp": { + "types": "./lib/editorApp/index.d.ts", + "default": "./lib/editorApp/index.js" } }, "typesVersions": { @@ -50,20 +58,26 @@ ".": [ "lib/index.d.ts" ], - "tools": [ - "lib/tools/index" + "common": [ + "lib/common/index" ], - "vscode/services": [ + "vscodeApiWrapper": [ "lib/vscode/index" ], + "vscodeApiLocales": [ + "lib/vscode/locales" + ], "fs": [ "lib/fs/index" ], "workerFactory": [ - "lib/workerFactory" + "lib/worker/index" ], - "wrapper": [ - "lib/languageClientWrapper" + "lcwrapper": [ + "lib/lrapper/index" + ], + "editorApp": [ + "lib/editorApp/index" ] } }, @@ -90,13 +104,41 @@ "@codingame/monaco-vscode-extensions-service-override": "~19.1.4", "@codingame/monaco-vscode-extension-api": "~19.1.4", "@codingame/monaco-vscode-languages-service-override": "~19.1.4", + "@codingame/monaco-vscode-language-pack-cs": "~19.1.4", + "@codingame/monaco-vscode-language-pack-de": "~19.1.4", + "@codingame/monaco-vscode-language-pack-es": "~19.1.4", + "@codingame/monaco-vscode-language-pack-fr": "~19.1.4", + "@codingame/monaco-vscode-language-pack-it": "~19.1.4", + "@codingame/monaco-vscode-language-pack-ja": "~19.1.4", + "@codingame/monaco-vscode-language-pack-ko": "~19.1.4", + "@codingame/monaco-vscode-language-pack-pl": "~19.1.4", + "@codingame/monaco-vscode-language-pack-pt-br": "~19.1.4", + "@codingame/monaco-vscode-language-pack-qps-ploc": "~19.1.4", + "@codingame/monaco-vscode-language-pack-ru": "~19.1.4", + "@codingame/monaco-vscode-language-pack-tr": "~19.1.4", + "@codingame/monaco-vscode-language-pack-zh-hans": "~19.1.4", + "@codingame/monaco-vscode-language-pack-zh-hant": "~19.1.4", "@codingame/monaco-vscode-localization-service-override": "~19.1.4", "@codingame/monaco-vscode-log-service-override": "~19.1.4", "@codingame/monaco-vscode-model-service-override": "~19.1.4", - "vscode": "npm:@codingame/monaco-vscode-extension-api@~19.1.4", + "@codingame/monaco-vscode-monarch-service-override": "~19.1.4", + "@codingame/monaco-vscode-textmate-service-override": "~19.1.4", + "@codingame/monaco-vscode-theme-defaults-default-extension": "~19.1.4", + "@codingame/monaco-vscode-theme-service-override": "~19.1.4", + "@codingame/monaco-vscode-views-service-override": "~19.1.4", + "@codingame/monaco-vscode-workbench-service-override": "~19.1.4", "vscode-languageclient": "~9.0.1", + "vscode-languageserver-protocol": "~3.17.5", "vscode-ws-jsonrpc": "~3.5.0" }, + "devDependencies": { + "@codingame/monaco-vscode-standalone-languages": "~19.1.4", + "@codingame/monaco-vscode-standalone-css-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-html-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-json-language-features": "~19.1.4", + "@codingame/monaco-vscode-standalone-typescript-language-features": "~19.1.4" + }, + "scripts": { "clean": "shx rm -fr ./lib *.tsbuildinfo", "compile": "tsc --build tsconfig.src.json", diff --git a/packages/client/src/client.ts b/packages/client/src/client.ts deleted file mode 100644 index eaaf9cad8..000000000 --- a/packages/client/src/client.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) 2024 TypeFox and others. - * Licensed under the MIT License. See LICENSE in the package root for license information. - * ------------------------------------------------------------------------------------------ */ - -import { BaseLanguageClient, MessageTransports, type LanguageClientOptions } from 'vscode-languageclient/browser.js'; - -export type MonacoLanguageClientOptions = { - name: string; - id?: string; - clientOptions: LanguageClientOptions; - messageTransports: MessageTransports; -} - -export class MonacoLanguageClient extends BaseLanguageClient { - protected readonly messageTransports: MessageTransports; - - constructor({ id, name, clientOptions, messageTransports }: MonacoLanguageClientOptions) { - super(id ?? name.toLowerCase(), name, clientOptions); - this.messageTransports = messageTransports; - } - - protected override createMessageTransports(_encoding: string): Promise { - return Promise.resolve(this.messageTransports); - } -} diff --git a/packages/client/src/commonTypes.ts b/packages/client/src/common/commonTypes.ts similarity index 93% rename from packages/client/src/commonTypes.ts rename to packages/client/src/common/commonTypes.ts index 537570f7e..14f1d9d9a 100644 --- a/packages/client/src/commonTypes.ts +++ b/packages/client/src/common/commonTypes.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import type { MonacoLanguageClient } from './client.js'; +import { BaseLanguageClient } from 'vscode-languageclient/browser.js'; export type ConnectionConfigOptions = WebSocketConfigOptionsDirect | WebSocketConfigOptionsParams | WebSocketConfigOptionsUrl | WorkerConfigOptionsParams | WorkerConfigOptionsDirect; export interface WebSocketCallOptions { /** Adds handle on languageClient */ - onCall: (languageClient?: MonacoLanguageClient) => void; + onCall: (languageClient?: BaseLanguageClient) => void; /** Reports Status Of Language Client */ reportStatus?: boolean; } diff --git a/packages/client/src/tools/index.ts b/packages/client/src/common/index.ts similarity index 91% rename from packages/client/src/tools/index.ts rename to packages/client/src/common/index.ts index e7fde78b9..39639bb8a 100644 --- a/packages/client/src/tools/index.ts +++ b/packages/client/src/common/index.ts @@ -3,5 +3,6 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ +export * from './commonTypes.js'; export * from './logging.js'; export * from './utils.js'; diff --git a/packages/client/src/tools/logging.ts b/packages/client/src/common/logging.ts similarity index 100% rename from packages/client/src/tools/logging.ts rename to packages/client/src/common/logging.ts diff --git a/packages/client/src/tools/utils.ts b/packages/client/src/common/utils.ts similarity index 72% rename from packages/client/src/tools/utils.ts rename to packages/client/src/common/utils.ts index 1a0190239..fe55393a8 100644 --- a/packages/client/src/tools/utils.ts +++ b/packages/client/src/common/utils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import type { WebSocketUrlParams, WebSocketUrlString } from 'monaco-languageclient'; +import type { WebSocketUrlParams, WebSocketUrlString } from './commonTypes.js'; export const createUrl = (config: WebSocketUrlParams | WebSocketUrlString) => { let buildUrl = ''; @@ -37,3 +37,18 @@ export const createUrl = (config: WebSocketUrlParams | WebSocketUrlString) => { } return buildUrl; }; + +export const verifyUrlOrCreateDataUrl = (input: string | URL) => { + if (input instanceof URL) { + return input.href; + } else { + const bytes = new TextEncoder().encode(input); + const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join(''); + const base64 = btoa(binString); + return new URL(`data:text/plain;base64,${base64}`).href; + } +}; + +export const delayExecution = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; diff --git a/packages/client/src/editorApp/config.ts b/packages/client/src/editorApp/config.ts new file mode 100644 index 000000000..91aba539c --- /dev/null +++ b/packages/client/src/editorApp/config.ts @@ -0,0 +1,67 @@ +/* -------------------------------------------------------------------------------------------- + * 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 { type ITextFileEditorModel } from '@codingame/monaco-vscode-api/monaco'; +import * as monaco from '@codingame/monaco-vscode-editor-api'; +import type { IReference } from '@codingame/monaco-vscode-editor-service-override'; +import { type OverallConfigType } from 'monaco-languageclient/vscodeApiWrapper'; + +export class ModelRefs { + modified?: IReference; + original?: IReference; +} + +export interface TextModels { + modified?: monaco.editor.ITextModel | null; + original?: monaco.editor.ITextModel | null; +} + +export interface TextContents { + modified?: string; + original?: string; +} + +export interface CodeContent { + text: string; + uri: string; + enforceLanguageId?: string; +} + +export interface CodeResources { + modified?: CodeContent; + original?: CodeContent; +} + +export interface CallbackDisposeable { + modified?: monaco.IDisposable; + original?: monaco.IDisposable; +} + +export interface DisposableModelRefs { + modified?: IReference; + original?: IReference; +} + +export interface EditorAppConfig { + id?: string; + $type?: OverallConfigType; + logLevel?: LogLevel | number; + codeResources?: CodeResources; + useDiffEditor?: boolean; + domReadOnly?: boolean; + readOnly?: boolean; + overrideAutomaticLayout?: boolean; + editorOptions?: monaco.editor.IStandaloneEditorConstructionOptions; + diffEditorOptions?: monaco.editor.IStandaloneDiffEditorConstructionOptions; + languageDef?: { + languageExtensionConfig: monaco.languages.ILanguageExtensionPoint; + monarchLanguage?: monaco.languages.IMonarchLanguage; + theme?: { + name: monaco.editor.BuiltinTheme | string; + data: monaco.editor.IStandaloneThemeData; + } + } +} diff --git a/packages/wrapper/src/editorApp.ts b/packages/client/src/editorApp/editorApp.ts similarity index 59% rename from packages/wrapper/src/editorApp.ts rename to packages/client/src/editorApp/editorApp.ts index 637d331ef..82403ec0d 100644 --- a/packages/wrapper/src/editorApp.ts +++ b/packages/client/src/editorApp/editorApp.ts @@ -3,69 +3,14 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import * as vscode from 'vscode'; -import * as monaco from '@codingame/monaco-vscode-editor-api'; -import { LogLevel } from '@codingame/monaco-vscode-api'; +import { ConfigurationTarget, IConfigurationService, LogLevel, StandaloneServices } from '@codingame/monaco-vscode-api'; import { createModelReference, type ITextFileEditorModel } from '@codingame/monaco-vscode-api/monaco'; -import { ConfigurationTarget, IConfigurationService, StandaloneServices } from '@codingame/monaco-vscode-api'; +import * as monaco from '@codingame/monaco-vscode-editor-api'; import type { IReference } from '@codingame/monaco-vscode-editor-service-override'; -import type { Logger } from 'monaco-languageclient/tools'; -import type { OverallConfigType } from './vscode/services.js'; - -export interface ModelRefs { - modified: IReference; - original?: IReference; -} - -export interface TextModels { - modified?: monaco.editor.ITextModel | null; - original?: monaco.editor.ITextModel | null; -} - -export interface TextContents { - modified?: string; - original?: string; -} - -export interface CodeContent { - text: string; - uri: string; - enforceLanguageId?: string; -} - -export interface CodeResources { - modified?: CodeContent; - original?: CodeContent; -} - -export interface CallbackDisposeable { - modified?: monaco.IDisposable; - original?: monaco.IDisposable; -} - -export interface DisposableModelRefs { - modified?: IReference; - original?: IReference; -} - -export interface EditorAppConfig { - codeResources?: CodeResources; - useDiffEditor?: boolean; - domReadOnly?: boolean; - readOnly?: boolean; - overrideAutomaticLayout?: boolean; - editorOptions?: monaco.editor.IStandaloneEditorConstructionOptions; - diffEditorOptions?: monaco.editor.IStandaloneDiffEditorConstructionOptions; - monacoWorkerFactory?: (logger?: Logger) => void; - languageDef?: { - languageExtensionConfig: monaco.languages.ILanguageExtensionPoint; - monarchLanguage?: monaco.languages.IMonarchLanguage; - theme?: { - name: monaco.editor.BuiltinTheme | string; - data: monaco.editor.IStandaloneThemeData; - } - } -} +import { ConsoleLogger, type Logger } from 'monaco-languageclient/common'; +import { getEnhancedMonacoEnvironment, type OverallConfigType } from 'monaco-languageclient/vscodeApiWrapper'; +import * as vscode from 'vscode'; +import { ModelRefs, type CallbackDisposeable, type CodeContent, type CodeResources, type DisposableModelRefs, type EditorAppConfig, type TextContents, type TextModels } from './config.js'; /** * This is the base class for both Monaco Ediotor Apps: @@ -79,12 +24,13 @@ export class EditorApp { private $type: OverallConfigType; private id: string; private config: EditorAppConfig; - protected logger: Logger | undefined; + + protected logger: Logger = new ConsoleLogger(); private editor: monaco.editor.IStandaloneCodeEditor | undefined; private diffEditor: monaco.editor.IStandaloneDiffEditor | undefined; - private modelRefs: ModelRefs; + private modelRefs: ModelRefs = new ModelRefs(); private onTextChanged?: (textChanges: TextContents) => void; private textChangedDiposeables: CallbackDisposeable = {}; @@ -92,10 +38,18 @@ export class EditorApp { private modelRefDisposeTimeout = -1; - constructor($type: OverallConfigType, id: string, userAppConfig?: EditorAppConfig, logger?: Logger) { - this.$type = $type; - this.id = id; - this.logger = logger; + private startingAwait?: Promise; + private startingResolve: (value: void | PromiseLike) => void; + + private disposingAwait?: Promise; + private disposingResolve: (value: void | PromiseLike) => void; + + constructor(userAppConfig?: EditorAppConfig) { + this.$type = userAppConfig?.$type ?? 'extended'; + this.id = userAppConfig?.id ?? Math.floor(Math.random() * 1000001).toString(); + if ((userAppConfig?.useDiffEditor ?? false) && !userAppConfig?.codeResources?.original) { + throw new Error(`Use diff editor was used without a valid config. code: ${userAppConfig?.codeResources?.modified} codeOriginal: ${userAppConfig?.codeResources?.original}`); + } this.config = { codeResources: userAppConfig?.codeResources ?? undefined, useDiffEditor: userAppConfig?.useDiffEditor ?? false, @@ -112,14 +66,16 @@ export class EditorApp { automaticLayout: userAppConfig?.overrideAutomaticLayout ?? true }; this.config.languageDef = userAppConfig?.languageDef; + + this.logger.setLevel(this.config.logLevel ?? LogLevel.Off); } - getConfig(): EditorAppConfig { - return this.config; + isDiffEditor() { + return this.config.useDiffEditor === true; } - haveEditor() { - return this.editor !== undefined || this.diffEditor !== undefined; + getConfig(): EditorAppConfig { + return this.config; } getEditor(): monaco.editor.IStandaloneCodeEditor | undefined { @@ -132,12 +88,16 @@ export class EditorApp { getTextModels(): TextModels { return { - modified: this.modelRefs.modified.object.textEditorModel, + modified: this.modelRefs.modified?.object.textEditorModel ?? undefined, original: this.modelRefs.original?.object.textEditorModel ?? undefined }; } - registerOnTextChangedCallbacks(onTextChanged?: (textChanges: TextContents) => void) { + getLogger() { + return this.logger; + } + + registerOnTextChangedCallback(onTextChanged?: (textChanges: TextContents) => void) { this.onTextChanged = onTextChanged; } @@ -145,7 +105,42 @@ export class EditorApp { this.modelRefDisposeTimeout = modelRefDisposeTimeout; } - async init(): Promise { + private markStarting() { + this.startingAwait = new Promise((resolve) => { + this.startingResolve = resolve; + }); + } + + private markStarted() { + this.startingResolve(); + this.startingAwait = undefined; + } + + isStarting() { + return this.startingAwait !== undefined; + } + + getStartingAwait() { + return this.startingAwait; + } + + isStarted() { + return this.editor !== undefined || this.diffEditor !== undefined; + } + + /** + * Starts the single editor application. + */ + async start(htmlContainer: HTMLElement) { + if (this.isStarting()) { + await this.getStartingAwait(); + } + this.markStarting(); + + if (!this.isDisposed()) { + throw new Error('You called start without properly disposing the EditorApp.'); + } + const languageDef = this.config.languageDef; if (languageDef) { if (this.$type === 'extended') { @@ -184,11 +179,9 @@ export class EditorApp { uri: this.config.codeResources?.modified?.uri ?? `default-uri-modified-${this.id}`, enforceLanguageId: this.config.codeResources?.modified?.enforceLanguageId ?? undefined }; - this.modelRefs = { - modified: await this.buildModelReference(modified, this.logger) - }; + this.modelRefs.modified = await this.buildModelReference(modified, this.logger); - if (this.config.useDiffEditor === true) { + if (this.isDiffEditor()) { const original = { text: this.config.codeResources?.original?.text ?? '', uri: this.config.codeResources?.original?.uri ?? `default-uri-original-${this.id}`, @@ -197,21 +190,42 @@ export class EditorApp { this.modelRefs.original = await this.buildModelReference(original, this.logger); } - this.logger?.info('Init of EditorApp was completed.'); + try { + const envEnhanced = getEnhancedMonacoEnvironment(); + const viewServiceType = envEnhanced.viewServiceType; + if (viewServiceType === 'EditorService' || viewServiceType === undefined) { + this.logger.info(`Starting monaco-editor (${this.id})`); + await this.createEditors(htmlContainer!); + } else { + this.logger.info('No EditorService configured. monaco-editor will not be started.'); + } + + this.logger.info('EditorApp start completed successfully.'); + // eslint-disable-next-line no-useless-catch + } catch (e) { + throw e; + } finally { + // in case of rejection, mark as started, otherwise the promise will never resolve + this.markStarted(); + } } async createEditors(htmlContainer: HTMLElement): Promise { - if (this.config.useDiffEditor === true) { + if (this.isDiffEditor()) { this.diffEditor = monaco.editor.createDiffEditor(htmlContainer, this.config.diffEditorOptions); - const model = { - modified: this.modelRefs.modified.object.textEditorModel!, - original: this.modelRefs.original!.object.textEditorModel! - }; - this.diffEditor.setModel(model); - this.announceModelUpdate(model); + const modified = this.modelRefs.modified?.object.textEditorModel ?? undefined; + const original = this.modelRefs.original?.object.textEditorModel ?? undefined; + if (modified !== undefined && original !== undefined) { + const model = { + modified, + original + }; + this.diffEditor.setModel(model); + this.announceModelUpdate(model); + } } else { const model = { - modified: this.modelRefs.modified.object.textEditorModel + modified: this.modelRefs.modified?.object.textEditorModel }; this.editor = monaco.editor.create(htmlContainer, { ...this.config.editorOptions, @@ -221,11 +235,27 @@ export class EditorApp { } } + updateCode(code: { modified?: string, original?: string }) { + if (this.isDiffEditor()) { + if (code.modified !== undefined) { + this.diffEditor?.getModifiedEditor().setValue(code.modified); + } + if (code.original !== undefined) { + this.diffEditor?.getOriginalEditor().setValue(code.original); + } + } else { + if (code.modified !== undefined) { + this.editor?.setValue(code.modified); + } + + } + } + async updateCodeResources(codeResources?: CodeResources): Promise { let updateModified = false; let updateOriginal = false; - if (codeResources?.modified !== undefined && codeResources.modified.uri !== this.modelRefs.modified.object.resource.path) { + if (codeResources?.modified !== undefined && codeResources.modified.uri !== this.modelRefs.modified?.object.resource.path) { this.modelDisposables.modified = this.modelRefs.modified; this.modelRefs.modified = await this.buildModelReference(codeResources.modified, this.logger); updateModified = true; @@ -236,27 +266,32 @@ export class EditorApp { updateOriginal = true; } - if (this.config.useDiffEditor === true) { + if (this.isDiffEditor()) { if (updateModified && updateOriginal) { - const model = { - modified: this.modelRefs.modified.object.textEditorModel!, - original: this.modelRefs.original!.object.textEditorModel! - }; - this.diffEditor?.setModel(model); - - this.announceModelUpdate(model); + const modified = this.modelRefs.modified?.object.textEditorModel ?? undefined; + const original = this.modelRefs.original?.object.textEditorModel ?? undefined; + if (modified !== undefined && original !== undefined) { + const model = { + modified, + original + }; + this.diffEditor?.setModel(model); + this.announceModelUpdate(model); + } } else { - this.logger?.info('Diff Editor: Code resources were not updated. They are ether unchanged or undefined.'); + this.logger.info('Diff Editor: Code resources were not updated. They are ether unchanged or undefined.'); } } else { if (updateModified) { const model = { - modified: this.modelRefs.modified.object.textEditorModel + modified: this.modelRefs.modified?.object.textEditorModel }; - this.editor?.setModel(model.modified); - this.announceModelUpdate(model); + if (model.modified !== undefined && model.modified !== null) { + this.editor?.setModel(model.modified); + this.announceModelUpdate(model); + } } else { - this.logger?.info('Editor: Code resources were not updated. They are either unchanged or undefined.'); + this.logger.info('Editor: Code resources were not updated. They are either unchanged or undefined.'); } } @@ -308,6 +343,11 @@ export class EditorApp { } async dispose() { + if (this.isDisposing()) { + await this.getDisposingAwait(); + } + this.markDisposing(); + if (this.editor) { this.editor.dispose(); this.editor = undefined; @@ -319,30 +359,59 @@ export class EditorApp { this.textChangedDiposeables.modified?.dispose(); this.textChangedDiposeables.original?.dispose(); + this.textChangedDiposeables.modified = undefined; + this.textChangedDiposeables.original = undefined; await this.disposeModelRefs(); + + this.markDisposed(); + } + + isDisposed(): boolean { + return this.editor === undefined && this.diffEditor === undefined && + // this.textChangedDiposeables.modified === undefined && this.textChangedDiposeables.original === undefined && + this.modelDisposables.original === undefined && this.modelDisposables.modified === undefined; + } + + private markDisposing() { + this.disposingAwait = new Promise((resolve) => { + this.disposingResolve = resolve; + }); + } + + private markDisposed() { + this.disposingResolve(); + this.disposingAwait = undefined; + } + + isDisposing() { + return this.disposingAwait !== undefined; + } + + getDisposingAwait() { + return this.disposingAwait; } async disposeModelRefs() { const diposeRefs = () => { - if (this.logger?.getLevel() === LogLevel.Debug) { + if (this.logger.getLevel() === LogLevel.Debug) { const models = monaco.editor.getModels(); this.logger.debug('Current model URIs:'); models.forEach((model, _index) => { - this.logger?.debug(`${model.uri.toString()}`); + this.logger.debug(`${model.uri.toString()}`); }); } - if (this.modelDisposables.modified !== undefined && !this.modelDisposables.modified.object.isDisposed()) { + if (this.modelDisposables.modified !== undefined && !(this.modelDisposables.modified.object.isDisposed() === true)) { this.modelDisposables.modified.dispose(); this.modelDisposables.modified = undefined; } - if (this.modelDisposables.original !== undefined && !this.modelDisposables.original.object.isDisposed()) { + if (this.modelDisposables.original !== undefined && !(this.modelDisposables.original.object.isDisposed() === true)) { this.modelDisposables.original.dispose(); this.modelDisposables.original = undefined; } - if (this.logger?.getLevel() === LogLevel.Debug) { + if (this.logger.getLevel() === LogLevel.Debug) { if (this.modelDisposables.modified === undefined && this.modelDisposables.original === undefined) { this.logger.debug('All model references are disposed.'); } else { @@ -352,7 +421,7 @@ export class EditorApp { }; if (this.modelRefDisposeTimeout > 0) { - this.logger?.debug('Using async dispose of model references'); + this.logger.debug('Using async dispose of model references'); await new Promise(resolve => setTimeout(() => { diposeRefs(); resolve(); @@ -363,25 +432,21 @@ export class EditorApp { } updateLayout() { - if (this.config.useDiffEditor ?? false) { + if (this.isDiffEditor()) { this.diffEditor?.layout(); } else { this.editor?.layout(); } } -} - -export const verifyUrlOrCreateDataUrl = (input: string | URL) => { - if (input instanceof URL) { - return input.href; - } else { - const bytes = new TextEncoder().encode(input); - const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join(''); - const base64 = btoa(binString); - return new URL(`data:text/plain;base64,${base64}`).href; + reportStatus() { + const status: string[] = []; + status.push('EditorApp status:'); + status.push(`Editor: ${this.editor?.getId()}`); + status.push(`DiffEditor: ${this.diffEditor?.getId()}`); + return status; } -}; +} export const didModelContentChange = (textModels: TextModels, onTextChanged?: (textChanges: TextContents) => void) => { const modified = textModels.modified?.getValue() ?? ''; diff --git a/packages/wrapper/src/index.ts b/packages/client/src/editorApp/index.ts similarity index 92% rename from packages/wrapper/src/index.ts rename to packages/client/src/editorApp/index.ts index a0bfa2c6a..d94fd5316 100644 --- a/packages/wrapper/src/index.ts +++ b/packages/client/src/editorApp/index.ts @@ -3,5 +3,5 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ +export * from './config.js'; export * from './editorApp.js'; -export * from './wrapper.js'; diff --git a/packages/client/src/fs/definitions.ts b/packages/client/src/fs/definitions.ts index 64bd62116..1e875c195 100644 --- a/packages/client/src/fs/definitions.ts +++ b/packages/client/src/fs/definitions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import type { Logger } from 'monaco-languageclient/tools'; +import type { Logger } from 'monaco-languageclient/common'; export interface FileReadRequest { resourceUri: string diff --git a/packages/client/src/fs/endpoints/defaultEndpoint.ts b/packages/client/src/fs/endpoints/defaultEndpoint.ts index a8c01fef0..bd4e8d4c6 100644 --- a/packages/client/src/fs/endpoints/defaultEndpoint.ts +++ b/packages/client/src/fs/endpoints/defaultEndpoint.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import type { Logger } from 'monaco-languageclient/tools'; +import type { Logger } from 'monaco-languageclient/common'; import type { DirectoryListingRequest, DirectoryListingRequestResult, FileReadRequest, FileReadRequestResult, FileSystemEndpoint, FileUpdate, FileUpdateResult, StatsRequest, StatsRequestResult } from '../definitions.js'; import { EndpointType } from '../definitions.js'; diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 02b191395..f51a86d84 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -3,5 +3,32 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -export * from './client.js'; -export * from './commonTypes.js'; +import { BaseLanguageClient, MessageTransports, ProposedFeatures, type LanguageClientOptions } from 'vscode-languageclient/browser.js'; + +export type MonacoLanguageClientOptions = { + name: string; + id?: string; + clientOptions: LanguageClientOptions; + messageTransports: MessageTransports; +} + +export class MonacoLanguageClient extends BaseLanguageClient { + protected readonly messageTransports: MessageTransports; + + constructor({ id, name, clientOptions, messageTransports }: MonacoLanguageClientOptions) { + super(id ?? name.toLowerCase(), name, clientOptions); + this.messageTransports = messageTransports; + ProposedFeatures.createAll(this); + } + + protected override createMessageTransports(_encoding: string): Promise { + return Promise.resolve(this.messageTransports); + } +} + +export class MonacoLanguageClientWithProposedFeatures extends MonacoLanguageClient { + constructor({ id, name, clientOptions, messageTransports }: MonacoLanguageClientOptions) { + super({ id, name, clientOptions, messageTransports }); + ProposedFeatures.createAll(this); + } +} diff --git a/packages/client/src/vscode/apiWrapper.ts b/packages/client/src/vscode/apiWrapper.ts new file mode 100644 index 000000000..c9ada52fa --- /dev/null +++ b/packages/client/src/vscode/apiWrapper.ts @@ -0,0 +1,333 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { initialize, LogLevel } from '@codingame/monaco-vscode-api'; +import { ExtensionHostKind, getExtensionManifests, registerExtension, type IExtensionManifest, type RegisterExtensionResult } from '@codingame/monaco-vscode-api/extensions'; +import { DisposableStore, setUnexpectedErrorHandler } from '@codingame/monaco-vscode-api/monaco'; +import getConfigurationServiceOverride, { initUserConfiguration } from '@codingame/monaco-vscode-configuration-service-override'; +import getLanguagesServiceOverride from '@codingame/monaco-vscode-languages-service-override'; +import getLogServiceOverride from '@codingame/monaco-vscode-log-service-override'; +import getModelServiceOverride from '@codingame/monaco-vscode-model-service-override'; +import { ConsoleLogger, verifyUrlOrCreateDataUrl, type Logger } from 'monaco-languageclient/common'; +import { useWorkerFactory } from 'monaco-languageclient/workerFactory'; +import * as vscode from 'vscode'; +import 'vscode/localExtensionHost'; +import type { MonacoVscodeApiConfig } from './config.js'; +import { configureExtHostWorker, getEnhancedMonacoEnvironment, mergeServices, reportServiceLoading, useOpenEditorStub } from './utils.js'; + +export interface InitServicesInstructions { + caller?: string; + performServiceConsistencyChecks?: boolean; + htmlContainer?: HTMLElement | null; +} + +export class MonacoVscodeApiWrapper { + + private logger: Logger = new ConsoleLogger(); + private extensionRegisterResults: Map = new Map(); + private disposableStore: DisposableStore = new DisposableStore(); + private apiConfig: MonacoVscodeApiConfig; + + constructor(apiConfig: MonacoVscodeApiConfig) { + this.apiConfig = apiConfig; + this.apiConfig.logLevel = this.apiConfig.logLevel ?? LogLevel.Off; + this.logger.setLevel(this.apiConfig.logLevel); + } + + getLogger(): Logger { + return this.logger; + } + + getExtensionRegisterResult(extensionName: string) { + return this.extensionRegisterResults.get(extensionName); + } + + getMonacoVscodeApiConfig(): MonacoVscodeApiConfig { + return this.apiConfig; + } + + protected configureMonacoWorkers() { + if (typeof this.apiConfig.monacoWorkerFactory === 'function') { + this.apiConfig.monacoWorkerFactory(this.logger); + } else { + useWorkerFactory({ + logger: this.logger + }); + } + } + + protected async configureHighlightingServices() { + if (this.apiConfig.$type === 'extended') { + const getTextmateServiceOverride = (await import('@codingame/monaco-vscode-textmate-service-override')).default; + const getThemeServiceOverride = (await import('@codingame/monaco-vscode-theme-service-override')).default; + mergeServices(this.apiConfig.serviceOverrides, { + ...getTextmateServiceOverride(), + ...getThemeServiceOverride() + }); + } else { + const getMonarchServiceOverride = (await import('@codingame/monaco-vscode-monarch-service-override')).default; + mergeServices(this.apiConfig.serviceOverrides, { + ...getMonarchServiceOverride() + }); + } + } + + protected async configureViewsServices() { + const viewServiceType = this.apiConfig.viewsConfig?.viewServiceType ?? 'EditorService'; + if (this.apiConfig.$type === 'classic' && (viewServiceType === 'ViewsService' || viewServiceType === 'WorkspaceService')) { + throw new Error(`View Service Type "${viewServiceType}" cannot be used with classic configuration.`); + } + + const envEnhanced = getEnhancedMonacoEnvironment(); + if (this.apiConfig.viewsConfig?.viewServiceType === 'ViewsService') { + const getViewsServiceOverride = (await import('@codingame/monaco-vscode-views-service-override')).default; + mergeServices(this.apiConfig.serviceOverrides, { + ...getViewsServiceOverride(this.apiConfig.viewsConfig.openEditorFunc ?? useOpenEditorStub) + }); + envEnhanced.viewServiceType = 'ViewsService'; + } else if (this.apiConfig.viewsConfig?.viewServiceType === 'WorkspaceService') { + const getWorkbenchServiceOverride = (await import('@codingame/monaco-vscode-workbench-service-override')).default; + mergeServices(this.apiConfig.serviceOverrides, { + ...getWorkbenchServiceOverride() + }); + envEnhanced.viewServiceType = 'WorkspaceService'; + } else { + const getEditorServiceOverride = (await import('@codingame/monaco-vscode-editor-service-override')).default; + mergeServices(this.apiConfig.serviceOverrides, { + ...getEditorServiceOverride(this.apiConfig.viewsConfig?.openEditorFunc ?? useOpenEditorStub) + }); + envEnhanced.viewServiceType = 'EditorService'; + } + } + + protected async applyViewsPostConfig() { + this.apiConfig.viewsConfig?.htmlAugmentationInstructions?.(this.apiConfig.htmlContainer); + await this.apiConfig.viewsConfig?.viewsInitFunc?.(); + } + + /** + * Adding the default workspace config if not provided + */ + protected configureWorkspaceConfig() { + if (this.apiConfig.workspaceConfig === undefined) { + this.apiConfig.workspaceConfig = { + workspaceProvider: { + trusted: true, + workspace: { + workspaceUri: vscode.Uri.file('/workspace.code-workspace') + }, + async open() { + window.open(window.location.href); + return true; + } + }, + }; + } + } + + /** + * set the log-level via the development settings + */ + protected configureDevLogLevel() { + const devLogLevel = this.apiConfig.workspaceConfig?.developmentOptions?.logLevel; + if (devLogLevel === undefined) { + + // this needs to be done so complicated, because developmentOptions is read-only + const devOptions: Record = { + ...this.apiConfig.workspaceConfig!.developmentOptions + }; + devOptions.logLevel = this.apiConfig.logLevel; + (this.apiConfig.workspaceConfig!.developmentOptions as Record) = Object.assign({}, devOptions); + } else if (devLogLevel !== this.apiConfig.logLevel) { + throw new Error(`You have configured mismatching logLevels: ${this.apiConfig.logLevel} (wrapperConfig) ${devLogLevel} (workspaceConfig.developmentOptions)`); + } else { + this.logger.debug('Development log level and api log level are in aligned.'); + } + } + + /** + * enable semantic highlighting in the default configuration + */ + protected configureSemanticHighlighting() { + if (this.apiConfig.advanced?.enforceSemanticHighlighting === true) { + const configDefaults: Record = { + ...this.apiConfig.workspaceConfig!.configurationDefaults ?? {} + }; + configDefaults['editor.semanticHighlighting.enabled'] = true; + (this.apiConfig.workspaceConfig!.configurationDefaults as Record) = Object.assign({}, configDefaults); + } + } + + protected async initUserConfiguration() { + if (this.apiConfig.userConfiguration?.json !== undefined) { + await initUserConfiguration(this.apiConfig.userConfiguration.json); + } + } + + protected async supplyRequiredServices() { + return { + ...getConfigurationServiceOverride(), + ...getLanguagesServiceOverride(), + ...getLogServiceOverride(), + ...getModelServiceOverride() + }; + } + + protected checkServiceConsistency() { + const userServices = this.apiConfig.serviceOverrides; + const haveThemeService = Object.keys(userServices).includes('themeService'); + const haveTextmateService = Object.keys(userServices).includes('textMateTokenizationFeature'); + const haveMarkersService = Object.keys(userServices).includes('markersService'); + const haveViewsService = Object.keys(userServices).includes('viewsService'); + + // theme requires textmate + if (haveThemeService && !haveTextmateService) { + throw new Error('"theme" service requires "textmate" service. Please add it to the "userServices".'); + } + + // markers service requires views service + if (haveMarkersService && !haveViewsService) { + throw new Error('"markers" service requires "views" service. Please add it to the "userServices".'); + } + } + + /** + * monaco-vscode-api automatically loads the following services: + * - layout + * - environment + * - extension + * - files + * - quickAccess + * monaco-languageclient always adds the following services: + * - languages + * - log + * - model + */ + protected async importAllServices(instructions?: InitServicesInstructions) { + const services = await this.supplyRequiredServices(); + + mergeServices(services, this.apiConfig.serviceOverrides); + await configureExtHostWorker(this.apiConfig.advanced?.enableExtHostWorker === true, services); + + reportServiceLoading(services, this.logger); + + if (instructions?.performServiceConsistencyChecks === true) { + this.checkServiceConsistency(); + } + + if (this.apiConfig.viewsConfig?.viewServiceType === 'ViewsService' || this.apiConfig.viewsConfig?.viewServiceType === 'WorkspaceService') { + await initialize(services, this.apiConfig.htmlContainer, this.apiConfig.workspaceConfig, this.apiConfig.envOptions); + } else { + await initialize(services, undefined, this.apiConfig.workspaceConfig, this.apiConfig.envOptions); + } + + setUnexpectedErrorHandler((e) => { + this.logger.createErrorAndLog('Unexpected error', e); + }); + } + + async initExtensions(): Promise { + if (this.apiConfig.$type === 'extended' && (this.apiConfig.advanced?.loadThemes === undefined ? true : this.apiConfig.advanced.loadThemes === true)) { + await import('@codingame/monaco-vscode-theme-defaults-default-extension'); + } + + const extensions = this.apiConfig.extensions; + if (this.apiConfig.extensions) { + const allPromises: Array> = []; + const extensionIds: string[] = []; + getExtensionManifests().forEach((ext) => { + extensionIds.push(ext.identifier.id); + }); + for (const extensionConfig of extensions ?? []) { + if (!extensionIds.includes(`${extensionConfig.config.publisher}.${extensionConfig.config.name}`)) { + const manifest = extensionConfig.config as IExtensionManifest; + const extRegResult = registerExtension(manifest, ExtensionHostKind.LocalProcess); + this.extensionRegisterResults.set(manifest.name, extRegResult); + if (extensionConfig.filesOrContents && Object.hasOwn(extRegResult, 'registerFileUrl')) { + for (const entry of extensionConfig.filesOrContents) { + this.disposableStore.add(extRegResult.registerFileUrl(entry[0], verifyUrlOrCreateDataUrl(entry[1]))); + } + } + allPromises.push(extRegResult.whenReady()); + } + } + await Promise.all(allPromises); + } + } + + protected markGlobalInit() { + this.logger.debug('markGlobalInit'); + + const envEnhanced = getEnhancedMonacoEnvironment(); + envEnhanced.vscodeApiGlobalInitAwait = new Promise((resolve) => { + envEnhanced.vscodeApiGlobalInitResolve = resolve; + }); + } + + protected markGlobalInitDone() { + const envEnhanced = getEnhancedMonacoEnvironment(); + if (typeof envEnhanced.vscodeApiGlobalInitResolve === 'function') { + envEnhanced.vscodeApiGlobalInitResolve(); + } + envEnhanced.vscodeApiInitialised = true; + envEnhanced.vscodeApiGlobalInitAwait = undefined; + envEnhanced.vscodeApiGlobalInitResolve = undefined; + this.logger.debug('markGlobalInitDone'); + } + + async init(instructions?: InitServicesInstructions): Promise { + const envEnhanced = getEnhancedMonacoEnvironment(); + if (envEnhanced.vscodeApiInitialised === true) { + this.logger.warn('Initialization of monaco-vscode api can only performed once!'); + } else { + if (!(envEnhanced.vscodeApiInitialising === true)) { + envEnhanced.vscodeApiInitialising = true; + this.markGlobalInit(); + if (instructions?.htmlContainer !== undefined && instructions.htmlContainer !== null) { + this.apiConfig.htmlContainer = instructions.htmlContainer; + } + + // ensures "vscodeApiConfig.workspaceConfig" is available + this.configureWorkspaceConfig(); + + // ensure logging and development logging options are in-line + this.configureDevLogLevel(); + this.logger.info(`Initializing monaco-vscode api. Caller: ${instructions?.caller ?? 'unknown'}`); + + await this.configureMonacoWorkers(); + + // ensure either classic (monarch) or textmate (extended) highlighting is used + await this.configureHighlightingServices(); + + // ensure one of the three potential view services are configured + await this.configureViewsServices(); + + // enforece semantic highlighting if configured + this.configureSemanticHighlighting(); + + await this.initUserConfiguration(); + + await this.importAllServices(instructions); + + await this.applyViewsPostConfig(); + + await this.initExtensions(); + + this.markGlobalInitDone(); + this.logger.debug('Initialization of monaco-vscode api completed successfully.'); + } else { + this.logger.debug('Initialization of monaco-vscode api is already ongoing.'); + } + } + } + + dispose() { + this.extensionRegisterResults.forEach((k) => k.dispose()); + this.disposableStore.dispose(); + + // re-create disposable stores + this.disposableStore = new DisposableStore(); + } +} diff --git a/packages/client/src/vscode/config.ts b/packages/client/src/vscode/config.ts new file mode 100644 index 000000000..1c0870e9c --- /dev/null +++ b/packages/client/src/vscode/config.ts @@ -0,0 +1,55 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as monaco from '@codingame/monaco-vscode-editor-api'; +import { LogLevel } from '@codingame/monaco-vscode-api'; +import type { IWorkbenchConstructionOptions } from '@codingame/monaco-vscode-api'; +import type { IExtensionManifest } from '@codingame/monaco-vscode-api/extensions'; +import type { EnvironmentOverride } from '@codingame/monaco-vscode-api/workbench'; +import type { OpenEditor } from '@codingame/monaco-vscode-editor-service-override'; +import type { Logger } from 'monaco-languageclient/common'; + +export interface MonacoEnvironmentEnhanced extends monaco.Environment { + vscodeApiInitialising?: boolean; + vscodeApiInitialised?: boolean; + vscodeApiGlobalInitAwait?: Promise; + vscodeApiGlobalInitResolve?: ((value: void | PromiseLike) => void); + viewServiceType?: 'EditorService' | 'ViewsService' | 'WorkspaceService'; +} + +export type OverallConfigType = 'extended' | 'classic'; + +export interface UserConfiguration { + json?: string; +} +export interface ViewsConfig { + viewServiceType: 'EditorService' | 'ViewsService' | 'WorkspaceService'; + openEditorFunc?: OpenEditor; + htmlAugmentationInstructions?: (htmlContainer: HTMLElement | null | undefined) => void; + viewsInitFunc?: () => Promise; +} + +export interface ExtensionConfig { + config: IExtensionManifest; + filesOrContents?: Map; +} + +export interface MonacoVscodeApiConfig { + $type: OverallConfigType; + htmlContainer?: HTMLElement; + serviceOverrides: monaco.editor.IEditorOverrideServices; + logLevel?: LogLevel | number; + workspaceConfig?: IWorkbenchConstructionOptions; + userConfiguration?: UserConfiguration; + viewsConfig?: ViewsConfig, + envOptions?: EnvironmentOverride; + extensions?: ExtensionConfig[]; + monacoWorkerFactory?: (logger?: Logger) => void; + advanced?: { + enableExtHostWorker?: boolean; + loadThemes?: boolean; + enforceSemanticHighlighting?: boolean; + }; +} diff --git a/packages/client/src/vscode/index.ts b/packages/client/src/vscode/index.ts index 9fa9c02d2..d41a5f2df 100644 --- a/packages/client/src/vscode/index.ts +++ b/packages/client/src/vscode/index.ts @@ -3,5 +3,7 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -export * from './fakeWorker.js'; -export * from './services.js'; +export * from './config.js'; +export * from './apiWrapper.js'; +export * from './utils.js'; +export * from './viewsService.js'; diff --git a/packages/wrapper/src/vscode/localeLoader.ts b/packages/client/src/vscode/locales.ts similarity index 53% rename from packages/wrapper/src/vscode/localeLoader.ts rename to packages/client/src/vscode/locales.ts index 988ed4c79..4cb8e6844 100644 --- a/packages/wrapper/src/vscode/localeLoader.ts +++ b/packages/client/src/vscode/locales.ts @@ -3,6 +3,72 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ +import type { LocalizationOptions } from '@codingame/monaco-vscode-localization-service-override'; + +export const createDefaultLocaleConfiguration = (): LocalizationOptions => { + return { + async clearLocale() { + const url = new URL(window.location.href); + url.searchParams.delete('locale'); + window.history.pushState(null, '', url.toString()); + }, + async setLocale(id: string) { + const url = new URL(window.location.href); + url.searchParams.set('locale', id); + window.history.pushState(null, '', url.toString()); + }, + availableLanguages: [{ + locale: 'en', + languageName: 'English' + }, { + locale: 'cs', + languageName: 'Czech' + }, { + locale: 'de', + languageName: 'German' + }, { + locale: 'es', + languageName: 'Spanish' + }, { + locale: 'fr', + languageName: 'French' + }, { + locale: 'it', + languageName: 'Italian' + }, { + locale: 'ja', + languageName: 'Japanese' + }, { + locale: 'ko', + languageName: 'Korean' + }, { + locale: 'pl', + languageName: 'Polish' + }, { + locale: 'pt-br', + languageName: 'Portuguese (Brazil)' + }, { + locale: 'qps-ploc', + languageName: 'Pseudo Language' + }, { + locale: 'ru', + languageName: 'Russian' + }, { + locale: 'tr', + languageName: 'Turkish' + }, { + locale: 'zh-hans', + languageName: 'Chinese (Simplified)' + }, { + locale: 'zh-hant', + languageName: 'Chinese (Traditional)' + }, { + locale: 'en', + languageName: 'English' + }] + }; +}; + const localeLoader: Partial Promise>> = { cs: async () => { await import('@codingame/monaco-vscode-language-pack-cs'); diff --git a/packages/client/src/vscode/services.ts b/packages/client/src/vscode/services.ts deleted file mode 100644 index 77d962150..000000000 --- a/packages/client/src/vscode/services.ts +++ /dev/null @@ -1,249 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) 2024 TypeFox and others. - * Licensed under the MIT License. See LICENSE in the package root for license information. - * ------------------------------------------------------------------------------------------ */ - -import * as monaco from '@codingame/monaco-vscode-editor-api'; -import 'vscode/localExtensionHost'; -import { initialize, type IWorkbenchConstructionOptions } from '@codingame/monaco-vscode-api'; -import type { OpenEditor } from '@codingame/monaco-vscode-editor-service-override'; -import type { WorkerConfig } from '@codingame/monaco-vscode-extensions-service-override'; -import getConfigurationServiceOverride, { initUserConfiguration } from '@codingame/monaco-vscode-configuration-service-override'; -import getExtensionServiceOverride from '@codingame/monaco-vscode-extensions-service-override'; -import getLanguagesServiceOverride from '@codingame/monaco-vscode-languages-service-override'; -import getModelServiceOverride from '@codingame/monaco-vscode-model-service-override'; -import getLogServiceOverride from '@codingame/monaco-vscode-log-service-override'; -import type { LocalizationOptions } from '@codingame/monaco-vscode-localization-service-override'; -import type { EnvironmentOverride } from '@codingame/monaco-vscode-api/workbench'; -import type { Logger } from 'monaco-languageclient/tools'; -import { FakeWorker as Worker } from './fakeWorker.js'; -import { setUnexpectedErrorHandler } from '@codingame/monaco-vscode-api/monaco'; -import { useWorkerFactory } from '../workerFactory.js'; - -export interface MonacoEnvironmentEnhanced extends monaco.Environment { - vscodeInitialising?: boolean; - vscodeApiInitialised?: boolean; -} - -export interface UserConfiguration { - json?: string; -} -export interface ViewsConfig { - viewServiceType: 'EditorService' | 'ViewsService' | 'WorkspaceService'; - openEditorFunc?: OpenEditor; - htmlAugmentationInstructions?: (htmlContainer: HTMLElement | null | undefined) => void; - viewsInitFunc?: () => Promise; -} - -export interface VscodeApiConfig { - vscodeApiInitPerformExternally?: boolean; - loadThemes?: boolean; - serviceOverrides?: monaco.editor.IEditorOverrideServices; - enableExtHostWorker?: boolean; - workspaceConfig?: IWorkbenchConstructionOptions; - userConfiguration?: UserConfiguration; - viewsConfig?: ViewsConfig, - envOptions?: EnvironmentOverride; -} - -export interface InitServicesInstructions { - htmlContainer?: HTMLElement; - caller?: string; - performServiceConsistencyChecks?: () => boolean; - logger?: Logger; - monacoWorkerFactory?: (logger?: Logger) => void; -} - -export const getEnhancedMonacoEnvironment = (): MonacoEnvironmentEnhanced => { - const monWin = (self as Window); - if (monWin.MonacoEnvironment === undefined) { - monWin.MonacoEnvironment = {}; - } - const envEnhanced = monWin.MonacoEnvironment as MonacoEnvironmentEnhanced; - if (envEnhanced.vscodeApiInitialised === undefined) { - envEnhanced.vscodeApiInitialised = false; - } - if (envEnhanced.vscodeInitialising === undefined) { - envEnhanced.vscodeInitialising = false; - } - - return envEnhanced; -}; - -export const getMonacoEnvironmentEnhanced = () => { - const monWin = (self as Window); - return monWin.MonacoEnvironment as MonacoEnvironmentEnhanced; -}; - -export const supplyRequiredServices = async () => { - return { - ...getConfigurationServiceOverride(), - ...getLanguagesServiceOverride(), - ...getLogServiceOverride(), - ...getModelServiceOverride() - }; -}; - -export const reportServiceLoading = (services: monaco.editor.IEditorOverrideServices, logger?: Logger) => { - for (const serviceName of Object.keys(services)) { - logger?.debug(`Loading service: ${serviceName}`); - } -}; - -export const mergeServices = (overrideServices: monaco.editor.IEditorOverrideServices, services?: monaco.editor.IEditorOverrideServices) => { - if (services !== undefined) { - for (const [name, service] of Object.entries(services)) { - overrideServices[name] = service; - } - } -}; - -export const initServices = async (vscodeApiConfig: VscodeApiConfig, instructions?: InitServicesInstructions) => { - const envEnhanced = getEnhancedMonacoEnvironment(); - - if (typeof instructions?.monacoWorkerFactory === 'function') { - instructions.monacoWorkerFactory(instructions.logger); - } else { - useWorkerFactory({ - logger: instructions?.logger - }); - } - if (!(envEnhanced.vscodeInitialising ?? false)) { - - if (envEnhanced.vscodeApiInitialised ?? false) { - instructions?.logger?.debug('Initialization of vscode services can only performed once!'); - } else { - envEnhanced.vscodeInitialising = true; - instructions?.logger?.debug(`Initializing vscode services. Caller: ${instructions.caller ?? 'unknown'}`); - - if (vscodeApiConfig.userConfiguration?.json !== undefined) { - await initUserConfiguration(vscodeApiConfig.userConfiguration.json); - } - await importAllServices(vscodeApiConfig, instructions); - - vscodeApiConfig.viewsConfig?.htmlAugmentationInstructions?.(instructions?.htmlContainer); - await vscodeApiConfig.viewsConfig?.viewsInitFunc?.(); - instructions?.logger?.debug('Initialization of vscode services completed successfully.'); - - envEnhanced.vscodeApiInitialised = true; - envEnhanced.vscodeInitialising = false; - } - } - - return envEnhanced.vscodeApiInitialised; -}; - -/** - * monaco-vscode-api automatically loads the following services: - * - layout - * - environment - * - extension - * - files - * - quickAccess - * monaco-languageclient always adds the following services: - * - languages - * - log - * - model - */ -export const importAllServices = async (vscodeApiConfig: VscodeApiConfig, instructions?: InitServicesInstructions) => { - const services = await supplyRequiredServices(); - - mergeServices(services, vscodeApiConfig.serviceOverrides); - await configureExtHostWorker(vscodeApiConfig.enableExtHostWorker === true, services); - - reportServiceLoading(services, instructions?.logger); - - if (instructions?.performServiceConsistencyChecks === undefined || - (typeof instructions.performServiceConsistencyChecks === 'function' && instructions.performServiceConsistencyChecks())) { - if (vscodeApiConfig.viewsConfig?.viewServiceType === 'ViewsService' || vscodeApiConfig.viewsConfig?.viewServiceType === 'WorkspaceService') { - await initialize(services, instructions?.htmlContainer, vscodeApiConfig.workspaceConfig, vscodeApiConfig.envOptions); - } else { - await initialize(services, undefined, vscodeApiConfig.workspaceConfig, vscodeApiConfig.envOptions); - } - } - - setUnexpectedErrorHandler((e) => { - instructions?.logger?.createErrorAndLog('Unexpected error', e); - }); -}; - -/** - * Enable ext host to run in a worker - */ -export const configureExtHostWorker = async (enableExtHostWorker: boolean, userServices: monaco.editor.IEditorOverrideServices) => { - if (enableExtHostWorker) { - const fakeWorker = new Worker(new URL('@codingame/monaco-vscode-api/workers/extensionHost.worker', import.meta.url), { type: 'module' }); - const workerConfig: WorkerConfig = { - url: fakeWorker.url.toString(), - options: fakeWorker.options - }; - - mergeServices(userServices, { - ...getExtensionServiceOverride(workerConfig), - }); - } -}; - -export const createDefaultLocaleConfiguration = (): LocalizationOptions => { - return { - async clearLocale() { - const url = new URL(window.location.href); - url.searchParams.delete('locale'); - window.history.pushState(null, '', url.toString()); - }, - async setLocale(id: string) { - const url = new URL(window.location.href); - url.searchParams.set('locale', id); - window.history.pushState(null, '', url.toString()); - }, - availableLanguages: [{ - locale: 'en', - languageName: 'English' - }, { - locale: 'cs', - languageName: 'Czech' - }, { - locale: 'de', - languageName: 'German' - }, { - locale: 'es', - languageName: 'Spanish' - }, { - locale: 'fr', - languageName: 'French' - }, { - locale: 'it', - languageName: 'Italian' - }, { - locale: 'ja', - languageName: 'Japanese' - }, { - locale: 'ko', - languageName: 'Korean' - }, { - locale: 'pl', - languageName: 'Polish' - }, { - locale: 'pt-br', - languageName: 'Portuguese (Brazil)' - }, { - locale: 'qps-ploc', - languageName: 'Pseudo Language' - }, { - locale: 'ru', - languageName: 'Russian' - }, { - locale: 'tr', - languageName: 'Turkish' - }, { - locale: 'zh-hans', - languageName: 'Chinese (Simplified)' - }, { - locale: 'zh-hant', - languageName: 'Chinese (Traditional)' - }, { - locale: 'en', - languageName: 'English' - }] - }; -}; diff --git a/packages/client/src/vscode/utils.ts b/packages/client/src/vscode/utils.ts new file mode 100644 index 000000000..1b7a9a190 --- /dev/null +++ b/packages/client/src/vscode/utils.ts @@ -0,0 +1,67 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as monaco from '@codingame/monaco-vscode-editor-api'; +import type { OpenEditor } from '@codingame/monaco-vscode-editor-service-override'; +import type { WorkerConfig } from '@codingame/monaco-vscode-extensions-service-override'; +import getExtensionServiceOverride from '@codingame/monaco-vscode-extensions-service-override'; +import type { Logger } from 'monaco-languageclient/common'; +import { FakeWorker as Worker } from 'monaco-languageclient/workerFactory'; +import type { MonacoEnvironmentEnhanced } from './config.js'; + +export const getEnhancedMonacoEnvironment = (): MonacoEnvironmentEnhanced => { + const monWin = (self as Window); + if (monWin.MonacoEnvironment === undefined) { + monWin.MonacoEnvironment = {}; + } + const envEnhanced = monWin.MonacoEnvironment as MonacoEnvironmentEnhanced; + if (envEnhanced.vscodeApiInitialising === undefined) { + envEnhanced.vscodeApiInitialising = false; + } + if (envEnhanced.vscodeApiInitialised === undefined) { + envEnhanced.vscodeApiInitialised = false; + } + if (envEnhanced.viewServiceType === undefined) { + envEnhanced.viewServiceType = 'EditorService'; + } + + return envEnhanced; +}; + +export const reportServiceLoading = (services: monaco.editor.IEditorOverrideServices, logger?: Logger) => { + for (const serviceName of Object.keys(services)) { + logger?.debug(`Loading service: ${serviceName}`); + } +}; + +export const mergeServices = (overrideServices: monaco.editor.IEditorOverrideServices, services?: monaco.editor.IEditorOverrideServices) => { + if (services !== undefined) { + for (const [name, service] of Object.entries(services)) { + overrideServices[name] = service; + } + } +}; + +/** + * Enable ext host to run in a worker + */ +export const configureExtHostWorker = async (enableExtHostWorker: boolean, userServices: monaco.editor.IEditorOverrideServices) => { + if (enableExtHostWorker) { + const fakeWorker = new Worker(new URL('@codingame/monaco-vscode-api/workers/extensionHost.worker', import.meta.url), { type: 'module' }); + const workerConfig: WorkerConfig = { + url: fakeWorker.url.toString(), + options: fakeWorker.options + }; + + mergeServices(userServices, { + ...getExtensionServiceOverride(workerConfig), + }); + } +}; + +export const useOpenEditorStub: OpenEditor = async (modelRef, options, sideBySide) => { + console.log('Received open editor call with parameters: ', modelRef, options, sideBySide); + return undefined; +}; diff --git a/packages/wrapper/src/vscode/viewsService.ts b/packages/client/src/vscode/viewsService.ts similarity index 100% rename from packages/wrapper/src/vscode/viewsService.ts rename to packages/client/src/vscode/viewsService.ts diff --git a/packages/client/src/vscode/fakeWorker.ts b/packages/client/src/worker/fakeWorker.ts similarity index 100% rename from packages/client/src/vscode/fakeWorker.ts rename to packages/client/src/worker/fakeWorker.ts diff --git a/packages/client/src/worker/index.ts b/packages/client/src/worker/index.ts new file mode 100644 index 000000000..065acbff2 --- /dev/null +++ b/packages/client/src/worker/index.ts @@ -0,0 +1,8 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +export * from './fakeWorker.js'; +export * from './workerFactory.js'; +export * from './workerLoaders.js'; diff --git a/packages/client/src/workerFactory.ts b/packages/client/src/worker/workerFactory.ts similarity index 93% rename from packages/client/src/workerFactory.ts rename to packages/client/src/worker/workerFactory.ts index c94093a1c..22adbe0bd 100644 --- a/packages/client/src/workerFactory.ts +++ b/packages/client/src/worker/workerFactory.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import { getEnhancedMonacoEnvironment } from 'monaco-languageclient/vscode/services'; -import type { Logger } from 'monaco-languageclient/tools'; +import { getEnhancedMonacoEnvironment } from 'monaco-languageclient/vscodeApiWrapper'; +import type { Logger } from 'monaco-languageclient/common'; export type WorkerLoader = (() => Worker) | undefined; diff --git a/packages/wrapper/src/workers/workerLoaders.ts b/packages/client/src/worker/workerLoaders.ts similarity index 96% rename from packages/wrapper/src/workers/workerLoaders.ts rename to packages/client/src/worker/workerLoaders.ts index 0b5a837b2..234901c4b 100644 --- a/packages/wrapper/src/workers/workerLoaders.ts +++ b/packages/client/src/worker/workerLoaders.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import type { Logger } from 'monaco-languageclient/tools'; +import type { Logger } from 'monaco-languageclient/common'; import { useWorkerFactory, type WorkerLoader } from 'monaco-languageclient/workerFactory'; export const defineDefaultWorkerLoaders: () => Record = () => { diff --git a/packages/wrapper/src/vscode/index.ts b/packages/client/src/wrapper/index.ts similarity index 77% rename from packages/wrapper/src/vscode/index.ts rename to packages/client/src/wrapper/index.ts index fadac77cb..e23b3116f 100644 --- a/packages/wrapper/src/vscode/index.ts +++ b/packages/client/src/wrapper/index.ts @@ -3,5 +3,6 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -export * from './services.js'; -export * from './viewsService.js'; +export * from './lcconfig.js'; +export * from './lcmanager.js'; +export * from './lcwrapper.js'; diff --git a/packages/client/src/wrapper/lcconfig.ts b/packages/client/src/wrapper/lcconfig.ts new file mode 100644 index 000000000..b41480869 --- /dev/null +++ b/packages/client/src/wrapper/lcconfig.ts @@ -0,0 +1,32 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import type { LanguageClientOptions, MessageTransports } from 'vscode-languageclient/browser.js'; +import { type ConnectionConfigOptions } from 'monaco-languageclient/common'; + +export interface ConnectionConfig { + options: ConnectionConfigOptions; + messageTransports?: MessageTransports; +} + +export interface LanguageClientConfig { + name?: string; + connection: ConnectionConfig; + clientOptions: LanguageClientOptions; + restartOptions?: LanguageClientRestartOptions; + disposeWorker?: boolean; +} + +export interface LanguageClientRestartOptions { + retries: number; + timeout: number; + keepWorker?: boolean; +} + +export interface LanguageClientConfigs { + configs: Record + overwriteExisting?: boolean; + enforceDispose?: boolean; +} diff --git a/packages/client/src/wrapper/lcmanager.ts b/packages/client/src/wrapper/lcmanager.ts new file mode 100644 index 000000000..346b1e341 --- /dev/null +++ b/packages/client/src/wrapper/lcmanager.ts @@ -0,0 +1,89 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import type { Logger } from 'monaco-languageclient/common'; +import type { LanguageClientConfigs } from './lcconfig.js'; +import { LanguageClientWrapper } from './lcwrapper.js'; + +export class LanguageClientsManager { + + private logger?: Logger; + private languageClientConfigs?: LanguageClientConfigs; + private languageClientWrappers: Map = new Map(); + + constructor(logger?: Logger) { + this.logger = logger; + } + + haveLanguageClients(): boolean { + return this.languageClientWrappers.size > 0; + } + + getLanguageClientWrapper(languageId: string): LanguageClientWrapper | undefined { + return this.languageClientWrappers.get(languageId); + } + + getLanguageClient(languageId: string) { + return this.languageClientWrappers.get(languageId)?.getLanguageClient(); + } + + getWorker(languageId: string): Worker | undefined { + return this.languageClientWrappers.get(languageId)?.getWorker(); + } + + async setConfigs(languageClientConfigs: LanguageClientConfigs): Promise { + this.languageClientConfigs = languageClientConfigs; + + const lccEntries = Object.entries(this.languageClientConfigs.configs); + if (lccEntries.length > 0) { + for (const [languageId, lcc] of lccEntries) { + const current = this.languageClientWrappers.get(languageId); + const lcw = new LanguageClientWrapper(lcc, this.logger); + + if (current !== undefined) { + if (languageClientConfigs.overwriteExisting === true) { + if (languageClientConfigs.enforceDispose === true) { + await current.dispose(); + } + } else { + throw new Error(`A languageclient config with id "${languageId}" already exists and you confiured to not override.`); + } + } + this.languageClientWrappers.set(languageId, lcw); + } + } + } + + async start(): Promise { + const allPromises: Array> = []; + for (const lcw of this.languageClientWrappers.values()) { + if (!lcw.isStarted()) { + allPromises.push(lcw.start()); + } + } + return Promise.all(allPromises); + } + + isStarted(): boolean { + for (const lcw of this.languageClientWrappers.values()) { + // as soon as one is not started return + if (!lcw.isStarted()) { + return false; + } + } + return true; + } + + async dispose(): Promise { + const allPromises: Array> = []; + for (const lcw of this.languageClientWrappers.values()) { + if (lcw.haveLanguageClient()) { + allPromises.push(lcw.dispose()); + } + } + await Promise.all(allPromises); + this.languageClientWrappers.clear(); + } +} diff --git a/packages/client/src/languageClientWrapper.ts b/packages/client/src/wrapper/lcwrapper.ts similarity index 88% rename from packages/client/src/languageClientWrapper.ts rename to packages/client/src/wrapper/lcwrapper.ts index 25339b4b6..2b13fd02c 100644 --- a/packages/client/src/languageClientWrapper.ts +++ b/packages/client/src/wrapper/lcwrapper.ts @@ -4,28 +4,11 @@ * ------------------------------------------------------------------------------------------ */ import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver-protocol/browser.js'; -import { CloseAction, ErrorAction, type LanguageClientOptions, MessageTransports, State } from 'vscode-languageclient/browser.js'; -import { type ConnectionConfigOptions, MonacoLanguageClient, type WorkerConfigOptionsDirect, type WorkerConfigOptionsParams } from 'monaco-languageclient'; -import { createUrl, type Logger } from 'monaco-languageclient/tools'; +import { CloseAction, ErrorAction, MessageTransports, State } from 'vscode-languageclient/browser.js'; +import { createUrl, type Logger, type WorkerConfigOptionsDirect, type WorkerConfigOptionsParams } from 'monaco-languageclient/common'; import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'; - -export interface ConnectionConfig { - options: ConnectionConfigOptions; - messageTransports?: MessageTransports; -} - -export interface LanguageClientConfig { - name?: string; - connection: ConnectionConfig; - clientOptions: LanguageClientOptions; - restartOptions?: LanguageClientRestartOptions; -} - -export interface LanguageClientRestartOptions { - retries: number; - timeout: number; - keepWorker?: boolean; -} +import { MonacoLanguageClient } from 'monaco-languageclient'; +import type { LanguageClientConfig, LanguageClientRestartOptions } from './lcconfig.js'; export interface LanguageClientError { message: string; @@ -41,13 +24,10 @@ export class LanguageClientWrapper { private name?: string; private logger: Logger | undefined; - constructor(config: { - languageClientConfig: LanguageClientConfig, - logger?: Logger - }) { - this.languageClientConfig = config.languageClientConfig; + constructor(config: LanguageClientConfig, logger?: Logger) { + this.languageClientConfig = config; this.name = this.languageClientConfig.name ?? 'unnamed'; - this.logger = config.logger; + this.logger = logger; } haveLanguageClient(): boolean { @@ -92,8 +72,8 @@ export class LanguageClientWrapper { * @param updatedWorker Set a new worker here that should be used. keepWorker has no effect then, as we want to dispose of the prior workers * @param disposeWorker Set to false if worker should not be disposed */ - async restartLanguageClient(updatedWorker?: Worker, disposeWorker: boolean = true): Promise { - await this.disposeLanguageClient(disposeWorker); + async restart(updatedWorker?: Worker, forceWorkerDispose?: boolean): Promise { + await this.dispose(forceWorkerDispose); this.worker = updatedWorker; this.logger?.info('Re-Starting monaco-languageclient'); @@ -248,14 +228,14 @@ export class LanguageClientWrapper { readerOnError.dispose(); readerOnClose.dispose(); - await this.restartLanguageClient(this.worker, restartOptions.keepWorker); + await this.restart(this.worker, restartOptions.keepWorker); } finally { retry++; if (retry > (restartOptions.retries) && !this.isStarted()) { this.logger?.info('Disabling Language Client. Failed to start clangd after 5 retries'); } else { setTimeout(async () => { - await this.restartLanguageClient(this.worker, restartOptions.keepWorker); + await this.restart(this.worker, restartOptions.keepWorker); }, restartOptions.timeout); } } @@ -269,7 +249,7 @@ export class LanguageClientWrapper { this.worker = undefined; } - async disposeLanguageClient(disposeWorker: boolean): Promise { + async dispose(forceWorkerDispose?: boolean): Promise { try { if (this.isStarted()) { await this.languageClient?.dispose(); @@ -284,7 +264,7 @@ export class LanguageClientWrapper { return Promise.reject(languageClientError); } finally { // always terminate the worker if desired - if (disposeWorker) { + if (this.languageClientConfig.disposeWorker === true || forceWorkerDispose === true) { this.disposeWorker(); } } diff --git a/packages/client/test/tools/index.test.ts b/packages/client/test/common/logging.test.ts similarity index 95% rename from packages/client/test/tools/index.test.ts rename to packages/client/test/common/logging.test.ts index 873bf25ff..8e537e39f 100644 --- a/packages/client/test/tools/index.test.ts +++ b/packages/client/test/common/logging.test.ts @@ -4,7 +4,7 @@ * ------------------------------------------------------------------------------------------ */ import { describe, expect, test } from 'vitest'; -import { ConsoleLogger } from 'monaco-languageclient/tools'; +import { ConsoleLogger } from 'monaco-languageclient/common'; import { LogLevel } from '@codingame/monaco-vscode-api'; describe('Logger', () => { diff --git a/packages/client/test/tools/utils.test.ts b/packages/client/test/common/utils.test.ts similarity index 96% rename from packages/client/test/tools/utils.test.ts rename to packages/client/test/common/utils.test.ts index 3906e26eb..f66710112 100644 --- a/packages/client/test/tools/utils.test.ts +++ b/packages/client/test/common/utils.test.ts @@ -4,8 +4,7 @@ * ------------------------------------------------------------------------------------------ */ import { describe, expect, test } from 'vitest'; -import type { WebSocketConfigOptionsParams, WebSocketConfigOptionsUrl } from 'monaco-languageclient'; -import { createUrl } from 'monaco-languageclient/tools'; +import { createUrl, type WebSocketConfigOptionsParams, type WebSocketConfigOptionsUrl } from 'monaco-languageclient/common'; describe('createUrl', () => { diff --git a/packages/wrapper/test/utils.test.ts b/packages/client/test/editorApp/config.test.ts similarity index 96% rename from packages/wrapper/test/utils.test.ts rename to packages/client/test/editorApp/config.test.ts index 3906e26eb..f66710112 100644 --- a/packages/wrapper/test/utils.test.ts +++ b/packages/client/test/editorApp/config.test.ts @@ -4,8 +4,7 @@ * ------------------------------------------------------------------------------------------ */ import { describe, expect, test } from 'vitest'; -import type { WebSocketConfigOptionsParams, WebSocketConfigOptionsUrl } from 'monaco-languageclient'; -import { createUrl } from 'monaco-languageclient/tools'; +import { createUrl, type WebSocketConfigOptionsParams, type WebSocketConfigOptionsUrl } from 'monaco-languageclient/common'; describe('createUrl', () => { diff --git a/packages/client/test/editorApp/editorApp-classic.test.ts b/packages/client/test/editorApp/editorApp-classic.test.ts new file mode 100644 index 000000000..2f758bb66 --- /dev/null +++ b/packages/client/test/editorApp/editorApp-classic.test.ts @@ -0,0 +1,348 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +/* eslint-disable dot-notation */ + +import * as monaco from '@codingame/monaco-vscode-editor-api'; +import { EditorApp, type TextContents } from 'monaco-languageclient/editorApp'; +import { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; +import { beforeAll, describe, expect, test } from 'vitest'; +import { createDefaultMonacoVscodeApiConfig, createEditorAppConfigClassic, createMonacoEditorDiv } from '../support/helper.js'; + +describe('Test Test EditorApp (classic)', () => { + + const htmlContainer = createMonacoEditorDiv(); + + beforeAll(async () => { + const apiConfig = createDefaultMonacoVscodeApiConfig(htmlContainer); + const apiWrapper = new MonacoVscodeApiWrapper(apiConfig); + await apiWrapper.init(); + }); + + test('classic type: empty EditorAppConfigClassic', () => { + const editorAppConfig = createEditorAppConfigClassic({}); + expect(editorAppConfig.$type).toBe('classic'); + }); + + test('config defaults', () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + editorAppConfig.id = 'test-config-defaults'; + + const editorApp = new EditorApp(editorAppConfig); + expect(editorApp.getConfig().codeResources?.modified?.text).toEqual('const text = "Hello World!";'); + expect(editorApp.getConfig().codeResources?.original).toBeUndefined(); + expect(editorApp.getConfig().useDiffEditor ?? false).toBeFalsy(); + expect(editorApp.getConfig().readOnly).toBeFalsy(); + expect(editorApp.getConfig().domReadOnly).toBeFalsy(); + }); + + test('editorOptions: semanticHighlighting=false', () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + editorAppConfig!.editorOptions!['semanticHighlighting.enabled'] = false; + editorAppConfig.id = 'test-semanticHighlighting-false'; + + const editorApp = new EditorApp(editorAppConfig); + expect(editorApp.getConfig().editorOptions?.['semanticHighlighting.enabled']).toBeFalsy(); + }); + + test('editorOptions: semanticHighlighting="configuredByTheme"', () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + editorAppConfig!.editorOptions!['semanticHighlighting.enabled'] = 'configuredByTheme'; + editorAppConfig.id = 'test-semanticHighlighting-theme'; + + const editorApp = new EditorApp(editorAppConfig); + expect(editorApp.getConfig().editorOptions?.['semanticHighlighting.enabled']).toEqual('configuredByTheme'); + }); + + test('editorOptions: semanticHighlighting=true', () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + editorAppConfig!.editorOptions!['semanticHighlighting.enabled'] = true; + editorAppConfig.id = 'test-semanticHighlighting-true'; + + const editorApp = new EditorApp(editorAppConfig); + expect(editorAppConfig.$type).toEqual('classic'); + expect(editorApp.getConfig().editorOptions?.['semanticHighlighting.enabled']).toBeTruthy(); + }); + + test('Check default values', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + + const editorApp = new EditorApp(editorAppConfig); + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + expect(editorApp).toBeDefined(); + + const appConfig = editorApp!.getConfig(); + expect(appConfig.overrideAutomaticLayout).toBeTruthy(); + }); + + test('Code resources main', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + const modelRefs = editorApp['modelRefs']; + expect(modelRefs?.modified).toBeDefined(); + expect(modelRefs?.original).toBeUndefined(); + editorApp.dispose(); + }); + + test('Call start twice without prior disposal', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + + const editorApp = new EditorApp(editorAppConfig); + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + await expect(async () => { + await editorApp.start(htmlContainer); + }).rejects.toThrowError('You called start without properly disposing the EditorApp.'); + }); + + test('Call start twice with prior disposal', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + await editorApp.dispose(); + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + }); + + test('Code resources original (regular editor)', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + const codeResources = editorAppConfig.codeResources ?? {}; + codeResources.modified = undefined; + codeResources.original = { + text: 'original', + uri: '/workspace/test-code-resources-original-regular-editor.js', + }; + + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + const modelRefs = editorApp['modelRefs']; + expect(modelRefs?.modified).toBeDefined(); + expect(modelRefs?.original).toBeUndefined(); + editorApp.dispose(); + }); + + test('Code resources original (diff editor)', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + editorAppConfig.useDiffEditor = true; + const codeResources = editorAppConfig.codeResources ?? {}; + codeResources.modified = undefined; + codeResources.original = { + text: 'original', + uri: '/workspace/test-code-resources-original-diff-editor.js', + }; + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + const modelRefs = editorApp['modelRefs']; + expect(modelRefs?.modified).toBeDefined(); + expect(modelRefs?.original).toBeDefined(); + editorApp.dispose(); + }); + + test('Code resources main and original', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'modified', + uri: `/workspace/${expect.getState().testPath}_modified.js` + } + }); + const codeResources = editorAppConfig.codeResources!; + codeResources.original = { + text: 'original', + uri: `/workspace/${expect.getState().testPath}_original.js` + }; + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + const modelRefs = editorApp['modelRefs']; + expect(modelRefs?.modified).toBeDefined(); + // if no diff editor is used, the original modelRef is undefined + expect(modelRefs?.original).toBeUndefined(); + + const name = modelRefs?.modified.object.name; + const nameOriginal = modelRefs?.original?.object.name; + expect(name).toBeDefined(); + expect(nameOriginal).toBeUndefined(); + expect(name).not.toEqual(nameOriginal); + + editorApp.dispose(); + }); + + test('Code resources empty', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + editorAppConfig.codeResources = {}; + const editorApp = new EditorApp(editorAppConfig); + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + const modelRefs = editorApp['modelRefs']; + // default modelRef is created with regular editor even if no codeResources are given + expect(modelRefs?.modified).toBeDefined(); + expect(modelRefs?.original).toBeUndefined(); + }); + + test('Code resources model direct', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + editorAppConfig.codeResources = {}; + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + editorApp.setModelRefDisposeTimeout(1000); + + editorApp.updateCodeResources({ + modified: { + text: 'const text = "Hello World!";', + uri: '/workspace/statemachineUri.statemachine' + } + }); + + const modelRefs = editorApp['modelRefs']; + expect(modelRefs?.modified).toBeDefined(); + expect(modelRefs?.original).toBeUndefined(); + }); + + test('Early code resources update on editorApp are ok', async () => { + const editorAppConfig = createEditorAppConfigClassic({}); + const editorApp = new EditorApp(editorAppConfig); + + editorApp.setModelRefDisposeTimeout(1000); + + expect(editorApp.getEditor()).toBeUndefined(); + expect(editorApp.getDiffEditor()).toBeUndefined(); + + const modelRefsBefore = editorApp['modelRefs']; + expect(modelRefsBefore?.modified).toBeUndefined(); + expect(modelRefsBefore?.original).toBeUndefined(); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + editorApp.registerOnTextChangedCallback((textChanges: TextContents) => { + console.log(textChanges); + expect(textChanges.modified).toEqual('// comment'); + }); + + await expect(await editorApp.updateCodeResources({ + modified: { + text: '// comment', + uri: '/workspace/test.statemachine', + } + })).toBeUndefined(); + + const modelRefsAfter = editorApp['modelRefs']; + expect(modelRefsAfter?.modified).toBeDefined(); + expect(modelRefsAfter?.original).toBeUndefined(); + }); + + test('Check current model is globally removed after dispose', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "test";', + uri: `/workspace/${expect.getState().testPath}_single-model.js` + } + }); + const editorApp = new EditorApp(editorAppConfig); + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + const currentModel = editorApp.getEditor()?.getModel(); + expect(monaco.editor.getModels().includes(currentModel!)).toBeTruthy(); + editorApp.getEditor()?.getModel()!.dispose(); + expect(monaco.editor.getModels().includes(currentModel!)).toBeFalsy(); + }); + + test('Check current model is globally removed after dispose (second model)', async () => { + const editorAppConfig = createEditorAppConfigClassic({ + modified: { + text: 'const text = "test";', + uri: `/workspace/${expect.getState().testPath}_second-model.js` + } + }); + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + const currentModel = editorApp.getEditor()?.getModel(); + expect(monaco.editor.getModels().includes(currentModel!)).toBeTruthy(); + + editorApp.setModelRefDisposeTimeout(1000); + + await editorApp.updateCodeResources({ + modified: { + text: 'const text = "test 2";', + uri: `/workspace/${expect.getState().testPath}_second-model_2.js` + } + }); + const currentModelMod = editorApp.getEditor()?.getModel(); + expect(monaco.editor.getModels().includes(currentModelMod!)).toBeTruthy(); + expect(monaco.editor.getModels().includes(currentModel!)).toBeFalsy(); + }); + +}); diff --git a/packages/client/test/editorApp/editorApp.test.ts b/packages/client/test/editorApp/editorApp.test.ts new file mode 100644 index 000000000..b80506570 --- /dev/null +++ b/packages/client/test/editorApp/editorApp.test.ts @@ -0,0 +1,228 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +/* eslint-disable dot-notation */ + +import { verifyUrlOrCreateDataUrl } from 'monaco-languageclient/common'; +import { EditorApp, type TextContents } from 'monaco-languageclient/editorApp'; +import { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; +import { beforeAll, describe, expect, test, vi } from 'vitest'; +import { createDefaultMonacoVscodeApiConfig, createEditorAppConfigClassicExtended, createMonacoEditorDiv } from '../support/helper.js'; + +describe('Test EditorApp', () => { + + const htmlContainer = createMonacoEditorDiv(); + + beforeAll(async () => { + const apiConfig = createDefaultMonacoVscodeApiConfig(htmlContainer); + const apiWrapper = new MonacoVscodeApiWrapper(apiConfig); + await apiWrapper.init(); + }); + + test('extended type: empty EditorAppConfigExtended', () => { + const editorAppConfig = createEditorAppConfigClassicExtended({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + expect(editorAppConfig.$type).toBe('extended'); + }); + + test('verifyUrlorCreateDataUrl: url', () => { + const url = new URL('./editorAppExtended.test.ts', import.meta.url); + expect(verifyUrlOrCreateDataUrl(url)).toBe(url.href); + }); + + test('verifyUrlorCreateDataUrl: url', async () => { + const url = new URL('../../../node_modules/langium-statemachine-dsl/syntaxes/statemachine.tmLanguage.json', window.location.href); + const text = await (await fetch(url)).text(); + const bytes = new TextEncoder().encode(text); + const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join(''); + const base64 = btoa(binString); + expect(verifyUrlOrCreateDataUrl(text)).toBe(`data:text/plain;base64,${base64}`); + }); + + test('verifyUrlorCreateDataUrl: url', () => { + const text = '✓✓'; + const bytes = new TextEncoder().encode(text); + const binString = Array.from(bytes, (b) => String.fromCodePoint(b)).join(''); + const base64 = btoa(binString); + expect(verifyUrlOrCreateDataUrl(text)).toBe(`data:text/plain;base64,${base64}`); + }); + + test('config defaults', () => { + const editorAppConfig = createEditorAppConfigClassicExtended({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + editorAppConfig.id = 'test-config-defaults'; + + const editorApp = new EditorApp(editorAppConfig); + expect(editorApp.getConfig().codeResources?.modified?.text).toEqual('const text = "Hello World!";'); + expect(editorApp.getConfig().codeResources?.original).toBeUndefined(); + expect(editorApp.getConfig().useDiffEditor ?? false).toBeFalsy(); + expect(editorApp.getConfig().readOnly).toBeFalsy(); + expect(editorApp.getConfig().domReadOnly).toBeFalsy(); + }); + + test('New editorApp has undefined editor', () => { + const editorAppConfig = createEditorAppConfigClassicExtended({}); + const editorApp = new EditorApp(editorAppConfig); + expect(editorApp.getEditor()).toBeUndefined(); + }); + + test('New editorApp has undefined diff editor', () => { + const editorAppConfig = createEditorAppConfigClassicExtended({}); + const editorApp = new EditorApp(editorAppConfig); + expect(editorApp.getDiffEditor()).toBeUndefined(); + }); + + test('Start EditorApp', async () => { + const editorAppConfig = createEditorAppConfigClassicExtended({}); + const editorApp = new EditorApp(editorAppConfig); + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + }); + + test('Update code resources after start (same file)', async () => { + const editorAppConfig = createEditorAppConfigClassicExtended({ + modified: { + text: 'const text = "Hello World";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + expect(editorApp.isStarted()).toBeTruthy(); + + editorApp.registerOnTextChangedCallback((textChanges: TextContents) => { + console.log(`text: ${textChanges.modified}\ntextOriginal: ${textChanges.original}`); + }); + + editorApp.setModelRefDisposeTimeout(1000); + + await editorApp.updateCodeResources({ + modified: { + text: 'const text = "Goodbye World";', + uri: `/workspace/${expect.getState().testPath}_2.js` + } + }); + + const textModels = editorApp.getTextModels(); + expect(textModels.modified?.getValue()).toEqual('const text = "Goodbye World";'); + + expect(editorApp.getEditor()?.getModel()?.getValue()).toEqual('const text = "Goodbye World";'); + }); + + test('Update code resources after start (different file)', async () => { + const editorAppConfig = createEditorAppConfigClassicExtended({ + modified: { + text: 'const text = "Hello World";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + expect(editorApp.isStarted()).toBeTruthy(); + + editorApp.setModelRefDisposeTimeout(1000); + + await expect(await editorApp.updateCodeResources({ + modified: { + text: 'const text = "Goodbye World";', + uri: `/workspace/${expect.getState().testPath}_2.js`, + } + })).toBeUndefined(); + + const textModels = editorApp.getTextModels(); + expect(textModels.modified?.getValue()).toEqual('const text = "Goodbye World";'); + + expect(editorApp.getEditor()?.getModel()?.getValue()).toEqual('const text = "Goodbye World";'); + }); + + test('Verify registerTextChangeCallback', async () => { + const editorAppConfig = createEditorAppConfigClassicExtended({}); + + const onTextChanged = (textChanges: TextContents) => { + console.log(`text: ${textChanges.modified}\ntextOriginal: ${textChanges.original}`); + }; + const editorApp = new EditorApp(editorAppConfig); + + let onTextChangedDiposeable = editorApp['textChangedDiposeables'].modified; + expect(onTextChangedDiposeable).toBeUndefined(); + + editorApp.registerOnTextChangedCallback(onTextChanged); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const spyAnnounceModelUpdate = vi.spyOn(editorApp as any, 'announceModelUpdate'); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + + onTextChangedDiposeable = editorApp['textChangedDiposeables'].modified; + expect(onTextChangedDiposeable).toBeDefined(); + + const spyOnTextChangedDiposeable = vi.spyOn(onTextChangedDiposeable, 'dispose'); + + // because there are default models now, the first update of models will not lead to onTextChanged dispoe + expect(spyAnnounceModelUpdate).toHaveBeenCalledTimes(1); + expect(spyOnTextChangedDiposeable).toHaveBeenCalledTimes(0); + editorApp.setModelRefDisposeTimeout(1000); + + await editorApp.updateCodeResources({ + modified: { + text: 'const text = "Hello World!";', + uri: `/workspace/${expect.getState().testPath}_2.statemachine`, + } + }); + + expect(spyAnnounceModelUpdate).toHaveBeenCalledTimes(2); + expect(spyOnTextChangedDiposeable).toHaveBeenCalledTimes(1); + }); + + test('Test editorApp init/start/dispose phase promises', async () => { + let editorAppConfig = createEditorAppConfigClassicExtended({ + modified: { + text: 'const text = "Hello World";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + let editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + expect(editorApp.isStarting()).toBeFalsy(); + expect(editorApp.isDisposing()).toBeFalsy(); + + await expect(await editorApp.dispose()).toBeUndefined(); + + expect(editorApp.isStarting()).toBeFalsy(); + expect(editorApp.isDisposing()).toBeFalsy(); + + editorAppConfig = createEditorAppConfigClassicExtended({ + modified: { + text: 'const text = "Hello World";', + uri: `/workspace/${expect.getState().testPath}_2.js` + } + }); + editorApp = new EditorApp(editorAppConfig); + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + }); + + test('Test html parameter with start', async () => { + const editorAppConfig = createEditorAppConfigClassicExtended({ + modified: { + text: 'const text = "Hello World";', + uri: `/workspace/${expect.getState().testPath}.js` + } + }); + const editorApp = new EditorApp(editorAppConfig); + + await expect(await editorApp.start(htmlContainer)).toBeUndefined(); + }); + +}); diff --git a/packages/wrapper/test/support/helper-classic.ts b/packages/client/test/support/helper-classic.ts similarity index 83% rename from packages/wrapper/test/support/helper-classic.ts rename to packages/client/test/support/helper-classic.ts index 46b4fd7b5..e779ec75b 100644 --- a/packages/wrapper/test/support/helper-classic.ts +++ b/packages/client/test/support/helper-classic.ts @@ -4,9 +4,7 @@ * ------------------------------------------------------------------------------------------ */ import { useWorkerFactory, type WorkerLoader } from 'monaco-languageclient/workerFactory'; -import type { Logger } from 'monaco-languageclient/tools'; -import type { CodeResources, WrapperConfig } from 'monaco-editor-wrapper'; -import { createMonacoEditorDiv } from './helper.js'; +import type { Logger } from 'monaco-languageclient/common'; const workerResolver: Map) => void> = new Map(); const workerPromises: Map> = new Map(); @@ -96,18 +94,3 @@ export const configureClassicWorkerFactory = (logger?: Logger) => { logger }); }; - -export const createWrapperConfigClassicApp = (codeResources: CodeResources): WrapperConfig => { - return { - $type: 'classic', - htmlContainer: createMonacoEditorDiv(), - vscodeApiConfig: { - loadThemes: false - }, - editorAppConfig: { - codeResources, - editorOptions: {}, - monacoWorkerFactory: configureClassicWorkerFactory - } - }; -}; diff --git a/packages/client/test/support/helper.ts b/packages/client/test/support/helper.ts index f27853a91..99f5c31d2 100644 --- a/packages/client/test/support/helper.ts +++ b/packages/client/test/support/helper.ts @@ -3,8 +3,18 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import { MessageTransports } from 'vscode-languageclient'; -import type { LanguageClientConfig } from 'monaco-languageclient/wrapper'; +import type { CodeResources, 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 { MessageTransports } from 'vscode-languageclient/browser.js'; + +export const createMonacoEditorDiv = () => { + const div = document.createElement('div'); + div.id = 'monaco-editor-root'; + document.body.insertAdjacentElement('beforeend', div); + return div; +}; export const createDefaultLcWorkerConfig = (worker: Worker, languageId: string, messageTransports?: MessageTransports): LanguageClientConfig => { @@ -16,7 +26,6 @@ export const createDefaultLcWorkerConfig = (worker: Worker, languageId: string, connection: { options: { $type: 'WorkerDirect', - // create a web worker to pass to the wrapper worker }, messageTransports @@ -33,7 +42,7 @@ export const createUnreachableWorkerConfig = (): LanguageClientConfig => { connection: { options: { $type: 'WorkerConfig', - url: new URL(`${import.meta.url.split('@fs')[0]}/packages/wrapper/test/worker/langium-server.ts`), + url: new URL(`${import.meta.url.split('@fs')[0]}/unknown.ts`), type: 'module' } } @@ -54,3 +63,36 @@ export const createDefaultLcUnreachableUrlConfig = (port: number): LanguageClien } }; }; + +export const createEditorAppConfigClassic = (codeResources: CodeResources): EditorAppConfig => { + return { + $type: 'classic', + codeResources, + editorOptions: {}, + }; +}; + +export const createEditorAppConfigClassicExtended = (codeResources: CodeResources): EditorAppConfig => { + return { + $type: 'extended', + codeResources + }; +}; + +export const createDefaultMonacoVscodeApiConfig = (htmlContainer: HTMLElement): MonacoVscodeApiConfig => { + return { + $type: 'extended', + advanced: { + enforceSemanticHighlighting: true, + loadThemes: false + }, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern' + }) + }, + htmlContainer, + serviceOverrides: {}, + monacoWorkerFactory: configureDefaultWorkerFactory + }; +}; diff --git a/packages/client/test/vscode/manager.test.ts b/packages/client/test/vscode/manager.test.ts new file mode 100644 index 000000000..29338cce6 --- /dev/null +++ b/packages/client/test/vscode/manager.test.ts @@ -0,0 +1,127 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { beforeAll, describe, expect, test } from 'vitest'; +import { IConfigurationService, LogLevel, StandaloneServices } from '@codingame/monaco-vscode-api'; +import { MonacoVscodeApiWrapper, type MonacoEnvironmentEnhanced } from 'monaco-languageclient/vscodeApiWrapper'; +import { createDefaultMonacoVscodeApiConfig, createMonacoEditorDiv } from '../support/helper.js'; + +describe('MonacoVscodeApiWrapper Tests', () => { + + let apiWrapper: MonacoVscodeApiWrapper; + const htmlContainer = createMonacoEditorDiv(); + + beforeAll(() => { + const apiConfig = createDefaultMonacoVscodeApiConfig(htmlContainer); + apiConfig.extensions = [{ + config: { + name: 'unit-test-extension', + publisher: 'TypeFox', + version: '1.0.0', + engines: { + vscode: '*' + }, + contributes: { + languages: [{ + id: 'js', + extensions: ['.js'], + configuration: './language-configuration.json' + }], + grammars: [{ + language: 'js', + scopeName: 'source.js', + path: './javascript.tmLanguage.json' + }] + } + }, + filesOrContents: new Map([ + ['/language-configuration.json', '{}'], + ['/javascript.tmLanguage.json', '{}'] + ]), + }]; + apiWrapper = new MonacoVscodeApiWrapper(apiConfig); + }); + + test.sequential('test init MonacoVscodeApiWrapper and gloabl state', async () => { + let envEnhanced = (self as Window).MonacoEnvironment as MonacoEnvironmentEnhanced; + expect(envEnhanced).toBeUndefined(); + + // call init with api config + const promise = apiWrapper.init(); + envEnhanced = (self as Window).MonacoEnvironment as MonacoEnvironmentEnhanced; + expect(envEnhanced.vscodeApiGlobalInitAwait).toBeDefined(); + expect(envEnhanced.vscodeApiGlobalInitResolve).toBeDefined(); + expect(envEnhanced.vscodeApiInitialised).toBeFalsy(); + + // wait for the initial promise to complete and expect that api init was completed and is no longer ongoing + await expect(await promise).toBeUndefined(); + envEnhanced = (self as Window).MonacoEnvironment as MonacoEnvironmentEnhanced; + expect(envEnhanced.vscodeApiGlobalInitAwait).toBeUndefined(); + expect(envEnhanced.vscodeApiGlobalInitResolve).toBeUndefined(); + expect(envEnhanced.vscodeApiInitialised).toBeTruthy(); + expect(envEnhanced.viewServiceType).toBe('EditorService'); + expect(apiWrapper.getMonacoVscodeApiConfig().workspaceConfig?.developmentOptions?.logLevel).toBe(LogLevel.Off); + }); + + test.sequential('test configureServices logLevel and developmenet info', () => { + const apiConfig = apiWrapper.getMonacoVscodeApiConfig(); + apiConfig.logLevel = LogLevel.Info; + apiConfig.workspaceConfig = { + ...apiConfig.workspaceConfig, + developmentOptions: { + logLevel: LogLevel.Info + } + }; + + // eslint-disable-next-line dot-notation + apiWrapper['configureDevLogLevel'](); + expect(apiWrapper.getMonacoVscodeApiConfig().workspaceConfig?.developmentOptions?.logLevel).toBe(LogLevel.Info); + }); + + test.sequential('test configureServices logLevel and developmenet debug', () => { + const apiConfig = apiWrapper.getMonacoVscodeApiConfig(); + apiConfig.logLevel = LogLevel.Debug; + apiConfig.workspaceConfig = { + ...apiConfig.workspaceConfig, + developmentOptions: { + logLevel: LogLevel.Debug + } + }; + + // eslint-disable-next-line dot-notation + apiWrapper['configureDevLogLevel'](); + expect(apiWrapper.getMonacoVscodeApiConfig().workspaceConfig?.developmentOptions?.logLevel).toBe(LogLevel.Debug); + }); + + test.sequential('test configureServices logLevel development mismatch', () => { + const apiConfig = apiWrapper.getMonacoVscodeApiConfig(); + apiConfig.logLevel = LogLevel.Trace; + apiConfig.workspaceConfig = { + ...apiConfig.workspaceConfig, + developmentOptions: { + logLevel: LogLevel.Info + } + }; + + // eslint-disable-next-line dot-notation + expect(() => apiWrapper['configureDevLogLevel']()).toThrowError('You have configured mismatching logLevels: 1 (wrapperConfig) 3 (workspaceConfig.developmentOptions)'); + }); + + test.sequential('test semanticHighlighting.enabled workaround', async () => { + expect(apiWrapper.getMonacoVscodeApiConfig().workspaceConfig?.configurationDefaults?.['editor.semanticHighlighting.enabled']).toEqual(true); + + const semHigh = await new Promise(resolve => { + setTimeout(() => { + resolve(StandaloneServices.get(IConfigurationService).getValue('editor.semanticHighlighting.enabled')); + }, 100); + }); + expect(semHigh).toEqual(true); + }); + + test.sequential('test dispose extensions and re-init', async () => { + expect(() => apiWrapper.dispose()).not.toThrowError(); + await expect(await apiWrapper.initExtensions()).toBeUndefined(); + }); +}); diff --git a/packages/client/test/vscode/services.test.ts b/packages/client/test/vscode/services.test.ts deleted file mode 100644 index 5cf0b9159..000000000 --- a/packages/client/test/vscode/services.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) 2024 TypeFox and others. - * Licensed under the MIT License. See LICENSE in the package root for license information. - * ------------------------------------------------------------------------------------------ */ - -import { describe, expect, test } from 'vitest'; -import { initServices, type MonacoEnvironmentEnhanced } from 'monaco-languageclient/vscode/services'; - -describe('VSCode services Tests', () => { - - test('initServices', async () => { - const vscodeApiConfig = { - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern' - }) - } - }; - - let envEnhanced = (self as Window).MonacoEnvironment as MonacoEnvironmentEnhanced; - expect(envEnhanced).toBeUndefined(); - - // call initServices with userConfiguration - const promise = initServices(vscodeApiConfig); - envEnhanced = (self as Window).MonacoEnvironment as MonacoEnvironmentEnhanced; - expect(envEnhanced.vscodeInitialising).toBeTruthy(); - expect(envEnhanced.vscodeApiInitialised).toBeFalsy(); - - // try a second time and expect that the api init is ongoing but not completed - const secondCallResult = await initServices(vscodeApiConfig); - - expect(secondCallResult).toBeFalsy(); - envEnhanced = (self as Window).MonacoEnvironment as MonacoEnvironmentEnhanced; - expect(envEnhanced.vscodeInitialising).toBeTruthy(); - expect(envEnhanced.vscodeApiInitialised).toBeFalsy(); - - // wait for the initial promise to complete and expect that api init was completed and is no longer ongoing - const initialCallResult = await promise; - expect(initialCallResult).toBeTruthy(); - envEnhanced = (self as Window).MonacoEnvironment as MonacoEnvironmentEnhanced; - expect(envEnhanced.vscodeInitialising).toBeFalsy(); - expect(envEnhanced.vscodeApiInitialised).toBeTruthy(); - }); - -}); diff --git a/packages/client/test/workerFactory.test.ts b/packages/client/test/worker/workerFactory.test.ts similarity index 96% rename from packages/client/test/workerFactory.test.ts rename to packages/client/test/worker/workerFactory.test.ts index 3e55f8437..ad7fb2a6e 100644 --- a/packages/client/test/workerFactory.test.ts +++ b/packages/client/test/worker/workerFactory.test.ts @@ -5,7 +5,7 @@ import { describe, expect, test } from 'vitest'; import { LogLevel } from '@codingame/monaco-vscode-api'; -import { ConsoleLogger } from 'monaco-languageclient/tools'; +import { ConsoleLogger } from 'monaco-languageclient/common'; import { useWorkerFactory } from 'monaco-languageclient/workerFactory'; describe('WorkerFactory Tests', () => { diff --git a/packages/client/test/worker/workerLoaders.test.ts b/packages/client/test/worker/workerLoaders.test.ts new file mode 100644 index 000000000..a5bacefa6 --- /dev/null +++ b/packages/client/test/worker/workerLoaders.test.ts @@ -0,0 +1,71 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2018-2022 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { beforeAll, describe, expect, test } from 'vitest'; + +import * as monaco from '@codingame/monaco-vscode-editor-api'; +import '@codingame/monaco-vscode-standalone-languages'; +import '@codingame/monaco-vscode-standalone-css-language-features'; +import '@codingame/monaco-vscode-standalone-html-language-features'; +import '@codingame/monaco-vscode-standalone-json-language-features'; +import '@codingame/monaco-vscode-standalone-typescript-language-features'; +import { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; +import { awaitWorkerPromises, configureClassicWorkerFactory, createWorkerPromises } from '../support/helper-classic.js'; +import { createDefaultMonacoVscodeApiConfig, createMonacoEditorDiv } from '../support/helper.js'; +import { createModelReference } from '@codingame/monaco-vscode-api/monaco'; + +describe('Test WorkerLoaders', () => { + + let editor: monaco.editor.IStandaloneCodeEditor; + const htmlContainer = createMonacoEditorDiv(); + + beforeAll(async () => { + const apiConfig = createDefaultMonacoVscodeApiConfig(htmlContainer); + apiConfig.monacoWorkerFactory = configureClassicWorkerFactory; + const apiWrapper = new MonacoVscodeApiWrapper(apiConfig); + await apiWrapper.init(); + + editor = monaco.editor.create(htmlContainer, { + value: 'const text = "Hello World!";', + language: 'javascript' + }); + }); + + test.sequential('Test default worker application', async () => { + // default, expect editor and ts worker to be loaded + createWorkerPromises(['editorWorker', 'tsWorker']); + await expect(await awaitWorkerPromises()).toStrictEqual([undefined, undefined]); + }); + + test.sequential('Test TS worker application', async () => { + // ts worker, expect no worker to be loaded + createWorkerPromises([]); + const modelRefTs = await createModelReference(monaco.Uri.parse(`/workspace/${expect.getState().testPath}.ts`), ''); + editor.setModel(modelRefTs.object.textEditorModel); + await expect(await awaitWorkerPromises()).toStrictEqual([]); + }); + + test.sequential('Test CSS worker application', async () => { + createWorkerPromises(['cssWorker']); + const modelRefCss = await createModelReference(monaco.Uri.parse(`/workspace/${expect.getState().testPath}.css`), ''); + editor.setModel(modelRefCss.object.textEditorModel); + await expect(await awaitWorkerPromises()).toStrictEqual([undefined]); + }); + + test.sequential('Test JSON worker application', async () => { + createWorkerPromises(['jsonWorker']); + const modelRefJson = await createModelReference(monaco.Uri.parse(`/workspace/${expect.getState().testPath}.json`), ''); + editor.setModel(modelRefJson.object.textEditorModel); + await expect(await awaitWorkerPromises()).toStrictEqual([undefined]); + }); + + test.sequential('Test HTML worker application', async () => { + createWorkerPromises(['htmlWorker']); + const modelRefHtml = await createModelReference(monaco.Uri.parse(`/workspace/${expect.getState().testPath}.html`), ''); + editor.setModel(modelRefHtml.object.textEditorModel); + await expect(await awaitWorkerPromises()).toStrictEqual([undefined]); + }); + +}); diff --git a/packages/client/test/wrapper/lcmanager.test.ts b/packages/client/test/wrapper/lcmanager.test.ts new file mode 100644 index 000000000..d4e59a98d --- /dev/null +++ b/packages/client/test/wrapper/lcmanager.test.ts @@ -0,0 +1,64 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { beforeAll, describe, expect, test } from 'vitest'; +import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageclient/browser.js'; +import { delayExecution } from 'monaco-languageclient/common'; +import { LanguageClientsManager } from 'monaco-languageclient/lcwrapper'; +import { MonacoVscodeApiWrapper, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import { createDefaultLcWorkerConfig, createMonacoEditorDiv } from '../support/helper.js'; + +describe('Test LanguageClientWrapper', () => { + + beforeAll(async () => { + const apiConfig: MonacoVscodeApiConfig = { + $type: 'extended', + htmlContainer: createMonacoEditorDiv(), + serviceOverrides: {} + }; + const apiWrapper = new MonacoVscodeApiWrapper(apiConfig); + await apiWrapper.init(); + }); + + test('restart with languageclient', async () => { + let error = false; + const lcManager = new LanguageClientsManager(); + + const workerUrl = new URL('monaco-languageclient-examples/worker/langium', import.meta.url); + const worker = new Worker(workerUrl, { + type: 'module', + name: 'Langium LS (Regular Test)' + }); + expect(worker).toBeDefined(); + + const reader = new BrowserMessageReader(worker); + const writer = new BrowserMessageWriter(worker); + const languageClientConfig = createDefaultLcWorkerConfig(worker, 'langium', { reader, writer }); + + const lcConfigs = { + configs: { + 'langium': languageClientConfig + } + }; + + try { + await expect(async () => await lcManager.setConfigs(lcConfigs)).not.toThrowError(); + await expect(async () => await lcManager.start()).not.toThrowError(); + await expect(async () => await lcManager.dispose()).not.toThrowError(); + + await delayExecution(1000); + + await expect(async () => await lcManager.setConfigs(lcConfigs)).not.toThrowError(); + await expect(async () => await lcManager.start()).not.toThrowError(); + await expect(async () => await lcManager.dispose()).not.toThrowError(); + } catch (e) { + console.error(`Unexpected error occured: ${e}`); + error = true; + } + + expect(error).toBe(false); + }); + +}); diff --git a/packages/client/test/languageClientWrapper.test.ts b/packages/client/test/wrapper/lcwrapper.test.ts similarity index 83% rename from packages/client/test/languageClientWrapper.test.ts rename to packages/client/test/wrapper/lcwrapper.test.ts index 14f213a9f..5d7b27d0b 100644 --- a/packages/client/test/languageClientWrapper.test.ts +++ b/packages/client/test/wrapper/lcwrapper.test.ts @@ -5,14 +5,20 @@ import { beforeAll, describe, expect, test } from 'vitest'; import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageclient/browser.js'; -import { LanguageClientWrapper } from 'monaco-languageclient/wrapper'; -import { initServices } from 'monaco-languageclient/vscode/services'; -import { createDefaultLcUnreachableUrlConfig, createDefaultLcWorkerConfig, createUnreachableWorkerConfig } from './support/helper.js'; +import { LanguageClientWrapper } from 'monaco-languageclient/lcwrapper'; +import { MonacoVscodeApiWrapper, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import { createDefaultLcUnreachableUrlConfig, createDefaultLcWorkerConfig, createMonacoEditorDiv, createUnreachableWorkerConfig } from '../support/helper.js'; describe('Test LanguageClientWrapper', () => { beforeAll(async () => { - await initServices({}); + const apiConfig: MonacoVscodeApiConfig = { + $type: 'extended', + htmlContainer: createMonacoEditorDiv(), + serviceOverrides: {} + }; + const monacoVscodeApiManager = new MonacoVscodeApiWrapper(apiConfig); + await monacoVscodeApiManager.init(); }); const createWorkerAndConfig = () => { @@ -28,6 +34,7 @@ describe('Test LanguageClientWrapper', () => { console.log('Received message from worker:', message); }); const languageClientConfig = createDefaultLcWorkerConfig(worker, 'langium', { reader, writer }); + languageClientConfig.disposeWorker = true; return { worker, languageClientConfig @@ -36,17 +43,13 @@ describe('Test LanguageClientWrapper', () => { test('Constructor: no config', () => { const workerAndConfig = createWorkerAndConfig(); - const languageClientWrapper = new LanguageClientWrapper({ - languageClientConfig: workerAndConfig.languageClientConfig - }); + const languageClientWrapper = new LanguageClientWrapper(workerAndConfig.languageClientConfig); expect(languageClientWrapper.haveLanguageClient()).toBeFalsy(); }); test('Dispose: direct worker is cleaned up afterwards', async () => { const workerAndConfig = createWorkerAndConfig(); - const languageClientWrapper = new LanguageClientWrapper({ - languageClientConfig: workerAndConfig.languageClientConfig - }); + const languageClientWrapper = new LanguageClientWrapper(workerAndConfig.languageClientConfig); expect(workerAndConfig.worker).toBeDefined(); expect(languageClientWrapper.getWorker()).toBeUndefined(); @@ -61,15 +64,13 @@ describe('Test LanguageClientWrapper', () => { expect(languageClientWrapper.getWorker()).toBeTruthy(); // dispose & verify - await languageClientWrapper.disposeLanguageClient(true); + await languageClientWrapper.dispose(); expect(languageClientWrapper.getWorker()).toBeUndefined(); }); test('Start: unreachable url', async () => { const languageClientConfig = createDefaultLcUnreachableUrlConfig(23456); - const languageClientWrapper = new LanguageClientWrapper({ - languageClientConfig - }); + const languageClientWrapper = new LanguageClientWrapper(languageClientConfig); try { await languageClientWrapper.start(); @@ -94,9 +95,7 @@ describe('Test LanguageClientWrapper', () => { test('Start: unreachable worker url', async () => { const languageClientConfig = createUnreachableWorkerConfig(); - const languageClientWrapper = new LanguageClientWrapper({ - languageClientConfig - }); + const languageClientWrapper = new LanguageClientWrapper(languageClientConfig); await expect(languageClientWrapper.start()).rejects.toEqual({ message: 'languageClientWrapper (test-worker-unreachable): Illegal worker configuration detected.', @@ -106,9 +105,7 @@ describe('Test LanguageClientWrapper', () => { test('Dispose: start, dispose worker and restart', async () => { const workerAndConfig = createWorkerAndConfig(); - const languageClientWrapper = new LanguageClientWrapper({ - languageClientConfig: workerAndConfig.languageClientConfig - }); + const languageClientWrapper = new LanguageClientWrapper(workerAndConfig.languageClientConfig); expect(workerAndConfig.worker).toBeDefined(); expect(languageClientWrapper.getWorker()).toBeUndefined(); @@ -123,7 +120,7 @@ describe('Test LanguageClientWrapper', () => { expect(languageClientWrapper.getWorker()).toBeTruthy(); // dispose & verify - await languageClientWrapper.disposeLanguageClient(true); + await languageClientWrapper.dispose(); expect(languageClientWrapper.getWorker()).toBeUndefined(); // restart & verify diff --git a/packages/examples/json.html b/packages/examples/json.html index d3e6565bd..55a6ab7f2 100644 --- a/packages/examples/json.html +++ b/packages/examples/json.html @@ -17,7 +17,7 @@
diff --git a/packages/examples/package.json b/packages/examples/package.json index a187d5cc6..4865d914f 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -118,7 +118,6 @@ "express": "~5.1.0", "jszip": "~3.10.1", "langium": "~4.0.0", - "monaco-editor-wrapper": "~7.0.0-next.0", "monaco-languageclient": "~10.0.0-next.0", "pyright": "~1.1.403", "react": "~19.1.1", diff --git a/packages/examples/resources/groovy/workspace/hello.groovy b/packages/examples/resources/groovy/workspace/hello.groovy new file mode 100644 index 000000000..a239f2c1f --- /dev/null +++ b/packages/examples/resources/groovy/workspace/hello.groovy @@ -0,0 +1,3 @@ +package test.org; +import java.io.File; +File file = new File("E:/Example.txt"); diff --git a/packages/examples/resources/json/workspace/hello.json b/packages/examples/resources/json/workspace/hello.json new file mode 100644 index 000000000..c9c6939f4 --- /dev/null +++ b/packages/examples/resources/json/workspace/hello.json @@ -0,0 +1,4 @@ +{ + "$schema": "http://json.schemastore.org/coffeelint", + "line_endings": {"value": "unix"} +} diff --git a/packages/examples/src/appPlayground/common.ts b/packages/examples/src/appPlayground/common.ts index 67d17da5c..e594ad8ec 100644 --- a/packages/examples/src/appPlayground/common.ts +++ b/packages/examples/src/appPlayground/common.ts @@ -5,11 +5,11 @@ import * as vscode from 'vscode'; import type { RegisterLocalProcessExtensionResult } from '@codingame/monaco-vscode-api/extensions'; -import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; import type { ConfigResult } from './config.js'; +import type { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; -export const configurePostStart = async (wrapper: MonacoEditorLanguageClientWrapper, configResult: ConfigResult) => { - const result = wrapper.getExtensionRegisterResult('mlc-app-playground') as RegisterLocalProcessExtensionResult; +export const configurePostStart = async (apiWrapper: MonacoVscodeApiWrapper, configResult: ConfigResult) => { + const result = apiWrapper.getExtensionRegisterResult('mlc-app-playground') as RegisterLocalProcessExtensionResult; result.setAsDefaultApi(); // WA: Force show explorer and not search diff --git a/packages/examples/src/appPlayground/config.ts b/packages/examples/src/appPlayground/config.ts index 2de4366ae..a59e7ab73 100644 --- a/packages/examples/src/appPlayground/config.ts +++ b/packages/examples/src/appPlayground/config.ts @@ -10,6 +10,7 @@ import { InMemoryFileSystemProvider, registerFileSystemOverlay, type IFileWriteO import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; import getLifecycleServiceOverride from '@codingame/monaco-vscode-lifecycle-service-override'; import getLocalizationServiceOverride from '@codingame/monaco-vscode-localization-service-override'; +import getOutlineServiceOverride from '@codingame/monaco-vscode-outline-service-override'; import getRemoteAgentServiceOverride from '@codingame/monaco-vscode-remote-agent-service-override'; import getSearchServiceOverride from '@codingame/monaco-vscode-search-service-override'; import getSecretStorageServiceOverride from '@codingame/monaco-vscode-secret-storage-service-override'; @@ -17,7 +18,6 @@ import getStorageServiceOverride from '@codingame/monaco-vscode-storage-service- import getBannerServiceOverride from '@codingame/monaco-vscode-view-banner-service-override'; import getStatusBarServiceOverride from '@codingame/monaco-vscode-view-status-bar-service-override'; import getTitleBarServiceOverride from '@codingame/monaco-vscode-view-title-bar-service-override'; -import getOutlineServiceOverride from '@codingame/monaco-vscode-outline-service-override'; import * as vscode from 'vscode'; // this is required syntax highlighting @@ -27,16 +27,17 @@ import '@codingame/monaco-vscode-typescript-language-features-default-extension' import '../../resources/vsix/open-collaboration-tools.vsix'; -import type { WrapperConfig } from 'monaco-editor-wrapper'; -import { defaultHtmlAugmentationInstructions, defaultViewsInit } from 'monaco-editor-wrapper/vscode/services'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; -import { createDefaultLocaleConfiguration } from 'monaco-languageclient/vscode/services'; +import { defaultHtmlAugmentationInstructions, defaultViewsInit, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import type { EditorAppConfig } from 'monaco-languageclient/editorApp'; +import { createDefaultLocaleConfiguration } from 'monaco-languageclient/vscodeApiLocales'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; import helloTsCode from '../../resources/appPlayground/hello.ts?raw'; import testerTsCode from '../../resources/appPlayground/tester.ts?raw'; import { createDefaultWorkspaceContent } from '../common/client/utils.js'; export type ConfigResult = { - wrapperConfig: WrapperConfig + vscodeApiConfig: MonacoVscodeApiConfig; + editorAppConfig: EditorAppConfig workspaceFileUri: vscode.Uri; helloTsUri: vscode.Uri; testerTsUri: vscode.Uri; @@ -45,69 +46,65 @@ export type ConfigResult = { export const configure = async (htmlContainer?: HTMLElement): Promise => { const workspaceFileUri = vscode.Uri.file('/workspace.code-workspace'); - const wrapperConfig: WrapperConfig = { + const vscodeApiConfig: MonacoVscodeApiConfig = { $type: 'extended', - id: 'AAP', logLevel: LogLevel.Debug, htmlContainer, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride(), - ...getLifecycleServiceOverride(), - ...getLocalizationServiceOverride(createDefaultLocaleConfiguration()), - ...getBannerServiceOverride(), - ...getStatusBarServiceOverride(), - ...getTitleBarServiceOverride(), - ...getExplorerServiceOverride(), - ...getRemoteAgentServiceOverride(), - ...getEnvironmentServiceOverride(), - ...getSecretStorageServiceOverride(), - ...getStorageServiceOverride(), - ...getSearchServiceOverride(), - ...getOutlineServiceOverride() - }, - enableExtHostWorker: true, - viewsConfig: { - viewServiceType: 'ViewsService', - htmlAugmentationInstructions: defaultHtmlAugmentationInstructions, - viewsInitFunc: defaultViewsInit + serviceOverrides: { + ...getKeybindingsServiceOverride(), + ...getLifecycleServiceOverride(), + ...getLocalizationServiceOverride(createDefaultLocaleConfiguration()), + ...getBannerServiceOverride(), + ...getStatusBarServiceOverride(), + ...getTitleBarServiceOverride(), + ...getExplorerServiceOverride(), + ...getRemoteAgentServiceOverride(), + ...getEnvironmentServiceOverride(), + ...getSecretStorageServiceOverride(), + ...getStorageServiceOverride(), + ...getSearchServiceOverride(), + ...getOutlineServiceOverride() + }, + viewsConfig: { + viewServiceType: 'ViewsService', + htmlAugmentationInstructions: defaultHtmlAugmentationInstructions, + viewsInitFunc: defaultViewsInit + }, + workspaceConfig: { + enableWorkspaceTrust: true, + windowIndicator: { + label: 'mlc-app-playground', + tooltip: '', + command: '' }, - workspaceConfig: { - enableWorkspaceTrust: true, - windowIndicator: { - label: 'mlc-app-playground', - tooltip: '', - command: '' - }, - workspaceProvider: { - trusted: true, - async open() { - window.open(window.location.href); - return true; - }, - workspace: { - workspaceUri: workspaceFileUri - } + workspaceProvider: { + trusted: true, + async open() { + window.open(window.location.href); + return true; }, - configurationDefaults: { - 'window.title': 'mlc-app-playground${separator}${dirty}${activeEditorShort}' - }, - productConfiguration: { - nameShort: 'mlc-app-playground', - nameLong: 'mlc-app-playground' + workspace: { + workspaceUri: workspaceFileUri } }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'editor.wordBasedSuggestions': 'off', - 'typescript.tsserver.web.projectWideIntellisense.enabled': true, - 'typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors': false, - 'editor.guides.bracketPairsHorizontal': true, - 'oct.serverUrl': 'https://api.open-collab.tools/', - 'editor.experimental.asyncTokenization': false - }) + configurationDefaults: { + 'window.title': 'mlc-app-playground${separator}${dirty}${activeEditorShort}' }, + productConfiguration: { + nameShort: 'mlc-app-playground', + nameLong: 'mlc-app-playground' + } + }, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.wordBasedSuggestions': 'off', + 'typescript.tsserver.web.projectWideIntellisense.enabled': true, + 'typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors': false, + 'editor.guides.bracketPairsHorizontal': true, + 'oct.serverUrl': 'https://api.open-collab.tools/', + 'editor.experimental.asyncTokenization': false + }) }, extensions: [{ config: { @@ -119,9 +116,15 @@ export const configure = async (htmlContainer?: HTMLElement): Promise { disableElement('button-start', true); + const configResult = await configure(document.body); - await wrapper.init(configResult.wrapperConfig); - await configurePostStart(wrapper, configResult); + + // perform global init + const apiWrapper = new MonacoVscodeApiWrapper(configResult.vscodeApiConfig); + await apiWrapper.init(); + + await configurePostStart(apiWrapper, configResult); }; diff --git a/packages/examples/src/appPlayground/reactLauncher.ts b/packages/examples/src/appPlayground/reactLauncher.ts index 214cbc255..e37cdb142 100644 --- a/packages/examples/src/appPlayground/reactLauncher.ts +++ b/packages/examples/src/appPlayground/reactLauncher.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import { initLocaleLoader } from 'monaco-editor-wrapper/vscode/locale'; +import { initLocaleLoader } from 'monaco-languageclient/vscodeApiLocales'; + await initLocaleLoader(); const { runApplicationPlaygroundReact } = await import('./reactMain.js'); diff --git a/packages/examples/src/appPlayground/reactMain.tsx b/packages/examples/src/appPlayground/reactMain.tsx index 009809e5e..b5a9819f2 100644 --- a/packages/examples/src/appPlayground/reactMain.tsx +++ b/packages/examples/src/appPlayground/reactMain.tsx @@ -5,7 +5,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; import { MonacoEditorReactComp } from '@typefox/monaco-editor-react'; import { configure } from './config.js'; import { configurePostStart } from './common.js'; @@ -18,9 +17,10 @@ export const runApplicationPlaygroundReact = async () => { return (
{ - await configurePostStart(wrapper, configResult); + vscodeApiConfig={configResult.vscodeApiConfig} + editorAppConfig={configResult.editorAppConfig} + onVscodeApiInitDone={async (apiWrapper) => { + await configurePostStart(apiWrapper, configResult); }} onError={(e) => { console.error(e); diff --git a/packages/examples/src/bare/client.ts b/packages/examples/src/bare/client.ts index cca7cd8a5..3be634ab6 100644 --- a/packages/examples/src/bare/client.ts +++ b/packages/examples/src/bare/client.ts @@ -3,22 +3,22 @@ * Licensed under the MIT License. See License.txt in the project root for license information. * ------------------------------------------------------------------------------------------ */ +import { LogLevel } from '@codingame/monaco-vscode-api'; import * as monaco from '@codingame/monaco-vscode-editor-api'; -import { initServices } from 'monaco-languageclient/vscode/services'; import getTextmateServiceOverride from '@codingame/monaco-vscode-textmate-service-override'; import getThemeServiceOverride from '@codingame/monaco-vscode-theme-service-override'; -import { LogLevel } from '@codingame/monaco-vscode-api'; +import { MonacoVscodeApiWrapper, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; // monaco-editor does not supply json highlighting with the json worker, // that's why we use the textmate extension from VSCode import '@codingame/monaco-vscode-json-default-extension'; -import { ConsoleLogger } from 'monaco-languageclient/tools'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; -import { LanguageClientWrapper, type LanguageClientConfig } from 'monaco-languageclient/wrapper'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; +import { LanguageClientWrapper, type LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; export const runClient = async () => { - const logger = new ConsoleLogger(LogLevel.Debug); const htmlContainer = document.getElementById('monaco-editor-root')!; - await initServices({ + const vscodeApiConfig: MonacoVscodeApiConfig = { + $type: 'classic', + logLevel: LogLevel.Debug, serviceOverrides: { ...getTextmateServiceOverride(), ...getThemeServiceOverride() @@ -28,9 +28,11 @@ export const runClient = async () => { 'editor.experimental.asyncTokenization': true }) }, - }, { - logger - }); + monacoWorkerFactory: configureDefaultWorkerFactory + }; + + const apiWrapper = new MonacoVscodeApiWrapper(vscodeApiConfig); + await apiWrapper.init(); // register the JSON language with Monaco monaco.languages.register({ @@ -40,8 +42,6 @@ export const runClient = async () => { mimetypes: ['application/json'] }); - configureDefaultWorkerFactory(logger); - // create monaco editor monaco.editor.create(htmlContainer, { value: `{ @@ -64,10 +64,9 @@ export const runClient = async () => { } } }; - const languageClientWrapper = new LanguageClientWrapper({ + const languageClientWrapper = new LanguageClientWrapper( languageClientConfig, - logger - }); - + apiWrapper.getLogger() + ); await languageClientWrapper.start(); }; diff --git a/packages/examples/src/browser/main.ts b/packages/examples/src/browser/main.ts index 51239c3ed..a4d11b579 100644 --- a/packages/examples/src/browser/main.ts +++ b/packages/examples/src/browser/main.ts @@ -11,8 +11,9 @@ import { getLanguageService, TextDocument } from 'vscode-json-languageservice'; import { createConverter as createCodeConverter } from 'vscode-languageclient/lib/common/codeConverter.js'; import { createConverter as createProtocolConverter } from 'vscode-languageclient/lib/common/protocolConverter.js'; import { LogLevel } from '@codingame/monaco-vscode-api'; -import { MonacoEditorLanguageClientWrapper, type WrapperConfig } from 'monaco-editor-wrapper'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; +import { EditorApp, type EditorAppConfig } from 'monaco-languageclient/editorApp'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; +import { MonacoVscodeApiWrapper, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; export const runBrowserEditor = async () => { const codeConverter = createCodeConverter(); @@ -27,35 +28,36 @@ export const runBrowserEditor = async () => { }`; const codeUri = '/workspace/model.json'; - const wrapper = new MonacoEditorLanguageClientWrapper(); - const jsonClientUserConfig: WrapperConfig = { + const vscodeApiConfig: MonacoVscodeApiConfig = { $type: 'extended', htmlContainer, logLevel: LogLevel.Debug, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride(), - }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'editor.guides.bracketPairsHorizontal': 'active', - 'editor.lightbulb.enabled': 'On', - 'editor.experimental.asyncTokenization': true - }) - } + serviceOverrides: { + ...getKeybindingsServiceOverride(), + }, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.guides.bracketPairsHorizontal': 'active', + 'editor.lightbulb.enabled': 'On', + 'editor.experimental.asyncTokenization': true + }) }, - editorAppConfig: { - codeResources: { - modified: { - text: code, - uri: codeUri - } - }, - monacoWorkerFactory: configureDefaultWorkerFactory + monacoWorkerFactory: configureDefaultWorkerFactory + }; + const editorAppConfig: EditorAppConfig = { + $type: vscodeApiConfig.$type, + codeResources: { + modified: { + text: code, + uri: codeUri + } } }; - await wrapper.init(jsonClientUserConfig); + const apiWrapper = new MonacoVscodeApiWrapper(vscodeApiConfig); + await apiWrapper.init(); + + const editorApp = new EditorApp(editorAppConfig); vscode.workspace.onDidOpenTextDocument((_event) => { mainVscodeDocument = _event; @@ -155,9 +157,9 @@ export const runBrowserEditor = async () => { diagnosticCollection.clear(); }; - await wrapper.start(); + await editorApp.start(htmlContainer); - wrapper.getTextModels()?.modified?.onDidChangeContent(() => { + editorApp.getTextModels().modified?.onDidChangeContent(() => { validate(); }); }; diff --git a/packages/examples/src/clangd/client/config.ts b/packages/examples/src/clangd/client/config.ts index 5561d9336..807cfbe91 100644 --- a/packages/examples/src/clangd/client/config.ts +++ b/packages/examples/src/clangd/client/config.ts @@ -3,113 +3,91 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import { Uri } from 'vscode'; +import { LogLevel } from '@codingame/monaco-vscode-api'; +import getEnvironmentServiceOverride from '@codingame/monaco-vscode-environment-service-override'; +import getExplorerServiceOverride from '@codingame/monaco-vscode-explorer-service-override'; import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; import getLifecycleServiceOverride from '@codingame/monaco-vscode-lifecycle-service-override'; +import getRemoteAgentServiceOverride from '@codingame/monaco-vscode-remote-agent-service-override'; +import getSecretStorageServiceOverride from '@codingame/monaco-vscode-secret-storage-service-override'; import getBannerServiceOverride from '@codingame/monaco-vscode-view-banner-service-override'; import getStatusBarServiceOverride from '@codingame/monaco-vscode-view-status-bar-service-override'; import getTitleBarServiceOverride from '@codingame/monaco-vscode-view-title-bar-service-override'; -import getExplorerServiceOverride from '@codingame/monaco-vscode-explorer-service-override'; -import getRemoteAgentServiceOverride from '@codingame/monaco-vscode-remote-agent-service-override'; -import getEnvironmentServiceOverride from '@codingame/monaco-vscode-environment-service-override'; -import getSecretStorageServiceOverride from '@codingame/monaco-vscode-secret-storage-service-override'; -import { LogLevel } from '@codingame/monaco-vscode-api'; -import type { WrapperConfig } from 'monaco-editor-wrapper'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; -import { defaultHtmlAugmentationInstructions, defaultViewsInit } from 'monaco-editor-wrapper/vscode/services'; +import type { EditorAppConfig } from 'monaco-languageclient/editorApp'; +import type { LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import { defaultHtmlAugmentationInstructions, defaultViewsInit, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; +import { Uri } from 'vscode'; import { ClangdWorkerHandler } from './workerHandler.js'; -export const createWrapperConfig = async (config: { +export type ClangdAppConfig = { + languageClientConfig: LanguageClientConfig; + vscodeApiConfig: MonacoVscodeApiConfig; + editorAppConfig: EditorAppConfig; +} + +export const createClangdAppConfig = async (config: { htmlContainer: HTMLElement, workspaceUri: Uri, workspaceFileUri: Uri, clangdWorkerHandler: ClangdWorkerHandler, lsMessageLocalPort: MessagePort -}): Promise => { - return { +}): Promise => { + const vscodeApiConfig: MonacoVscodeApiConfig = { $type: 'extended', htmlContainer: config.htmlContainer, logLevel: LogLevel.Debug, - languageClientConfigs: { - configs: { - LANGUAGE_ID: { - name: 'Clangd WASM Language Server', - connection: { - options: { - $type: 'WorkerDirect', - worker: await config.clangdWorkerHandler.createWorker(), - messagePort: config.lsMessageLocalPort - } - }, - restartOptions: { - retries: 5, - timeout: 1000, - keepWorker: true - }, - clientOptions: { - documentSelector: ['cpp'], - workspaceFolder: { - index: 0, - name: 'workspace', - uri: config.workspaceUri - } - } - } - } + serviceOverrides: { + ...getKeybindingsServiceOverride(), + ...getLifecycleServiceOverride(), + ...getBannerServiceOverride(), + ...getStatusBarServiceOverride(), + ...getTitleBarServiceOverride(), + ...getExplorerServiceOverride(), + ...getRemoteAgentServiceOverride(), + ...getEnvironmentServiceOverride(), + ...getSecretStorageServiceOverride() }, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride(), - ...getLifecycleServiceOverride(), - ...getBannerServiceOverride(), - ...getStatusBarServiceOverride(), - ...getTitleBarServiceOverride(), - ...getExplorerServiceOverride(), - ...getRemoteAgentServiceOverride(), - ...getEnvironmentServiceOverride(), - ...getSecretStorageServiceOverride() - }, - viewsConfig: { - viewServiceType: 'ViewsService', - htmlAugmentationInstructions: defaultHtmlAugmentationInstructions, - viewsInitFunc: defaultViewsInit + viewsConfig: { + viewServiceType: 'ViewsService', + htmlAugmentationInstructions: defaultHtmlAugmentationInstructions, + viewsInitFunc: defaultViewsInit + }, + workspaceConfig: { + enableWorkspaceTrust: true, + windowIndicator: { + label: 'mlc-clangd-example', + tooltip: '', + command: '' }, - workspaceConfig: { - enableWorkspaceTrust: true, - windowIndicator: { - label: 'mlc-clangd-example', - tooltip: '', - command: '' - }, - workspaceProvider: { - trusted: true, - async open() { - window.open(window.location.href); - return true; - }, - workspace: { - workspaceUri: config.workspaceFileUri - }, + workspaceProvider: { + trusted: true, + async open() { + window.open(window.location.href); + return true; }, - configurationDefaults: { - 'window.title': 'mlc-clangd-exampled${separator}${dirty}${activeEditorShort}' + workspace: { + workspaceUri: config.workspaceFileUri }, - productConfiguration: { - nameShort: 'mlc-clangd-example', - nameLong: 'mlc-clangd-example' - } }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'editor.wordBasedSuggestions': 'off', - 'editor.guides.bracketPairsHorizontal': true, - 'editor.inlayHints.enabled': 'offUnlessPressed', - 'editor.quickSuggestionsDelay': 200, - 'editor.experimental.asyncTokenization': false - }) + configurationDefaults: { + 'window.title': 'mlc-clangd-exampled${separator}${dirty}${activeEditorShort}' + }, + productConfiguration: { + nameShort: 'mlc-clangd-example', + nameLong: 'mlc-clangd-example' } }, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.wordBasedSuggestions': 'off', + 'editor.guides.bracketPairsHorizontal': true, + 'editor.inlayHints.enabled': 'offUnlessPressed', + 'editor.quickSuggestionsDelay': 200, + 'editor.experimental.asyncTokenization': false + }) + }, extensions: [{ config: { name: 'mlc-clangd-example', @@ -120,8 +98,40 @@ export const createWrapperConfig = async (config: { } } }], - editorAppConfig: { - monacoWorkerFactory: configureDefaultWorkerFactory + monacoWorkerFactory: configureDefaultWorkerFactory + }; + + const languageClientConfig: LanguageClientConfig = { + name: 'Clangd WASM Language Server', + connection: { + options: { + $type: 'WorkerDirect', + worker: await config.clangdWorkerHandler.createWorker(), + messagePort: config.lsMessageLocalPort + } + }, + restartOptions: { + retries: 5, + timeout: 1000, + keepWorker: true + }, + clientOptions: { + documentSelector: ['cpp'], + workspaceFolder: { + index: 0, + name: 'workspace', + uri: config.workspaceUri + } } }; + + const editorAppConfig: EditorAppConfig = { + $type: vscodeApiConfig.$type + }; + + return { + vscodeApiConfig, + languageClientConfig, + editorAppConfig + }; }; diff --git a/packages/examples/src/clangd/client/main.ts b/packages/examples/src/clangd/client/main.ts index d8f8852d1..96e8ea804 100644 --- a/packages/examples/src/clangd/client/main.ts +++ b/packages/examples/src/clangd/client/main.ts @@ -3,18 +3,17 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import * as vscode from 'vscode'; import { RegisteredFileSystemProvider, RegisteredMemoryFile, registerFileSystemOverlay } from '@codingame/monaco-vscode-files-service-override'; +import * as vscode from 'vscode'; // this is required syntax highlighting import '@codingame/monaco-vscode-cpp-default-extension'; -import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; -import { createWrapperConfig } from './config.js'; -import { ClangdWorkerHandler } from './workerHandler.js'; -import { MainRemoteMessageChannelFs } from './mainRemoteMessageChannelFs.js'; +import { LanguageClientWrapper } from 'monaco-languageclient/lcwrapper'; +import { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; import { createDefaultWorkspaceContent, disableElement } from '../../common/client/utils.js'; import { HOME_DIR, WORKSPACE_PATH } from '../definitions.js'; - -const wrapper = new MonacoEditorLanguageClientWrapper(); +import { createClangdAppConfig } from './config.js'; +import { MainRemoteMessageChannelFs } from './mainRemoteMessageChannelFs.js'; +import { ClangdWorkerHandler } from './workerHandler.js'; export const runClangdWrapper = async () => { const channelLs = new MessageChannel(); @@ -32,7 +31,7 @@ export const runClangdWrapper = async () => { new MainRemoteMessageChannelFs(fileSystemProvider, channelFs.port1, readiness); const clangdWorkerHandler = new ClangdWorkerHandler(); - const wrapperConfig = await createWrapperConfig({ + const appConfig = await createClangdAppConfig({ htmlContainer: document.body, workspaceUri: vscode.Uri.file(WORKSPACE_PATH), workspaceFileUri, @@ -40,7 +39,12 @@ export const runClangdWrapper = async () => { lsMessageLocalPort: channelLs.port1 }); - await wrapper.init(wrapperConfig); + // perform global init + const apiWrapper = new MonacoVscodeApiWrapper(appConfig.vscodeApiConfig); + await apiWrapper.init(); + + const lcWrapper = new LanguageClientWrapper(appConfig.languageClientConfig); + const initConfig = { lsMessagePort: channelLs.port2, fsMessagePort: channelFs.port2, @@ -53,7 +57,7 @@ export const runClangdWrapper = async () => { const startWrapper = async () => { await clangdWorkerHandler.init(initConfig); await clangdWorkerHandler.launch(); - await wrapper.startLanguageClients(); + await lcWrapper.start(); }; try { diff --git a/packages/examples/src/common/client/extendedClient.ts b/packages/examples/src/common/client/extendedClient.ts new file mode 100644 index 000000000..d81fa7da3 --- /dev/null +++ b/packages/examples/src/common/client/extendedClient.ts @@ -0,0 +1,111 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { RegisteredFileSystemProvider, RegisteredMemoryFile, registerFileSystemOverlay } from '@codingame/monaco-vscode-files-service-override'; +import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; +import * as vscode from 'vscode'; +// this is required syntax highlighting +import { LogLevel } from '@codingame/monaco-vscode-api'; +import '@codingame/monaco-vscode-java-default-extension'; +import { EditorApp, type EditorAppConfig } from 'monaco-languageclient/editorApp'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; +import { LanguageClientWrapper, type LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import { MonacoVscodeApiWrapper, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; + +export const runExtendedClient = async (lsConfig: ExampleLsConfig, helloCode: string) => { + const helloUri = vscode.Uri.file(`${lsConfig.basePath}/workspace/hello.${lsConfig.documentSelector}`); + const fileSystemProvider = new RegisteredFileSystemProvider(false); + fileSystemProvider.registerFile(new RegisteredMemoryFile(helloUri, helloCode)); + registerFileSystemOverlay(1, fileSystemProvider); + + const htmlContainer = document.getElementById('monaco-editor-root')!; + const vscodeApiConfig: MonacoVscodeApiConfig = { + $type: 'extended', + htmlContainer, + logLevel: LogLevel.Debug, + serviceOverrides: { + ...getKeybindingsServiceOverride(), + }, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.guides.bracketPairsHorizontal': 'active', + 'editor.lightbulb.enabled': 'On', + 'editor.wordBasedSuggestions': 'off', + 'editor.experimental.asyncTokenization': true + }) + }, + monacoWorkerFactory: configureDefaultWorkerFactory + }; + + const languageClientConfig: LanguageClientConfig = { + connection: { + options: { + $type: 'WebSocketUrl', + url: `ws://localhost:${lsConfig.port}${lsConfig.path}`, + startOptions: { + onCall: () => { + console.log('Connected to socket.'); + }, + reportStatus: true + }, + stopOptions: { + onCall: () => { + console.log('Disconnected from socket.'); + }, + reportStatus: true + } + }, + }, + clientOptions: { + documentSelector: [lsConfig.documentSelector], + workspaceFolder: { + index: 0, + name: 'workspace', + uri: vscode.Uri.parse(`${lsConfig.basePath}/workspace`) + } + } + }; + + const editorAppConfig: EditorAppConfig = { + $type: vscodeApiConfig.$type, + codeResources: { + modified: { + text: helloCode, + uri: helloUri.path + } + } + }; + + // perform global init + const apiWrapper = new MonacoVscodeApiWrapper(vscodeApiConfig); + await apiWrapper.init(); + + const lcWrapper = new LanguageClientWrapper(languageClientConfig, apiWrapper.getLogger()); + const editorApp = new EditorApp(editorAppConfig); + + try { + document.querySelector('#button-start')?.addEventListener('click', async () => { + await editorApp.start(htmlContainer); + await lcWrapper.start(); + + // open files, so the LS can pick it up + await vscode.workspace.openTextDocument(helloUri); + }); + document.querySelector('#button-dispose')?.addEventListener('click', async () => { + await editorApp.dispose(); + await lcWrapper.dispose(); + }); + } catch (e) { + console.error(e); + } +}; + +export type ExampleLsConfig = { + port: number; + path: string; + basePath: string; + documentSelector: string; +}; diff --git a/packages/examples/src/common/client/utils.ts b/packages/examples/src/common/client/utils.ts index 4d4783afa..995dff2cb 100644 --- a/packages/examples/src/common/client/utils.ts +++ b/packages/examples/src/common/client/utils.ts @@ -4,6 +4,9 @@ * ------------------------------------------------------------------------------------------ */ import type { IStoredWorkspace } from '@codingame/monaco-vscode-configuration-service-override'; +import type { EditorAppConfig } from 'monaco-languageclient/editorApp'; +import type { LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import type { MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; export const disableElement = (id: string, disabled: boolean) => { const button = document.getElementById(id) as HTMLButtonElement | HTMLInputElement | null; @@ -26,6 +29,8 @@ export const createDefaultWorkspaceContent = (workspacePath: string) => { ); }; -export const delayExecution = (ms: number) => { - return new Promise((resolve) => setTimeout(resolve, ms)); +export type ExampleAppConfig = { + vscodeApiConfig: MonacoVscodeApiConfig; + languageClientConfig: LanguageClientConfig; + editorAppConfig: EditorAppConfig; }; diff --git a/packages/examples/src/debugger/client/debugger.ts b/packages/examples/src/debugger/client/debugger.ts index 5a51ef579..0d7a99959 100644 --- a/packages/examples/src/debugger/client/debugger.ts +++ b/packages/examples/src/debugger/client/debugger.ts @@ -4,7 +4,7 @@ * ------------------------------------------------------------------------------------------ */ import * as vscode from 'vscode'; -import type { ExtensionConfig } from 'monaco-editor-wrapper'; +import type { ExtensionConfig } from 'monaco-languageclient/vscodeApiWrapper'; import type { ConfigParams, InitMessage } from '../common/definitions.js'; // This is derived from: diff --git a/packages/examples/src/eclipse.jdt.ls/client/main.ts b/packages/examples/src/eclipse.jdt.ls/client/main.ts index a91fb2ad3..8e20cc3c9 100644 --- a/packages/examples/src/eclipse.jdt.ls/client/main.ts +++ b/packages/examples/src/eclipse.jdt.ls/client/main.ts @@ -3,86 +3,12 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import * as vscode from 'vscode'; -import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; -import { RegisteredFileSystemProvider, RegisteredMemoryFile, registerFileSystemOverlay } from '@codingame/monaco-vscode-files-service-override'; // this is required syntax highlighting import '@codingame/monaco-vscode-java-default-extension'; -import { LogLevel } from '@codingame/monaco-vscode-api'; -import { MonacoEditorLanguageClientWrapper, type WrapperConfig } from 'monaco-editor-wrapper'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; -import { eclipseJdtLsConfig } from '../config.js'; import helloJavaCode from '../../../resources/eclipse.jdt.ls/workspace/hello.java?raw'; +import { runExtendedClient } from '../../common/client/extendedClient.js'; +import { eclipseJdtLsConfig } from '../config.js'; -export const runEclipseJdtLsClient = () => { - const helloJavaUri = vscode.Uri.file(`${eclipseJdtLsConfig.basePath}/workspace/hello.java`); - const fileSystemProvider = new RegisteredFileSystemProvider(false); - fileSystemProvider.registerFile(new RegisteredMemoryFile(helloJavaUri, helloJavaCode)); - registerFileSystemOverlay(1, fileSystemProvider); - - const wrapperConfig: WrapperConfig = { - $type: 'extended', - htmlContainer: document.getElementById('monaco-editor-root')!, - logLevel: LogLevel.Debug, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride(), - }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'editor.guides.bracketPairsHorizontal': 'active', - 'editor.wordBasedSuggestions': 'off', - 'editor.experimental.asyncTokenization': true - }) - } - }, - editorAppConfig: { - codeResources: { - modified: { - text: helloJavaCode, - uri: `${eclipseJdtLsConfig.basePath}/workspace/hello.java` - } - }, - monacoWorkerFactory: configureDefaultWorkerFactory - }, - languageClientConfigs: { - configs: { - java: { - connection: { - options: { - $type: 'WebSocketUrl', - url: 'ws://localhost:30003/jdtls' - } - }, - clientOptions: { - documentSelector: ['java'], - workspaceFolder: { - index: 0, - name: 'workspace', - uri: vscode.Uri.parse(`${eclipseJdtLsConfig.basePath}/workspace`) - } - } - } - } - } - }; - - const wrapper = new MonacoEditorLanguageClientWrapper(); - - try { - document.querySelector('#button-start')?.addEventListener('click', async () => { - await wrapper.init(wrapperConfig); - - // open files, so the LS can pick it up - await vscode.workspace.openTextDocument(helloJavaUri); - - await wrapper.start(); - }); - document.querySelector('#button-dispose')?.addEventListener('click', async () => { - await wrapper.dispose(); - }); - } catch (e) { - console.error(e); - } +export const runEclipseJdtLsClient = async () => { + await runExtendedClient(eclipseJdtLsConfig, helloJavaCode); }; diff --git a/packages/examples/src/eclipse.jdt.ls/config.ts b/packages/examples/src/eclipse.jdt.ls/config.ts index 81fd7d00b..f56ef84ac 100644 --- a/packages/examples/src/eclipse.jdt.ls/config.ts +++ b/packages/examples/src/eclipse.jdt.ls/config.ts @@ -2,8 +2,12 @@ * Copyright (c) 2024 TypeFox and others. * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -export const eclipseJdtLsConfig = { + +import type { ExampleLsConfig } from '../common/client/extendedClient.js'; + +export const eclipseJdtLsConfig: ExampleLsConfig = { port: 30003, path: '/jdtls', - basePath: '/home/mlc/packages/examples/resources/eclipse.jdt.ls' + basePath: '/home/mlc/packages/examples/resources/eclipse.jdt.ls', + documentSelector: 'java' }; diff --git a/packages/examples/src/groovy/client/main.ts b/packages/examples/src/groovy/client/main.ts index 18a46724f..2c25e8a5b 100644 --- a/packages/examples/src/groovy/client/main.ts +++ b/packages/examples/src/groovy/client/main.ts @@ -3,73 +3,12 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; // this is required syntax highlighting import '@codingame/monaco-vscode-groovy-default-extension'; -import { LogLevel } from '@codingame/monaco-vscode-api'; -import { MonacoEditorLanguageClientWrapper, type WrapperConfig } from 'monaco-editor-wrapper'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; +import helloGroovyCode from '../../../resources/groovy/workspace/hello.groovy?raw'; +import { runExtendedClient } from '../../common/client/extendedClient.js'; import { groovyConfig } from '../config.js'; -const code = `package test.org; -import java.io.File; -File file = new File("E:/Example.txt"); -`; - -const wrapperConfig: WrapperConfig = { - $type: 'extended', - htmlContainer: document.getElementById('monaco-editor-root')!, - logLevel: LogLevel.Debug, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride(), - }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'editor.guides.bracketPairsHorizontal': 'active', - 'editor.wordBasedSuggestions': 'off', - 'editor.experimental.asyncTokenization': true - }) - } - }, - editorAppConfig: { - codeResources: { - modified: { - text: code, - uri: '/workspace/test.groovy' - } - }, - monacoWorkerFactory: configureDefaultWorkerFactory - }, - languageClientConfigs: { - configs: { - groovy: { - clientOptions: { - documentSelector: ['groovy'] - }, - connection: { - options: { - $type: 'WebSocketUrl', - url: `ws://localhost:${groovyConfig.port}${groovyConfig.path}` - } - } - } - } - } -}; - -export const runGroovyClient = () => { - const wrapper = new MonacoEditorLanguageClientWrapper(); - - try { - document.querySelector('#button-start')?.addEventListener('click', async () => { - await wrapper.initAndStart(wrapperConfig); - }); - document.querySelector('#button-dispose')?.addEventListener('click', async () => { - await wrapper.dispose(); - }); - } catch (e) { - console.error(e); - } +export const runGroovyClient = async () => { + await runExtendedClient(groovyConfig, helloGroovyCode); }; diff --git a/packages/examples/src/groovy/config.ts b/packages/examples/src/groovy/config.ts index e63344733..7fbcdba28 100644 --- a/packages/examples/src/groovy/config.ts +++ b/packages/examples/src/groovy/config.ts @@ -2,8 +2,12 @@ * Copyright (c) 2024 TypeFox and others. * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -export const groovyConfig = { + +import type { ExampleLsConfig } from '../common/client/extendedClient.js'; + +export const groovyConfig: ExampleLsConfig = { port: 30002, path: '/groovy', - basePath: '/home/gradle/mlc/packages/examples/resources/groovy' + basePath: '/home/gradle/mlc/packages/examples/resources/groovy', + documentSelector: 'groovy' }; diff --git a/packages/examples/src/json/client/client.ts b/packages/examples/src/json/client/client.ts new file mode 100644 index 000000000..5edf2f519 --- /dev/null +++ b/packages/examples/src/json/client/client.ts @@ -0,0 +1,14 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +// this is required syntax highlighting +import '@codingame/monaco-vscode-json-default-extension'; +import helloJsonCode from '../../../resources/json/workspace/hello.json?raw'; +import { runExtendedClient } from '../../common/client/extendedClient.js'; +import { jsontLsConfig } from './config.js'; + +export const runJsonWrapper = async () => { + await runExtendedClient(jsontLsConfig, helloJsonCode); +}; diff --git a/packages/examples/src/json/client/config.ts b/packages/examples/src/json/client/config.ts new file mode 100644 index 000000000..5fac6a68a --- /dev/null +++ b/packages/examples/src/json/client/config.ts @@ -0,0 +1,13 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2024 TypeFox and others. + * Licensed under the MIT License. See LICENSE in the package root for license information. + * ------------------------------------------------------------------------------------------ */ + +import type { ExampleLsConfig } from '../../common/client/extendedClient.js'; + +export const jsontLsConfig: ExampleLsConfig = { + port: 30000, + path: '/sampleServer', + basePath: '/home/mlc/packages/examples/resources/json', + documentSelector: 'json' +}; diff --git a/packages/examples/src/json/client/wrapperWs.ts b/packages/examples/src/json/client/wrapperWs.ts deleted file mode 100644 index be22447fc..000000000 --- a/packages/examples/src/json/client/wrapperWs.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* -------------------------------------------------------------------------------------------- - * Copyright (c) 2024 TypeFox and others. - * Licensed under the MIT License. See LICENSE in the package root for license information. - * ------------------------------------------------------------------------------------------ */ - -import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; -// this is required syntax highlighting -import '@codingame/monaco-vscode-json-default-extension'; -import { LogLevel } from '@codingame/monaco-vscode-api'; -import { MonacoEditorLanguageClientWrapper, type WrapperConfig } from 'monaco-editor-wrapper'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; - -const text = `{ - "$schema": "http://json.schemastore.org/coffeelint", - "line_endings": {"value": "unix"} -}`; - -export const buildJsonClientUserConfig = (htmlContainer?: HTMLElement): WrapperConfig => { - return { - $type: 'extended', - htmlContainer, - logLevel: LogLevel.Debug, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride(), - }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'editor.guides.bracketPairsHorizontal': 'active', - 'editor.lightbulb.enabled': 'On', - 'editor.wordBasedSuggestions': 'off', - 'editor.experimental.asyncTokenization': true - }) - } - }, - editorAppConfig: { - codeResources: { - modified: { - text, - uri: '/workspace/test.json', - } - }, - monacoWorkerFactory: configureDefaultWorkerFactory - }, - languageClientConfigs: { - configs: { - json: { - clientOptions: { - documentSelector: ['json'] - }, - connection: { - options: { - $type: 'WebSocketUrl', - url: 'ws://localhost:30000/sampleServer', - startOptions: { - onCall: () => { - console.log('Connected to socket.'); - }, - reportStatus: true - }, - stopOptions: { - onCall: () => { - console.log('Disconnected from socket.'); - }, - reportStatus: true - } - } - } - } - } - } - }; -}; - -export const runJsonWrapper = () => { - const wrapper = new MonacoEditorLanguageClientWrapper(); - - try { - document.querySelector('#button-start')?.addEventListener('click', async () => { - const config = buildJsonClientUserConfig(document.getElementById('monaco-editor-root')!); - await wrapper.initAndStart(config); - }); - document.querySelector('#button-dispose')?.addEventListener('click', async () => { - await wrapper.dispose(); - }); - } catch (e) { - console.error(e); - } -}; diff --git a/packages/examples/src/langium/langium-dsl/config/classicConfig.ts b/packages/examples/src/langium/langium-dsl/config/classicConfig.ts index d4cf7a8a3..9544aed8a 100644 --- a/packages/examples/src/langium/langium-dsl/config/classicConfig.ts +++ b/packages/examples/src/langium/langium-dsl/config/classicConfig.ts @@ -6,67 +6,83 @@ import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; import { LogLevel } from '@codingame/monaco-vscode-api'; import { MessageTransports } from 'vscode-languageclient'; -import type { Logger } from 'monaco-languageclient/tools'; -import { useWorkerFactory } from 'monaco-languageclient/workerFactory'; -import type { WrapperConfig } from 'monaco-editor-wrapper'; -import { defineDefaultWorkerLoaders } from 'monaco-editor-wrapper/workers/workerLoaders'; +import type { Logger } from 'monaco-languageclient/common'; +import type { MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import type { LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import { defineDefaultWorkerLoaders, useWorkerFactory } from 'monaco-languageclient/workerFactory'; import { LangiumMonarchContent } from './langium.monarch.js'; import code from '../../../../resources/langium/langium-dsl/example.langium?raw'; +import type { ExampleAppConfig } from '../../../common/client/utils.js'; +import type { EditorAppConfig } from 'monaco-languageclient/editorApp'; -export const setupLangiumClientClassic = async (params: { +export const setupLangiumClientClassic = (params: { worker: Worker messageTransports?: MessageTransports, -}): Promise => { +}): ExampleAppConfig => { + const workerLoaders = defineDefaultWorkerLoaders(); workerLoaders.TextMateWorker = undefined; - return { + + const vscodeApiConfig: MonacoVscodeApiConfig = { $type: 'classic', - htmlContainer: document.getElementById('monaco-editor-root')!, logLevel: LogLevel.Debug, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride() - } + htmlContainer: document.getElementById('monaco-editor-root')!, + serviceOverrides: { + ...getKeybindingsServiceOverride() }, - editorAppConfig: { - codeResources: { - modified: { - text: code, - uri: '/workspace/grammar.langium', - enforceLanguageId: 'langium' - } - }, - editorOptions: { - 'semanticHighlighting.enabled': true, - wordBasedSuggestions: 'off', - theme: 'vs-dark' - }, - languageDef: { - monarchLanguage: LangiumMonarchContent, - languageExtensionConfig: { id: 'langium' } + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'GitHub Dark High Contrast', + 'editor.guides.bracketPairsHorizontal': 'active', + 'editor.wordBasedSuggestions': 'off', + 'editor.experimental.asyncTokenization': true, + 'vitest.disableWorkspaceWarning': true + }) + }, + monacoWorkerFactory: (logger?: Logger) => { + useWorkerFactory({ + workerLoaders, + logger + }); + } + }; + + const languageClientConfig: LanguageClientConfig = { + clientOptions: { + documentSelector: ['langium'] + }, + connection: { + options: { + $type: 'WorkerDirect', + worker: params.worker }, - monacoWorkerFactory: (logger?: Logger) => { - useWorkerFactory({ - workerLoaders, - logger - }); + messageTransports: params.messageTransports + } + }; + + const editorAppConfig: EditorAppConfig = { + $type: vscodeApiConfig.$type, + codeResources: { + modified: { + text: code, + uri: '/workspace/grammar.langium', + enforceLanguageId: 'langium' } }, - languageClientConfigs: { - configs: { - langium: { - clientOptions: { - documentSelector: ['langium'] - }, - connection: { - options: { - $type: 'WorkerDirect', - worker: params.worker - }, - messageTransports: params.messageTransports - } - } - } + editorOptions: { + 'semanticHighlighting.enabled': true, + wordBasedSuggestions: 'off', + theme: 'vs-dark' + }, + languageDef: { + monarchLanguage: LangiumMonarchContent, + languageExtensionConfig: { id: 'langium' } } }; + + return { + editorAppConfig, + vscodeApiConfig, + languageClientConfig + }; }; diff --git a/packages/examples/src/langium/langium-dsl/config/extendedConfig.ts b/packages/examples/src/langium/langium-dsl/config/extendedConfig.ts index 7572ef2b8..e7f9a3b04 100644 --- a/packages/examples/src/langium/langium-dsl/config/extendedConfig.ts +++ b/packages/examples/src/langium/langium-dsl/config/extendedConfig.ts @@ -7,39 +7,42 @@ import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings- import { LogLevel } from '@codingame/monaco-vscode-api'; import '../../../../resources/vsix/github-vscode-theme.vsix'; import { MessageTransports } from 'vscode-languageclient'; -import type { WrapperConfig } from 'monaco-editor-wrapper'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; +import type { MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import type { LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; import langiumLanguageConfig from './langium.configuration.json?raw'; import langiumTextmateGrammar from './langium.tmLanguage.json?raw'; import text from '../../../../resources/langium/langium-dsl//example.langium?raw'; +import type { ExampleAppConfig } from '../../../common/client/utils.js'; +import type { EditorAppConfig } from 'monaco-languageclient/editorApp'; -export const setupLangiumClientExtended = async (params: { +export const setupLangiumClientExtended = (params: { worker: Worker messageTransports?: MessageTransports, -}): Promise => { +}): ExampleAppConfig => { const extensionFilesOrContents = new Map(); // vite build is easier with string content extensionFilesOrContents.set('/langium-configuration.json', langiumLanguageConfig); extensionFilesOrContents.set('/langium-grammar.json', langiumTextmateGrammar); - return { + + const vscodeApiConfig: MonacoVscodeApiConfig = { $type: 'extended', - htmlContainer: document.getElementById('monaco-editor-root')!, logLevel: LogLevel.Debug, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride() - }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'GitHub Dark High Contrast', - 'editor.guides.bracketPairsHorizontal': 'active', - 'editor.wordBasedSuggestions': 'off', - 'editor.experimental.asyncTokenization': true, - 'vitest.disableWorkspaceWarning': true - }) - } + htmlContainer: document.getElementById('monaco-editor-root')!, + serviceOverrides: { + ...getKeybindingsServiceOverride() + }, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'GitHub Dark High Contrast', + 'editor.guides.bracketPairsHorizontal': 'active', + 'editor.wordBasedSuggestions': 'off', + 'editor.experimental.asyncTokenization': true, + 'vitest.disableWorkspaceWarning': true + }) }, + monacoWorkerFactory: configureDefaultWorkerFactory, extensions: [{ config: { name: 'langium-example', @@ -63,31 +66,35 @@ export const setupLangiumClientExtended = async (params: { } }, filesOrContents: extensionFilesOrContents - }], - editorAppConfig: { - codeResources: { - modified: { - text, - uri: '/workspace/grammar.langium' - } - }, - monacoWorkerFactory: configureDefaultWorkerFactory + }] + }; + + const languageClientConfig: LanguageClientConfig = { + clientOptions: { + documentSelector: ['langium'] }, - languageClientConfigs: { - configs: { - langium: { - clientOptions: { - documentSelector: ['langium'] - }, - connection: { - options: { - $type: 'WorkerDirect', - worker: params.worker - }, - messageTransports: params.messageTransports - } - } + connection: { + options: { + $type: 'WorkerDirect', + worker: params.worker + }, + messageTransports: params.messageTransports + } + }; + + const editorAppConfig: EditorAppConfig = { + $type: vscodeApiConfig.$type, + codeResources: { + modified: { + text, + uri: '/workspace/grammar.langium' } } }; + + return { + editorAppConfig, + vscodeApiConfig, + languageClientConfig + }; }; diff --git a/packages/examples/src/langium/langium-dsl/wrapperLangium.ts b/packages/examples/src/langium/langium-dsl/wrapperLangium.ts index 4893338ff..25728977e 100644 --- a/packages/examples/src/langium/langium-dsl/wrapperLangium.ts +++ b/packages/examples/src/langium/langium-dsl/wrapperLangium.ts @@ -4,16 +4,19 @@ * ------------------------------------------------------------------------------------------ */ import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageclient/browser.js'; -import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; +import { delayExecution } from 'monaco-languageclient/common'; +import { EditorApp } from 'monaco-languageclient/editorApp'; import { setupLangiumClientExtended } from './config/extendedConfig.js'; import { setupLangiumClientClassic } from './config/classicConfig.js'; -import { delayExecution, disableElement } from '../../common/client/utils.js'; +import { disableElement, type ExampleAppConfig } from '../../common/client/utils.js'; import text from '../../../resources/langium/langium-dsl/example.langium?raw'; import workerUrl from './worker/langium-server?worker&url'; +import { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; +import { LanguageClientWrapper } from 'monaco-languageclient/lcwrapper'; export const runLangiumDslWrapper = async (extendedMode: boolean) => { try { - let wrapper: MonacoEditorLanguageClientWrapper | undefined; + let editorApp: EditorApp | undefined; const loadLangiumWorker = () => { console.log(`Langium worker URL: ${workerUrl}`); @@ -24,7 +27,7 @@ export const runLangiumDslWrapper = async (extendedMode: boolean) => { }; const checkStarted = () => { - if (wrapper?.isStarted() ?? false) { + if (editorApp?.isStarted() ?? false) { alert('Editor was already started!\nPlease reload the page to test the alternative editor.'); return true; } @@ -42,24 +45,32 @@ export const runLangiumDslWrapper = async (extendedMode: boolean) => { console.log('Received message from worker:', message); }); + let appConfig: ExampleAppConfig; if (extendedMode) { - const config = await setupLangiumClientExtended({ + appConfig = setupLangiumClientExtended({ worker, messageTransports: { reader, writer } }); - wrapper = new MonacoEditorLanguageClientWrapper(); - await wrapper.initAndStart(config); } else { - const config = await setupLangiumClientClassic({ + appConfig = setupLangiumClientClassic({ worker, messageTransports: { reader, writer } }); - wrapper = new MonacoEditorLanguageClientWrapper(); - await wrapper.initAndStart(config); } + // perform global init + const apiWrapper = new MonacoVscodeApiWrapper(appConfig.vscodeApiConfig); + await apiWrapper.init(); + + // init language client + const lcWrapper = new LanguageClientWrapper(appConfig.languageClientConfig); + await lcWrapper.start(); + + // run editorApp + editorApp = new EditorApp(appConfig.editorAppConfig); + await editorApp.start(appConfig.vscodeApiConfig.htmlContainer!); await delayExecution(1000); - await wrapper.updateCodeResources({ + await editorApp.updateCodeResources({ modified: { text: `// modified file\n\n${text}`, uri: '/workspace/mod.langium', @@ -69,10 +80,9 @@ export const runLangiumDslWrapper = async (extendedMode: boolean) => { }; const disposeEditor = async () => { - if (!wrapper) return; - wrapper.reportStatus(); - await wrapper.dispose(); - wrapper = undefined; + editorApp?.reportStatus(); + await editorApp?.dispose(); + editorApp = undefined; disableElement('button-start', false); }; diff --git a/packages/examples/src/langium/statemachine/config/wrapperStatemachineConfig.ts b/packages/examples/src/langium/statemachine/config/wrapperStatemachineConfig.ts index c6fc2bca3..5972b4886 100644 --- a/packages/examples/src/langium/statemachine/config/wrapperStatemachineConfig.ts +++ b/packages/examples/src/langium/statemachine/config/wrapperStatemachineConfig.ts @@ -6,65 +6,62 @@ import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; import getLifecycleServiceOverride from '@codingame/monaco-vscode-lifecycle-service-override'; import getLocalizationServiceOverride from '@codingame/monaco-vscode-localization-service-override'; -import { createDefaultLocaleConfiguration } from 'monaco-languageclient/vscode/services'; import { LogLevel } from '@codingame/monaco-vscode-api'; import { MessageTransports } from 'vscode-languageclient'; -import type { CodeContent, LanguageClientConfigs, WrapperConfig } from 'monaco-editor-wrapper'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; +import { createDefaultLocaleConfiguration } from 'monaco-languageclient/vscodeApiLocales'; +import type { MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import type { LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; +import type { CodeContent, EditorAppConfig } from 'monaco-languageclient/editorApp'; // cannot be imported with assert as json contains comments import statemachineLanguageConfig from './language-configuration.json?raw'; import responseStatemachineTm from '../syntaxes/statemachine.tmLanguage.json?raw'; +import type { ExampleAppConfig } from '../../../common/client/utils.js'; export const createLangiumGlobalConfig = (params: { languageServerId: string, - useLanguageClient: boolean, codeContent: CodeContent, - worker?: Worker, + worker: Worker, messagePort?: MessagePort, messageTransports?: MessageTransports, htmlContainer: HTMLElement -}): WrapperConfig => { +}): ExampleAppConfig => { const extensionFilesOrContents = new Map(); extensionFilesOrContents.set(`/${params.languageServerId}-statemachine-configuration.json`, statemachineLanguageConfig); extensionFilesOrContents.set(`/${params.languageServerId}-statemachine-grammar.json`, responseStatemachineTm); - const languageClientConfigs: LanguageClientConfigs | undefined = params.useLanguageClient && params.worker ? { - configs: { - statemachine: { - clientOptions: { - documentSelector: ['statemachine'] - }, - connection: { - options: { - $type: 'WorkerDirect', - worker: params.worker, - messagePort: params.messagePort, - }, - messageTransports: params.messageTransports - } - } + const languageClientConfig: LanguageClientConfig = { + clientOptions: { + documentSelector: ['statemachine'] + }, + connection: { + options: { + $type: 'WorkerDirect', + worker: params.worker, + messagePort: params.messagePort, + }, + messageTransports: params.messageTransports } - } : undefined; + }; - return { + const vscodeApiConfig: MonacoVscodeApiConfig = { $type: 'extended', htmlContainer: params.htmlContainer, logLevel: LogLevel.Debug, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride(), - ...getLifecycleServiceOverride(), - ...getLocalizationServiceOverride(createDefaultLocaleConfiguration()), - }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'editor.guides.bracketPairsHorizontal': 'active', - 'editor.wordBasedSuggestions': 'off', - 'editor.experimental.asyncTokenization': true - }) - }, + serviceOverrides: { + ...getKeybindingsServiceOverride(), + ...getLifecycleServiceOverride(), + ...getLocalizationServiceOverride(createDefaultLocaleConfiguration()), + }, + monacoWorkerFactory: configureDefaultWorkerFactory, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.guides.bracketPairsHorizontal': 'active', + 'editor.wordBasedSuggestions': 'off', + 'editor.experimental.asyncTokenization': true + }) }, extensions: [{ config: { @@ -89,13 +86,19 @@ export const createLangiumGlobalConfig = (params: { } }, filesOrContents: extensionFilesOrContents - }], - editorAppConfig: { - codeResources: { - modified: params.codeContent - }, - monacoWorkerFactory: configureDefaultWorkerFactory - }, - languageClientConfigs + }] + }; + + const editorAppConfig: EditorAppConfig = { + $type: vscodeApiConfig.$type, + codeResources: { + modified: params.codeContent + } + }; + + return { + editorAppConfig, + vscodeApiConfig, + languageClientConfig }; }; diff --git a/packages/examples/src/langium/statemachine/launcher.ts b/packages/examples/src/langium/statemachine/launcher.ts index 858efad31..bddc06f12 100644 --- a/packages/examples/src/langium/statemachine/launcher.ts +++ b/packages/examples/src/langium/statemachine/launcher.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import { initLocaleLoader } from 'monaco-editor-wrapper/vscode/locale'; +import { initLocaleLoader } from 'monaco-languageclient/vscodeApiLocales'; await initLocaleLoader(); const { runStatemachineWrapper } = await import('./main.js'); diff --git a/packages/examples/src/langium/statemachine/main-react.tsx b/packages/examples/src/langium/statemachine/main-react.tsx index 921bf234c..b79756d24 100644 --- a/packages/examples/src/langium/statemachine/main-react.tsx +++ b/packages/examples/src/langium/statemachine/main-react.tsx @@ -6,7 +6,7 @@ import React, { StrictMode, useEffect, useState } from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageclient/browser.js'; -import type { TextContents } from 'monaco-editor-wrapper'; +import type { TextContents } from 'monaco-languageclient/editorApp'; import { MonacoEditorReactComp } from '@typefox/monaco-editor-react'; import { createLangiumGlobalConfig } from './config/wrapperStatemachineConfig.js'; import { loadStatemachineWorkerRegular } from './main.js'; @@ -20,9 +20,8 @@ export const runStatemachineReact = async () => { reader.listen((message) => { console.log('Received message from worker:', message); }); - const wrapperConfig = createLangiumGlobalConfig({ + const appConfig = createLangiumGlobalConfig({ languageServerId: 'react', - useLanguageClient: true, codeContent: { text, uri: '/workspace/example.statemachine' @@ -62,7 +61,8 @@ export const runStatemachineReact = async () => {
Debug:
{testState} diff --git a/packages/examples/src/langium/statemachine/main.ts b/packages/examples/src/langium/statemachine/main.ts index 359120b0b..f3349f77c 100644 --- a/packages/examples/src/langium/statemachine/main.ts +++ b/packages/examples/src/langium/statemachine/main.ts @@ -5,22 +5,26 @@ import * as vscode from 'vscode'; import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageclient/browser.js'; -import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; +import { EditorApp } from 'monaco-languageclient/editorApp'; import { createLangiumGlobalConfig } from './config/wrapperStatemachineConfig.js'; import workerUrl from './worker/statemachine-server?worker&url'; import workerPortUrl from './worker/statemachine-server-port?worker&url'; import text from '../../../resources/langium/statemachine/example.statemachine?raw'; import textMod from '../../../resources/langium/statemachine/example-mod.statemachine?raw'; -import { delayExecution, disableElement } from '../../common/client/utils.js'; +import { disableElement } from '../../common/client/utils.js'; +import { delayExecution } from 'monaco-languageclient/common'; +import { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; +import { LanguageClientWrapper } from 'monaco-languageclient/lcwrapper'; -const wrapper = new MonacoEditorLanguageClientWrapper(); -const wrapper2 = new MonacoEditorLanguageClientWrapper(); +let editorApp: EditorApp | undefined; +let editorApp2: EditorApp | undefined; +let lcWrapper: LanguageClientWrapper; const startEditor = async () => { disableElement('button-start', true); disableElement('button-dispose', false); - if (wrapper.isStarted() && wrapper2.isStarted()) { + if (editorApp?.isStarted() === true || editorApp2?.isStarted() === true) { alert('Editor was already started!'); return; } @@ -43,39 +47,49 @@ const startEditor = async () => { }); // the configuration does not contain any text content - const langiumGlobalConfig = createLangiumGlobalConfig({ + const appConfig = createLangiumGlobalConfig({ languageServerId: 'first', codeContent: { text, uri: '/workspace/example.statemachine' }, - useLanguageClient: true, worker: stateMachineWorkerPort, messagePort: channel.port1, messageTransports: { reader, writer }, htmlContainer: document.getElementById('monaco-editor-root')! }); - await wrapper.initAndStart(langiumGlobalConfig); + editorApp = new EditorApp(appConfig.editorAppConfig); - wrapper.updateCodeResources({ + // perform global init + const apiWrapper = new MonacoVscodeApiWrapper(appConfig.vscodeApiConfig); + await apiWrapper.init(); + + // init language client + lcWrapper = new LanguageClientWrapper(appConfig.languageClientConfig); + await lcWrapper.start(); + + // run editorApp + await editorApp.start(appConfig.vscodeApiConfig.htmlContainer!); + + editorApp.updateCodeResources({ modified: { text, uri: '/workspace/statemachine-mod.statemachine' } }); - // start the second wrapper without any languageclient config + // start the second editorApp without any languageclient config // => they share the language server and both text contents have different uris - const langiumGlobalConfig2 = createLangiumGlobalConfig({ - languageServerId: 'second', - useLanguageClient: false, - codeContent: { - text: textMod, - uri: '/workspace/example-mod.statemachine' - }, - htmlContainer: document.getElementById('monaco-editor-root2')! - }); - await wrapper2.initAndStart(langiumGlobalConfig2); + const appConfig2 = appConfig; + appConfig2.editorAppConfig.codeResources!.modified = { + text: textMod, + uri: '/workspace/example-mod.statemachine' + }; + appConfig2.vscodeApiConfig.htmlContainer = document.getElementById('monaco-editor-root2')!; + editorApp2 = new EditorApp(appConfig2.editorAppConfig); + + // run editorApp + await editorApp2.start(appConfig2.vscodeApiConfig.htmlContainer); vscode.commands.getCommands().then((x) => { console.log('Currently registered # of vscode commands: ' + x.length); @@ -83,7 +97,7 @@ const startEditor = async () => { await delayExecution(1000); - wrapper.updateCodeResources({ + editorApp.updateCodeResources({ modified: { text: `// modified file\n\n${text}`, uri: '/workspace/statemachine-mod2.statemachine' @@ -95,13 +109,15 @@ const disposeEditor = async () => { disableElement('button-start', false); disableElement('button-dispose', true); - wrapper.reportStatus(); - await wrapper.dispose(); - console.log(wrapper.reportStatus().join('\n')); + lcWrapper.dispose(); + + editorApp?.reportStatus(); + await editorApp?.dispose(); + console.log(editorApp?.reportStatus().join('\n')); - wrapper2.reportStatus(); - await wrapper2.dispose(); - console.log(wrapper2.reportStatus().join('\n')); + editorApp2?.reportStatus(); + await editorApp2?.dispose(); + console.log(editorApp2?.reportStatus().join('\n')); }; export const runStatemachineWrapper = async () => { diff --git a/packages/examples/src/multi/config.ts b/packages/examples/src/multi/config.ts index 81e355591..fe395822e 100644 --- a/packages/examples/src/multi/config.ts +++ b/packages/examples/src/multi/config.ts @@ -4,8 +4,8 @@ * ------------------------------------------------------------------------------------------ */ import * as vscode from 'vscode'; -import { MonacoLanguageClient } from 'monaco-languageclient'; -import type { LanguageClientConfig } from 'monaco-languageclient/wrapper'; +import type { LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import type { BaseLanguageClient } from 'vscode-languageclient/browser.js'; export const createJsonLanguageClientConfig: () => LanguageClientConfig = () => { return { @@ -38,7 +38,7 @@ export const createPythonLanguageClientConfig: () => LanguageClientConfig = () = authorization: 'UserAuth' }, startOptions: { - onCall: (languageClient?: MonacoLanguageClient) => { + onCall: (languageClient?: BaseLanguageClient) => { setTimeout(() => { ['pyright.restartserver', 'pyright.organizeimports'].forEach((cmdName) => { vscode.commands.registerCommand(cmdName, (...args: unknown[]) => { diff --git a/packages/examples/src/multi/twoLanguageClients.ts b/packages/examples/src/multi/twoLanguageClients.ts index 8ba9def70..84b11ec95 100644 --- a/packages/examples/src/multi/twoLanguageClients.ts +++ b/packages/examples/src/multi/twoLanguageClients.ts @@ -9,10 +9,12 @@ import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings- import '@codingame/monaco-vscode-json-default-extension'; import '@codingame/monaco-vscode-python-default-extension'; import { LogLevel } from '@codingame/monaco-vscode-api'; -import { MonacoEditorLanguageClientWrapper, type WrapperConfig } from 'monaco-editor-wrapper'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; +import { EditorApp, type EditorAppConfig } from 'monaco-languageclient/editorApp'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; import { disableElement } from '../common/client/utils.js'; import { createJsonLanguageClientConfig, createPythonLanguageClientConfig } from './config.js'; +import { MonacoVscodeApiWrapper, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import { LanguageClientsManager } from 'monaco-languageclient/lcwrapper'; export const runMultipleLanguageClientsExample = async () => { disableElement('button-flip', true); @@ -31,64 +33,63 @@ print("Hello Moon!") let currentText = textJson; let currenFileExt = 'json'; - const wrapperConfig: WrapperConfig = { - id: '42', + const htmlContainer = document.getElementById('monaco-editor-root')!; + const vscodeApiConfig: MonacoVscodeApiConfig = { $type: 'extended', - htmlContainer: document.getElementById('monaco-editor-root')!, + htmlContainer, logLevel: LogLevel.Debug, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride() - }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'editor.wordBasedSuggestions': 'off', - 'editor.experimental.asyncTokenization': true - }) - } + serviceOverrides: { + ...getKeybindingsServiceOverride() }, - editorAppConfig: { - codeResources: { - modified: { - text: currentText, - uri: `/workspace/example.${currenFileExt}` - } - }, - monacoWorkerFactory: configureDefaultWorkerFactory + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.wordBasedSuggestions': 'off', + 'editor.experimental.asyncTokenization': true + }) }, - languageClientConfigs: { - configs: { - json: createJsonLanguageClientConfig(), - python: createPythonLanguageClientConfig() + monacoWorkerFactory: configureDefaultWorkerFactory + }; + + const editorAppConfig: EditorAppConfig = { + $type: vscodeApiConfig.$type, + id: '42', + codeResources: { + modified: { + text: currentText, + uri: `/workspace/example.${currenFileExt}` } } }; - const wrapper = new MonacoEditorLanguageClientWrapper(); + // perform global init + const apiWrapper = new MonacoVscodeApiWrapper(vscodeApiConfig); + await apiWrapper.init(); + + const lcManager = new LanguageClientsManager(); + const languageClientConfigs = { + configs: { + json: createJsonLanguageClientConfig(), + python: createPythonLanguageClientConfig() + } + }; + + const editorApp = new EditorApp(editorAppConfig); document.querySelector('#button-start')?.addEventListener('click', async () => { try { disableElement('button-start', true); disableElement('button-flip', false); - disableElement('checkbox-extlc', true); - - const externalLc = (document.getElementById('checkbox-extlc') as HTMLInputElement).checked; - wrapperConfig.languageClientConfigs!.automaticallyInit = !externalLc; - wrapperConfig.languageClientConfigs!.automaticallyStart = !externalLc; - wrapperConfig.languageClientConfigs!.automaticallyDispose = !externalLc; - await wrapper.initAndStart(wrapperConfig); - if (wrapperConfig.editorAppConfig?.codeResources?.modified !== undefined) { - wrapperConfig.editorAppConfig.codeResources.modified.text = currentText; - wrapperConfig.editorAppConfig.codeResources.modified.uri = `/workspace/example.${currenFileExt}`; + await editorApp.start(htmlContainer); + if (editorAppConfig.codeResources?.modified !== undefined) { + editorAppConfig.codeResources.modified.text = currentText; + editorAppConfig.codeResources.modified.uri = `/workspace/example.${currenFileExt}`; } - // init language clients after start - if (externalLc) { - wrapper.initLanguageClients(); - await wrapper.startLanguageClients(); - } + // init and start language clients after start + await lcManager.setConfigs(languageClientConfigs); + await lcManager.start(); } catch (e) { console.error(e); } @@ -98,18 +99,13 @@ print("Hello Moon!") disableElement('button-dispose', true); disableElement('button-start', false); - const externalLc = (document.getElementById('checkbox-extlc')! as HTMLInputElement).checked; - - await wrapper.dispose(); - - if (externalLc) { - wrapper.disposeLanguageClients(); - } + await editorApp.dispose(); + await lcManager.dispose(); }); document.querySelector('#button-flip')?.addEventListener('click', async () => { currentText = currentText === textJson ? textPython : textJson; currenFileExt = currenFileExt === 'json' ? 'py' : 'json'; - wrapper.updateCodeResources({ + editorApp.updateCodeResources({ modified: { text: currentText, uri: `/workspace/example.${currenFileExt}` diff --git a/packages/examples/src/node.ts b/packages/examples/src/node.ts index 49c75ddc4..7da8a1e68 100644 --- a/packages/examples/src/node.ts +++ b/packages/examples/src/node.ts @@ -6,6 +6,6 @@ /* server side export only */ export * from './common/node/server-commons.js'; export * from './common/node/language-server-runner.js'; -export * from './json/server/json-server.js'; -export * from './json/server/main.js'; -export * from './python/server/main.js'; +// export * from './json/server/json-server.js'; +// export * from './json/server/main.js'; +// export * from './python/server/main.js'; diff --git a/packages/examples/src/python/client/config.ts b/packages/examples/src/python/client/config.ts index f6e157d6a..66323e247 100644 --- a/packages/examples/src/python/client/config.ts +++ b/packages/examples/src/python/client/config.ts @@ -21,13 +21,14 @@ import getTestingServiceOverride from '@codingame/monaco-vscode-testing-service- import getBannerServiceOverride from '@codingame/monaco-vscode-view-banner-service-override'; import getStatusBarServiceOverride from '@codingame/monaco-vscode-view-status-bar-service-override'; import getTitleBarServiceOverride from '@codingame/monaco-vscode-view-title-bar-service-override'; -import type { WrapperConfig } from 'monaco-editor-wrapper'; -import { defaultHtmlAugmentationInstructions, defaultViewsInit } from 'monaco-editor-wrapper/vscode/services'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; -import { MonacoLanguageClient } from 'monaco-languageclient'; -import { createUrl } from 'monaco-languageclient/tools'; -import { createDefaultLocaleConfiguration } from 'monaco-languageclient/vscode/services'; +import type { EditorAppConfig } from 'monaco-languageclient/editorApp'; +import { createUrl } from 'monaco-languageclient/common'; +import type { LanguageClientConfig } from 'monaco-languageclient/lcwrapper'; +import { createDefaultLocaleConfiguration } from 'monaco-languageclient/vscodeApiLocales'; +import { defaultHtmlAugmentationInstructions, defaultViewsInit, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; import * as vscode from 'vscode'; +import type { BaseLanguageClient } from 'vscode-languageclient/browser.js'; import { toSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'; import badPyCode from '../../../resources/python/bad.py?raw'; import helloPyCode from '../../../resources/python/hello.py?raw'; @@ -67,7 +68,7 @@ export const createDefaultConfigParams = (homeDir: string, htmlContainer?: HTMLE fileSystemProvider.registerFile(new RegisteredMemoryFile(files.get('hello.py')!.uri, helloPyCode)); fileSystemProvider.registerFile(new RegisteredMemoryFile(files.get('hello2.py')!.uri, hello2PyCode)); fileSystemProvider.registerFile(new RegisteredMemoryFile(files.get('bad.py')!.uri, badPyCode)); - fileSystemProvider.registerFile(new RegisteredMemoryFile(configParams.workspaceFile, createDefaultWorkspaceContent(configParams.workspaceRoot))); + fileSystemProvider.registerFile(new RegisteredMemoryFile(configParams.workspaceFile, createDefaultWorkspaceContent(workspaceRoot))); fileSystemProvider.registerFile(createDebugLaunchConfigFile(workspaceRoot, configParams.languageId)); registerFileSystemOverlay(1, fileSystemProvider); @@ -75,11 +76,13 @@ export const createDefaultConfigParams = (homeDir: string, htmlContainer?: HTMLE }; export type PythonAppConfig = { - wrapperConfig: WrapperConfig; + languageClientConfig: LanguageClientConfig; + vscodeApiConfig: MonacoVscodeApiConfig; + editorAppConfig: EditorAppConfig; configParams: ConfigParams; } -export const createWrapperConfig = (): PythonAppConfig => { +export const createPythonAppConfig = (): PythonAppConfig => { const configParams = createDefaultConfigParams('/home/mlc', document.body); const url = createUrl({ @@ -96,101 +99,65 @@ export const createWrapperConfig = (): PythonAppConfig => { const reader = new WebSocketMessageReader(iWebSocket); const writer = new WebSocketMessageWriter(iWebSocket); - const wrapperConfig: WrapperConfig = { + const vscodeApiConfig: MonacoVscodeApiConfig = { $type: 'extended', htmlContainer: configParams.htmlContainer, logLevel: LogLevel.Debug, - languageClientConfigs: { - configs: { - python: { - name: 'Python Language Server Example', - connection: { - options: { - $type: 'WebSocketDirect', - webSocket: webSocket, - startOptions: { - onCall: (languageClient?: MonacoLanguageClient) => { - setTimeout(() => { - ['pyright.restartserver', 'pyright.organizeimports'].forEach((cmdName) => { - vscode.commands.registerCommand(cmdName, (...args: unknown[]) => { - languageClient?.sendRequest('workspace/executeCommand', { command: cmdName, arguments: args }); - }); - }); - }, 250); - }, - reportStatus: true, - } - }, - messageTransports: { reader, writer } - }, - clientOptions: { - documentSelector: [configParams.languageId], - workspaceFolder: { - index: 0, - name: configParams.workspaceRoot, - uri: vscode.Uri.parse(configParams.workspaceRoot) - }, - } - } - } + serviceOverrides: { + ...getKeybindingsServiceOverride(), + ...getLifecycleServiceOverride(), + ...getLocalizationServiceOverride(createDefaultLocaleConfiguration()), + ...getBannerServiceOverride(), + ...getStatusBarServiceOverride(), + ...getTitleBarServiceOverride(), + ...getExplorerServiceOverride(), + ...getRemoteAgentServiceOverride(), + ...getEnvironmentServiceOverride(), + ...getSecretStorageServiceOverride(), + ...getStorageServiceOverride(), + ...getSearchServiceOverride(), + ...getDebugServiceOverride(), + ...getTestingServiceOverride(), + ...getPreferencesServiceOverride() }, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride(), - ...getLifecycleServiceOverride(), - ...getLocalizationServiceOverride(createDefaultLocaleConfiguration()), - ...getBannerServiceOverride(), - ...getStatusBarServiceOverride(), - ...getTitleBarServiceOverride(), - ...getExplorerServiceOverride(), - ...getRemoteAgentServiceOverride(), - ...getEnvironmentServiceOverride(), - ...getSecretStorageServiceOverride(), - ...getStorageServiceOverride(), - ...getSearchServiceOverride(), - ...getDebugServiceOverride(), - ...getTestingServiceOverride(), - ...getPreferencesServiceOverride() - }, - viewsConfig: { - viewServiceType: 'ViewsService', - htmlAugmentationInstructions: defaultHtmlAugmentationInstructions, - viewsInitFunc: defaultViewsInit - }, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'editor.guides.bracketPairsHorizontal': 'active', - 'editor.wordBasedSuggestions': 'off', - 'editor.experimental.asyncTokenization': true, - 'debug.toolBarLocation': 'docked' - }) + viewsConfig: { + viewServiceType: 'ViewsService', + htmlAugmentationInstructions: defaultHtmlAugmentationInstructions, + viewsInitFunc: defaultViewsInit + }, + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'editor.guides.bracketPairsHorizontal': 'active', + 'editor.wordBasedSuggestions': 'off', + 'editor.experimental.asyncTokenization': true, + 'debug.toolBarLocation': 'docked' + }) + }, + workspaceConfig: { + enableWorkspaceTrust: true, + windowIndicator: { + label: 'mlc-python-example', + tooltip: '', + command: '' }, - workspaceConfig: { - enableWorkspaceTrust: true, - windowIndicator: { - label: 'mlc-python-example', - tooltip: '', - command: '' - }, - workspaceProvider: { - trusted: true, - async open() { - window.open(window.location.href); - return true; - }, - workspace: { - workspaceUri: configParams.workspaceFile - } - }, - configurationDefaults: { - 'window.title': 'mlc-python-example${separator}${dirty}${activeEditorShort}' + workspaceProvider: { + trusted: true, + async open() { + window.open(window.location.href); + return true; }, - productConfiguration: { - nameShort: 'mlc-python-example', - nameLong: 'mlc-python-example' + workspace: { + workspaceUri: configParams.workspaceFile } }, + configurationDefaults: { + 'window.title': 'mlc-python-example${separator}${dirty}${activeEditorShort}' + }, + productConfiguration: { + nameShort: 'mlc-python-example', + nameLong: 'mlc-python-example' + } }, extensions: [ { @@ -205,13 +172,48 @@ export const createWrapperConfig = (): PythonAppConfig => { }, provideDebuggerExtensionConfig(configParams) ], - editorAppConfig: { - monacoWorkerFactory: configureDefaultWorkerFactory + monacoWorkerFactory: configureDefaultWorkerFactory + }; + + const languageClientConfig: LanguageClientConfig = { + name: 'Python Language Server Example', + connection: { + options: { + $type: 'WebSocketDirect', + webSocket: webSocket, + startOptions: { + onCall: (languageClient?: BaseLanguageClient) => { + setTimeout(() => { + ['pyright.restartserver', 'pyright.organizeimports'].forEach((cmdName) => { + vscode.commands.registerCommand(cmdName, (...args: unknown[]) => { + languageClient?.sendRequest('workspace/executeCommand', { command: cmdName, arguments: args }); + }); + }); + }, 250); + }, + reportStatus: true, + } + }, + messageTransports: { reader, writer } + }, + clientOptions: { + documentSelector: [configParams.languageId], + workspaceFolder: { + index: 0, + name: configParams.workspaceRoot, + uri: vscode.Uri.parse(configParams.workspaceRoot) + }, } }; + const editorAppConfig: EditorAppConfig = { + $type: vscodeApiConfig.$type + }; + return { - wrapperConfig, + vscodeApiConfig, + languageClientConfig, + editorAppConfig, configParams: configParams }; }; diff --git a/packages/examples/src/python/client/main.ts b/packages/examples/src/python/client/main.ts index 28798fece..38e3df066 100644 --- a/packages/examples/src/python/client/main.ts +++ b/packages/examples/src/python/client/main.ts @@ -3,32 +3,41 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import * as vscode from 'vscode'; import { type RegisterLocalProcessExtensionResult } from '@codingame/monaco-vscode-api/extensions'; -import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; -import { createWrapperConfig } from './config.js'; +import { EditorApp } from 'monaco-languageclient/editorApp'; +import { LanguageClientWrapper } from 'monaco-languageclient/lcwrapper'; +import { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; +import * as vscode from 'vscode'; import { configureDebugging } from '../../debugger/client/debugger.js'; +import { createPythonAppConfig } from './config.js'; export const runPythonWrapper = async () => { - const appConfig = createWrapperConfig(); - const wrapper = new MonacoEditorLanguageClientWrapper(); + const appConfig = createPythonAppConfig(); + + // perform global init + const apiWrapper = new MonacoVscodeApiWrapper(appConfig.vscodeApiConfig); + await apiWrapper.init(); + + const lcWrapper = new LanguageClientWrapper(appConfig.languageClientConfig); - if (wrapper.isStarted()) { + const editorApp = new EditorApp(appConfig.editorAppConfig); + + if (editorApp.isStarted()) { console.warn('Editor was already started!'); } else { - await wrapper.init(appConfig.wrapperConfig); - - const result = wrapper.getExtensionRegisterResult('mlc-python-example') as RegisterLocalProcessExtensionResult; + const result = apiWrapper.getExtensionRegisterResult('mlc-python-example') as RegisterLocalProcessExtensionResult; result.setAsDefaultApi(); - const initResult = wrapper.getExtensionRegisterResult('debugger-py-client') as RegisterLocalProcessExtensionResult | undefined; + const initResult = apiWrapper.getExtensionRegisterResult('debugger-py-client') as RegisterLocalProcessExtensionResult | undefined; if (initResult !== undefined) { configureDebugging(await initResult.getApi(), appConfig.configParams); } + await lcWrapper.start(); + await vscode.commands.executeCommand('workbench.view.explorer'); await vscode.window.showTextDocument(appConfig.configParams.files.get('hello2.py')!.uri); - await wrapper.start(); + await editorApp.start(appConfig.vscodeApiConfig.htmlContainer!); } }; diff --git a/packages/examples/src/python/client/reactPython.tsx b/packages/examples/src/python/client/reactPython.tsx index 58af0fb00..e440814ea 100644 --- a/packages/examples/src/python/client/reactPython.tsx +++ b/packages/examples/src/python/client/reactPython.tsx @@ -3,23 +3,23 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import * as vscode from 'vscode'; import { type RegisterLocalProcessExtensionResult } from '@codingame/monaco-vscode-api/extensions'; +import { MonacoEditorReactComp } from '@typefox/monaco-editor-react'; +import type { MonacoVscodeApiWrapper } from 'monaco-languageclient/vscodeApiWrapper'; import React from 'react'; import ReactDOM from 'react-dom/client'; -import { MonacoEditorReactComp } from '@typefox/monaco-editor-react'; -import { MonacoEditorLanguageClientWrapper } from 'monaco-editor-wrapper'; -import { createWrapperConfig } from './config.js'; +import * as vscode from 'vscode'; import { configureDebugging } from '../../debugger/client/debugger.js'; +import { createPythonAppConfig } from './config.js'; export const runPythonReact = async () => { - const appConfig = createWrapperConfig(); + const appConfig = createPythonAppConfig(); - const onLoad = async (wrapper: MonacoEditorLanguageClientWrapper) => { - const result = wrapper.getExtensionRegisterResult('mlc-python-example') as RegisterLocalProcessExtensionResult; + const onVscodeApiInitDone = async (apiWrapper: MonacoVscodeApiWrapper) => { + const result = apiWrapper.getExtensionRegisterResult('mlc-python-example') as RegisterLocalProcessExtensionResult; result.setAsDefaultApi(); - const initResult = wrapper.getExtensionRegisterResult('debugger-py-client') as RegisterLocalProcessExtensionResult | undefined; + const initResult = apiWrapper.getExtensionRegisterResult('debugger-py-client') as RegisterLocalProcessExtensionResult | undefined; if (initResult !== undefined) { configureDebugging(await initResult.getApi(), appConfig.configParams); } @@ -34,9 +34,10 @@ export const runPythonReact = async () => { return (
{ console.error(e); }} /> diff --git a/packages/examples/src/ts/wrapperTs.ts b/packages/examples/src/ts/wrapperTs.ts index f6d2ba67a..f96b12161 100644 --- a/packages/examples/src/ts/wrapperTs.ts +++ b/packages/examples/src/ts/wrapperTs.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See LICENSE in the package root for license information. * ------------------------------------------------------------------------------------------ */ -import * as vscode from 'vscode'; -import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; +import { LogLevel } from '@codingame/monaco-vscode-api'; import '@codingame/monaco-vscode-javascript-default-extension'; +import getKeybindingsServiceOverride from '@codingame/monaco-vscode-keybindings-service-override'; import '@codingame/monaco-vscode-typescript-basics-default-extension'; import '@codingame/monaco-vscode-typescript-language-features-default-extension'; -import { LogLevel } from '@codingame/monaco-vscode-api'; -import { MonacoEditorLanguageClientWrapper, type WrapperConfig } from 'monaco-editor-wrapper'; -import { configureDefaultWorkerFactory } from 'monaco-editor-wrapper/workers/workerLoaders'; +import { EditorApp, type EditorAppConfig } from 'monaco-languageclient/editorApp'; +import { MonacoVscodeApiWrapper, type MonacoVscodeApiConfig } from 'monaco-languageclient/vscodeApiWrapper'; +import { configureDefaultWorkerFactory } from 'monaco-languageclient/workerFactory'; +import * as vscode from 'vscode'; import { disableElement } from '../common/client/utils.js'; export const runTsWrapper = async () => { @@ -24,49 +25,55 @@ export const runTsWrapper = async () => { return "Goodbye"; };`; - const wrapperConfig: WrapperConfig = { + const htmlContainer = document.getElementById('monaco-editor-root')!; + const vscodeApiConfig: MonacoVscodeApiConfig = { $type: 'extended', - htmlContainer: document.getElementById('monaco-editor-root')!, + htmlContainer, logLevel: LogLevel.Debug, - vscodeApiConfig: { - serviceOverrides: { - ...getKeybindingsServiceOverride() - }, + serviceOverrides: { + ...getKeybindingsServiceOverride() + }, + advanced: { enableExtHostWorker: true, - userConfiguration: { - json: JSON.stringify({ - 'workbench.colorTheme': 'Default Dark Modern', - 'typescript.tsserver.web.projectWideIntellisense.enabled': true, - 'typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors': false, - 'diffEditor.renderSideBySide': false, - 'editor.lightbulb.enabled': 'on', - 'editor.glyphMargin': true, - 'editor.guides.bracketPairsHorizontal': true, - 'editor.experimental.asyncTokenization': true - }) - } }, - editorAppConfig: { - codeResources: { - modified: { - text: code, - uri: codeUri - }, - original: { - text: codeOriginal, - uri: codeOriginalUri, - } + userConfiguration: { + json: JSON.stringify({ + 'workbench.colorTheme': 'Default Dark Modern', + 'typescript.tsserver.web.projectWideIntellisense.enabled': true, + 'typescript.tsserver.web.projectWideIntellisense.suppressSemanticErrors': false, + 'diffEditor.renderSideBySide': false, + 'editor.lightbulb.enabled': 'on', + 'editor.glyphMargin': true, + 'editor.guides.bracketPairsHorizontal': true, + 'editor.experimental.asyncTokenization': true + }) + }, + monacoWorkerFactory: configureDefaultWorkerFactory + }; + + const editorAppConfig: EditorAppConfig = { + $type: vscodeApiConfig.$type, + codeResources: { + modified: { + text: code, + uri: codeUri }, - monacoWorkerFactory: configureDefaultWorkerFactory + original: { + text: codeOriginal, + uri: codeOriginalUri, + } } }; - const wrapper = new MonacoEditorLanguageClientWrapper(); + const apiWrapper = new MonacoVscodeApiWrapper(vscodeApiConfig); + await apiWrapper.init(); + + const editorApp = new EditorApp(editorAppConfig); disableElement('button-swap-code', true); try { document.querySelector('#button-start')?.addEventListener('click', async () => { - await wrapper.initAndStart(wrapperConfig); + await editorApp.start(htmlContainer); vscode.commands.getCommands().then((x) => { console.log(`Found ${x.length} commands`); @@ -74,13 +81,13 @@ export const runTsWrapper = async () => { console.log(`Found command: ${finding}`); }); - wrapper.getEditor()?.focus(); + editorApp.getEditor()?.focus(); await vscode.commands.executeCommand('actions.find'); }); document.querySelector('#button-swap-code')?.addEventListener('click', () => { - const codeResources = wrapper.getEditorApp()?.getConfig().codeResources; + const codeResources = editorApp.getConfig().codeResources; if (codeResources?.modified?.uri === codeUri) { - wrapper.updateCodeResources({ + editorApp.updateCodeResources({ modified: { text: codeOriginal, uri: codeOriginalUri @@ -91,7 +98,7 @@ export const runTsWrapper = async () => { } }); } else { - wrapper.updateCodeResources({ + editorApp.updateCodeResources({ modified: { text: code, uri: codeUri @@ -105,14 +112,14 @@ export const runTsWrapper = async () => { }); document.querySelector('#button-diff')?.addEventListener('click', async () => { // ensure it is boolean value and not undefined - const useDiffEditor = wrapperConfig.editorAppConfig!.useDiffEditor ?? false; - wrapperConfig.editorAppConfig!.useDiffEditor = !useDiffEditor; - disableElement('button-swap-code', !wrapperConfig.editorAppConfig!.useDiffEditor); + const useDiffEditor = editorAppConfig.useDiffEditor ?? false; + editorAppConfig.useDiffEditor = !useDiffEditor; + disableElement('button-swap-code', !editorAppConfig.useDiffEditor); - await wrapper.initAndStart(wrapperConfig); + await editorApp.start(htmlContainer); }); document.querySelector('#button-dispose')?.addEventListener('click', async () => { - await wrapper.dispose(); + await editorApp.dispose(); }); } catch (e) { console.error(e); diff --git a/packages/examples/tsconfig.src.json b/packages/examples/tsconfig.src.json index f9bc4aad8..7ae6c42ba 100644 --- a/packages/examples/tsconfig.src.json +++ b/packages/examples/tsconfig.src.json @@ -12,9 +12,6 @@ { "path": "../client/tsconfig.src.json", }, - { - "path": "../wrapper/tsconfig.src.json" - }, { "path": "../wrapper-react/tsconfig.src.json" }], diff --git a/packages/examples/two_langauge_clients.html b/packages/examples/two_langauge_clients.html index 494653aef..f4090c775 100644 --- a/packages/examples/two_langauge_clients.html +++ b/packages/examples/two_langauge_clients.html @@ -15,7 +15,6 @@ -