diff --git a/CHANGELOG.md b/CHANGELOG.md index f7eb46ae..a66fcdb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## [2.0.0] - Not yet release + +* TBA + ## [1.1.0] - 2022-06-08 * feat(package.json): export monaco-converter #[376](https://github.com/TypeFox/monaco-languageclient/pull/376) diff --git a/README.md b/README.md index 46796c52..57deacf5 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ - [Scripts Overview](#scripts-overview) - [**Examples**](#examples) - [ Node.js Language Server + web client example](#nodejs-language-server-plus-web-client-example) + - [Browser-LSP](#browser-language-client-and-server) - [Browser](#browser-example) - [VSCode integration](#vscode-integration) - [**History**](CHANGELOG.md) @@ -63,13 +64,19 @@ cd packages/client && npm run build ## Examples -There are two different examples (one is a client-server one now separated) that demonstrate how the `monaco-languageclient` can be used. The Node.js example uses Express and WebSockets to enable communication between the language server process and the web application. The browser example shows how a language service written in JavaScript can be used in a Monaco Editor contained in a simple HTML page. +There are three examples different examples that demonstrate how the `monaco-languageclient` can be used. The Node.js example uses Express and WebSockets to enable communication between the language server process and the web application. + +The Browser Language Client & Server examples does the same, but the server runs in a web worker and communication is via direct LSP message exchange. + +The browser example shows how a language service written in JavaScript can be used in a Monaco +Editor contained in a simple HTML page. All example packages now are now located under [./packages/examples](./packages/examples): -- Node.js Language Server example: [./packages/examples/node](./packages/examples/node): - Look at the [example express app](https://github.com/TypeFox/monaco-languageclient/blob/sandbox-331/packages/examples/node/src/server.ts) to learn how to open a web socket with an express app and launch a language server within the current process or as an external process. -- Web Client for Node.js Language Server: [./packages/examples/client](./packages/examples/client): Look at the [example client](https://github.com/TypeFox/monaco-languageclient/blob/sandbox-331/packages/examples/client/src/client.ts) to learn how to start Monaco language client. -- Browser example: [./packages/examples/browser](./packages/examples/browser): Look at the [browser example](https://github.com/TypeFox/monaco-languageclient/blob/sandbox-331/packages/examples/browser/src/client.ts) to learn how to use a language service written in JavaScript in a simple HTML page. +- Node.js Language Server example: [./packages/examples/node](./packages/examples/node): - Look at the [example express app](https://github.com/TypeFox/monaco-languageclient/blob/main/packages/examples/node/src/server.ts) to learn how to open a web socket with an express app and launch a language server within the current process or as an external process. +- Web Client for Node.js Language Server: [./packages/examples/client](./packages/examples/client): Look at the [example client](https://github.com/TypeFox/monaco-languageclient/blob/main/packages/examples/client/src/client.ts) to learn how to start Monaco language client. +- Browser Language Client and Server: [./packages/examples/browser-lsp](./packages/examples/browser-lsp): Look at the [client](https://github.com/TypeFox/monaco-languageclient/blob/main/packages/examples/browser-lsp/src/client.ts) and the [web worker](https://github.com/TypeFox/monaco-languageclient/blob/main/packages/examples/browser-lsp/src/serverWorker.ts) implementing the language server. They communicate via `vscode-languageserver-protocol/browser` instead of a web socket used in the first example. +- Browser example: [./packages/examples/browser](./packages/examples/browser): Look at the [browser example](https://github.com/TypeFox/monaco-languageclient/blob/main/packages/examples/browser/src/client.ts) to learn how to use a language service written in JavaScript in a simple HTML page ([here](https://github.com/TypeFox/monaco-languageclient/blob/main/packages/examples/browser-old/src/client.ts) you find the old now deprecated implementation using the [monaco converters](https://github.com/TypeFox/monaco-languageclient/blob/main/packages/client/src/monaco-converters.ts).) ### Node.js Language Server plus web client example @@ -86,9 +93,24 @@ npm run start-example-node:ext npm run dev ``` -After launching vite development server go to http://localhost:8080/packages/examples/client/index.html +After launching vite development server go to [client example](http://localhost:8080/packages/examples/client/index.html) + +**Hints for all examples:** Vite serves all client code from [localhost](http://localhost:8080). You can go to the [index.html](http://localhost:8080/index.html) and navigate to all client examples from there. You can edit the client example code directly (TypeScript) and vite ensures it automatically made available. + +### Browser Language Client and Server -You can edit the client example code directly (TypeScript) and vite ensures it automatically made available. +If you have build all packages or the specific package `packages/examples/browser-lsp` before you just need to run the vite development server: + +```bash +# Optional: Build all packages +npm run build +# Launch vite development server if not already done +npm run dev +``` + +After launching vite development an go to the [example](http://localhost:8080/packages/examples/browser-lsp/index.html) + +**Hint:** If you change the worker code, you have to re-create it (use `npm run build:worker` from the within the example [directory](./packages/examples/browser-lsp)! ### Browser example @@ -99,13 +121,9 @@ From CLI in root of the project you just need to run. If it is already running t npm run dev ``` -After launching vite development server go to http://localhost:8080/packages/examples/browser/index.html - -You can also go to http://localhost:8080/packages/examples/browser-old/index.html for the old implementation using the deprecated monaco converters - -You can edit the client example code directly (TypeScript) and vite ensures it automatically made available +After launching vite development an go to the [example](http://localhost:8080/packages/examples/browser/index.html) -**Hint:** Vite serves all client code from http://localhost:8080 . You can go to the index.html and from there select if you want to open **Web Client for Node.js Language Server** or **Browser Example** as well. +You can also [go to](http://localhost:8080/packages/examples/browser-old/index.html) for the old implementation using the deprecated monaco converters. ### Optional webpack build for client example diff --git a/index.html b/index.html index 95ec6518..dc8dc838 100644 --- a/index.html +++ b/index.html @@ -17,6 +17,8 @@

Examples


Browser Example
+ Browser Language Client & Server +
Browser Example old (using monaco-converter) diff --git a/package-lock.json b/package-lock.json index c1a80978..6da8ef0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "packages/client", "packages/examples/node", "packages/examples/client", + "packages/examples/browser-lsp", "packages/examples/browser", "packages/examples/browser-old" ], @@ -619,6 +620,10 @@ "resolved": "packages/examples/browser-old", "link": true }, + "node_modules/browser-lsp": { + "resolved": "packages/examples/browser-lsp", + "link": true + }, "node_modules/browserslist": { "version": "4.20.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", @@ -3484,6 +3489,15 @@ "vscode-json-languageservice": "^5.0.0" } }, + "packages/examples/browser-lsp": { + "dependencies": { + "monaco-editor": "0.33.0", + "monaco-editor-workers": "0.33.0", + "monaco-languageclient": "../../client", + "vscode-languageserver": "8.0.1", + "vscode-languageserver-protocol": "3.17.1" + } + }, "packages/examples/browser-old": { "name": "browser-example-old", "dependencies": { @@ -4081,6 +4095,16 @@ "vscode-json-languageservice": "^5.0.0" } }, + "browser-lsp": { + "version": "file:packages/examples/browser-lsp", + "requires": { + "monaco-editor": "0.33.0", + "monaco-editor-workers": "0.33.0", + "monaco-languageclient": "../../client", + "vscode-languageserver": "8.0.1", + "vscode-languageserver-protocol": "3.17.1" + } + }, "browserslist": { "version": "4.20.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.3.tgz", diff --git a/package.json b/package.json index 0c3aeda3..8e7e9131 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,11 @@ "dev": "vite --debug --force", "start-example-node": "npm run start --workspace packages/examples/node", "start-example-node:ext": "npm run start:ext --workspace packages/examples/node", - "build": "npm run build-client && npm run build-example-node && npm run build-example-client && npm run build-example-browser && npm run build-example-browser-old", + "build": "npm run build --workspaces", "build-client": "npm run build --workspace packages/client", "build-example-node": "npm run build --workspace packages/examples/node", "build-example-client": "npm run build --workspace packages/examples/client && npm run webpack:example-client-build", + "build-example-browser-lsp": "npm run build --workspace packages/examples/browser-lsp", "build-example-browser": "npm run build --workspace packages/examples/browser", "build-example-browser-old": "npm run build --workspace packages/examples/browser-old", "webpack:example-client-build": "npm run webpack:build --workspace packages/examples/client", @@ -40,6 +41,7 @@ "packages/client", "packages/examples/node", "packages/examples/client", + "packages/examples/browser-lsp", "packages/examples/browser", "packages/examples/browser-old" ] diff --git a/packages/examples/browser-lsp/index.html b/packages/examples/browser-lsp/index.html new file mode 100644 index 00000000..14db00d9 --- /dev/null +++ b/packages/examples/browser-lsp/index.html @@ -0,0 +1,16 @@ + + + + + Monaco Language Client Browser Language Client & Server + + + + + + +

Monaco Language Client Browser Language Client & Server

+
+ + + diff --git a/packages/examples/browser-lsp/package.json b/packages/examples/browser-lsp/package.json new file mode 100644 index 00000000..4f99ef8e --- /dev/null +++ b/packages/examples/browser-lsp/package.json @@ -0,0 +1,19 @@ +{ + "name": "browser-lsp", + "private": true, + "dependencies": { + "monaco-editor": "0.33.0", + "monaco-editor-workers": "0.33.0", + "monaco-languageclient": "../../client", + "vscode-languageserver": "8.0.1", + "vscode-languageserver-protocol": "3.17.1" + }, + "scripts": { + "clean": "npx shx rm -fr dist tsconfig.tsbuildinfo", + "compile": "tsc", + "watch": "tsc -w", + "copy:monacoworkers": "shx mkdir -p dist && shx cp -r ../../../node_modules/monaco-editor-workers/dist/workers/editorWorker* ./dist", + "build:worker": "vite -c vite.config.worker.ts build", + "build": "npm run clean && npm run compile && npm run build:worker && npm run copy:monacoworkers" + } +} diff --git a/packages/examples/browser-lsp/src/client.ts b/packages/examples/browser-lsp/src/client.ts new file mode 100644 index 00000000..fdc35917 --- /dev/null +++ b/packages/examples/browser-lsp/src/client.ts @@ -0,0 +1,79 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) 2018-2022 TypeFox GmbH (http://www.typefox.io). All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +import 'monaco-editor/esm/vs/editor/editor.all.js'; + +// support all editor features +import 'monaco-editor/esm/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.js'; +import 'monaco-editor/esm/vs/editor/standalone/browser/inspectTokens/inspectTokens.js'; +import 'monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.js'; +import 'monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneHelpQuickAccess.js'; +import 'monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoLineQuickAccess.js'; +import 'monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneGotoSymbolQuickAccess.js'; +import 'monaco-editor/esm/vs/editor/standalone/browser/quickAccess/standaloneCommandsQuickAccess.js'; +import 'monaco-editor/esm/vs/editor/standalone/browser/quickInput/standaloneQuickInputService.js'; +import 'monaco-editor/esm/vs/editor/standalone/browser/referenceSearch/standaloneReferenceSearch.js'; +import 'monaco-editor/esm/vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.js'; + +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; + +import { buildWorkerDefinition } from 'monaco-editor-workers'; +buildWorkerDefinition('dist', new URL('', window.location.href).href, false); + +import { MonacoLanguageClient, CloseAction, ErrorAction, MonacoServices, MessageTransports } from 'monaco-languageclient'; +import { BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver-protocol/browser'; + +// register Monaco languages +monaco.languages.register({ + id: 'plaintext', + extensions: ['.txt'], + aliases: ['PLAINTEXT', 'plaintext'], + mimetypes: ['text/plain'] +}); + +// create Monaco editor +const editorText = ` +#ff0000 (red) +#00ff00 (green) +#0000ff (blue) +`; +monaco.editor.create(document.getElementById("container")!, { + model: monaco.editor.createModel(editorText, 'plaintext', monaco.Uri.parse('inmemory://model.txt')), + glyphMargin: true, + lightbulb: { + enabled: true + } +}); + +function createLanguageClient(transports: MessageTransports): MonacoLanguageClient { + return new MonacoLanguageClient({ + name: "Sample Language Client", + clientOptions: { + // use a language id as a document selector + documentSelector: [{ language: 'plaintext' }], + // disable the default error handler + errorHandler: { + error: () => ({ action: ErrorAction.Continue }), + closed: () => ({ action: CloseAction.DoNotRestart }) + } + }, + // create a language client connection to the server running in the web worker + connectionProvider: { + get: () => { + return Promise.resolve(transports); + } + } + }); +} + +// install Monaco language client services +MonacoServices.install(); + +const worker = new Worker(new URL('./dist/serverWorker-es.js', window.location.href).href); +const reader = new BrowserMessageReader(worker); +const writer = new BrowserMessageWriter(worker); +const languageClient = createLanguageClient({ reader, writer }); +languageClient.start(); + +reader.onClose(() => languageClient.stop()); diff --git a/packages/examples/browser-lsp/src/serverWorker.ts b/packages/examples/browser-lsp/src/serverWorker.ts new file mode 100644 index 00000000..0d13a47a --- /dev/null +++ b/packages/examples/browser-lsp/src/serverWorker.ts @@ -0,0 +1,115 @@ +// This is an example copied as is from here: +// https://github.com/microsoft/vscode-extension-samples/blob/main/lsp-web-extension-sample/server/src/browserServerMain.ts +// the only addition is the following line: +declare const self: DedicatedWorkerGlobalScope; + +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { createConnection, BrowserMessageReader, BrowserMessageWriter } from 'vscode-languageserver/browser'; + +import { Color, ColorInformation, Range, InitializeParams, InitializeResult, ServerCapabilities, TextDocuments, ColorPresentation, TextEdit, TextDocumentIdentifier } from 'vscode-languageserver'; +import { TextDocument } from 'vscode-languageserver-textdocument'; + + +console.log('running server lsp-web-extension-sample'); + +/* browser specific setup code */ + +const messageReader = new BrowserMessageReader(self); +const messageWriter = new BrowserMessageWriter(self); + +const connection = createConnection(messageReader, messageWriter); + +/* from here on, all code is non-browser specific and could be shared with a regular extension */ + +connection.onInitialize((_params: InitializeParams): InitializeResult => { + const capabilities: ServerCapabilities = { + colorProvider: {} // provide a color providr + }; + return { capabilities }; +}); + +// Track open, change and close text document events +const documents = new TextDocuments(TextDocument); +documents.listen(connection); + +// Register providers +connection.onDocumentColor(params => getColorInformation(params.textDocument)); +connection.onColorPresentation(params => getColorPresentation(params.color, params.range)); + +// Listen on the connection +connection.listen(); + + +const colorRegExp = /#([0-9A-Fa-f]{6})/g; + +function getColorInformation(textDocument: TextDocumentIdentifier) { + const colorInfos: ColorInformation[] = []; + + const document = documents.get(textDocument.uri); + if (document) { + const text = document.getText(); + + colorRegExp.lastIndex = 0; + let match; + while ((match = colorRegExp.exec(text)) != null) { + const offset = match.index; + const length = match[0].length; + + const range = Range.create(document.positionAt(offset), document.positionAt(offset + length)); + const color = parseColor(text, offset); + colorInfos.push({ color, range }); + } + } + + return colorInfos; +} + +function getColorPresentation(color: Color, range: Range) { + const result: ColorPresentation[] = []; + const red256 = Math.round(color.red * 255), green256 = Math.round(color.green * 255), blue256 = Math.round(color.blue * 255); + + function toTwoDigitHex(n: number): string { + const r = n.toString(16); + return r.length !== 2 ? '0' + r : r; + } + + const label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}`; + result.push({ label: label, textEdit: TextEdit.replace(range, label) }); + + return result; +} + + +const enum CharCode { + Digit0 = 48, + Digit9 = 57, + + A = 65, + F = 70, + + a = 97, + f = 102, +} + +function parseHexDigit(charCode: CharCode): number { + if (charCode >= CharCode.Digit0 && charCode <= CharCode.Digit9) { + return charCode - CharCode.Digit0; + } + if (charCode >= CharCode.A && charCode <= CharCode.F) { + return charCode - CharCode.A + 10; + } + if (charCode >= CharCode.a && charCode <= CharCode.f) { + return charCode - CharCode.a + 10; + } + return 0; +} + +function parseColor(content: string, offset: number): Color { + const r = (16 * parseHexDigit(content.charCodeAt(offset + 1)) + parseHexDigit(content.charCodeAt(offset + 2))) / 255; + const g = (16 * parseHexDigit(content.charCodeAt(offset + 3)) + parseHexDigit(content.charCodeAt(offset + 4))) / 255; + const b = (16 * parseHexDigit(content.charCodeAt(offset + 5)) + parseHexDigit(content.charCodeAt(offset + 6))) / 255; + return Color.create(r, g, b, 1); +} diff --git a/packages/examples/browser-lsp/tsconfig.json b/packages/examples/browser-lsp/tsconfig.json new file mode 100644 index 00000000..b29efc44 --- /dev/null +++ b/packages/examples/browser-lsp/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "declarationDir": "dist", + "module": "ES2020", + "lib": ["ES2020", "dom", "WebWorker"] + }, + "references": [{ "path": "../../client" }], + "include": [ + "src/**/*.ts", + ], + "exclude": [ + "src/dist", + "node_modules" + ] +} diff --git a/packages/examples/browser-lsp/vite.config.worker.ts b/packages/examples/browser-lsp/vite.config.worker.ts new file mode 100644 index 00000000..1a52de3e --- /dev/null +++ b/packages/examples/browser-lsp/vite.config.worker.ts @@ -0,0 +1,17 @@ +import path from 'path'; +import { defineConfig } from 'vite'; + +const config = defineConfig({ + build: { + lib: { + entry: path.resolve(__dirname, './src/serverWorker.ts'), + name: 'serverWorker', + fileName: (format) => `serverWorker-${format}.js`, + formats: ['es'] + }, + outDir: 'dist', + emptyOutDir: false + } +}); + +export default config;