diff --git a/index.html b/index.html index 578d72460..b83186472 100644 --- a/index.html +++ b/index.html @@ -27,6 +27,9 @@

Examples



React Client Example +

Python

+ Python Pyright Client Example +

Verification

Webpack

Please start npm run start:verify:webpack beforehand:
diff --git a/package-lock.json b/package-lock.json index e1ee30da8..cbccd70d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3008,7 +3008,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -4729,6 +4728,21 @@ "node": ">=6" } }, + "node_modules/pyright": { + "version": "1.1.322", + "resolved": "https://registry.npmjs.org/pyright/-/pyright-1.1.322.tgz", + "integrity": "sha512-PyWajD2Fs3zQ9ngT/qKMqqw3KXF1kQGOk13QGO9IfPDiK5KQVUb35eDCc05sZkEQvufkbG934X4SbsapPYg4YA==", + "bin": { + "pyright": "index.js", + "pyright-langserver": "langserver.index.js" + }, + "engines": { + "node": ">=12.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -6442,6 +6456,7 @@ "monaco-editor-workers": "~0.41.0", "monaco-languageclient": "~6.4.0", "normalize-url": "~8.0.0", + "pyright": "~1.1.322", "react": "~18.2.0", "react-dom": "~18.2.0", "request-light": "~0.7.0", diff --git a/packages/examples/main/package.json b/packages/examples/main/package.json index 7697f34a9..0d5c8d8d1 100644 --- a/packages/examples/main/package.json +++ b/packages/examples/main/package.json @@ -25,6 +25,7 @@ "monaco-editor-workers": "~0.41.0", "monaco-languageclient": "~6.4.0", "normalize-url": "~8.0.0", + "pyright": "~1.1.322", "react": "~18.2.0", "react-dom": "~18.2.0", "request-light": "~0.7.0", @@ -57,6 +58,7 @@ "build:worker:langium": "esbuild ./src/langium/langiumServerWorker.js --bundle --tree-shaking=true --minify --format=iife --outfile=./dist/worker/langiumServerWorker.js", "build": "npm run build:msg && npm run clean && npm run compile && npm run build:worker:statemachine && npm run build:worker:langium", "start": "node --loader ts-node/esm src/server/main.ts", - "start:ext": "node --loader ts-node/esm src/server/main.ts --external" + "start:ext": "node --loader ts-node/esm src/server/main.ts --external", + "start:python": "node --loader ts-node/esm src/python/server.ts" } } \ No newline at end of file diff --git a/packages/examples/main/python.html b/packages/examples/main/python.html new file mode 100644 index 000000000..d2fed7307 --- /dev/null +++ b/packages/examples/main/python.html @@ -0,0 +1,16 @@ + + + + + Monaco Language Client Python Example + + + + + +

Monaco Language Client Python Example

+
+ + + + diff --git a/packages/examples/main/src/python/client.ts b/packages/examples/main/src/python/client.ts new file mode 100644 index 000000000..710994c92 --- /dev/null +++ b/packages/examples/main/src/python/client.ts @@ -0,0 +1,106 @@ +/* -------------------------------------------------------------------------------------------- + * 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'; +import 'monaco-editor/esm/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.js'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js'; +import * as vscode from 'vscode'; +import 'vscode/default-extensions/theme-defaults'; +import 'vscode/default-extensions/python'; +import { updateUserConfiguration } from 'vscode/service-override/configuration'; +import { createConfiguredEditor, createModelReference } from 'vscode/monaco'; +import { initServices, MonacoLanguageClient } from 'monaco-languageclient'; +import { CloseAction, ErrorAction, MessageTransports } from 'vscode-languageclient'; +import { WebSocketMessageReader, WebSocketMessageWriter, toSocket } from 'vscode-ws-jsonrpc'; +import { RegisteredFileSystemProvider, registerFileSystemOverlay, RegisteredMemoryFile } from 'vscode/service-override/files'; + +import { buildWorkerDefinition } from 'monaco-editor-workers'; +buildWorkerDefinition('../../../node_modules/monaco-editor-workers/dist/workers/', new URL('', window.location.href).href, false); + +const createWebSocket = (url: string): WebSocket => { + const webSocket = new WebSocket(url); + webSocket.onopen = () => { + const socket = toSocket(webSocket); + const reader = new WebSocketMessageReader(socket); + const writer = new WebSocketMessageWriter(socket); + const languageClient = createLanguageClient({ + reader, + writer + }); + languageClient.start(); + reader.onClose(() => languageClient.stop()); + }; + return webSocket; +}; + +const createLanguageClient = (transports: MessageTransports): MonacoLanguageClient => { + return new MonacoLanguageClient({ + name: 'Pyright Language Client', + clientOptions: { + // use a language id as a document selector + documentSelector: ['json'], + // disable the default error handler + errorHandler: { + error: () => ({ action: ErrorAction.Continue }), + closed: () => ({ action: CloseAction.DoNotRestart }) + }, + synchronize: { + fileEvents: [vscode.workspace.createFileSystemWatcher('**')] + } + }, + // create a language client connection from the JSON RPC connection on demand + connectionProvider: { + get: () => { + return Promise.resolve(transports); + } + } + }); +}; + +const run = async () => { + // init vscode-api + await initServices({ + enableModelService: true, + enableThemeService: true, + enableTextmateService: true, + enableKeybindingsService: true, + configureConfigurationServiceConfig: { + defaultWorkspaceUri: '/tmp' + }, + debugLogging: true + }); + + updateUserConfiguration(`{ + "editor.fontSize": 14, + "workbench.colorTheme": "Default Dark Modern" + }`); + + const fileSystemProvider = new RegisteredFileSystemProvider(false); + fileSystemProvider.registerFile(new RegisteredMemoryFile(vscode.Uri.file('/tmp/hello.py'), 'print("Hello, World!")')); + registerFileSystemOverlay(1, fileSystemProvider); + + // create the web socket and configure to start the language client on open + createWebSocket('ws://localhost:30000/pyright'); + + // register the JSON language with Monaco + const languageId = 'python'; + monaco.languages.register({ + id: languageId, + extensions: ['.py'], + aliases: ['PYTHON', 'python'] + }); + + // create the model inside the workspace + const modelRef = await createModelReference(monaco.Uri.file('/tmp/hello.py')); + modelRef.object.setLanguageId(languageId); + + // create monaco editor + createConfiguredEditor(document.getElementById('container')!, { + model: modelRef.object.textEditorModel, + automaticLayout: true + }); +}; + +run(); diff --git a/packages/examples/main/src/python/index.html b/packages/examples/main/src/python/index.html new file mode 100644 index 000000000..07c7bfa58 --- /dev/null +++ b/packages/examples/main/src/python/index.html @@ -0,0 +1,14 @@ + + + + + + + Pyright Python Language Server + + + +

Hello Pyright

+ + + diff --git a/packages/examples/main/src/python/server.ts b/packages/examples/main/src/python/server.ts new file mode 100644 index 000000000..692d653f1 --- /dev/null +++ b/packages/examples/main/src/python/server.ts @@ -0,0 +1,86 @@ +/* -------------------------------------------------------------------------------------------- + * 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 { WebSocketServer } from 'ws'; +import { IncomingMessage } from 'http'; +import { URL } from 'url'; +import { Socket } from 'net'; +import express from 'express'; +import { IWebSocket, WebSocketMessageReader, WebSocketMessageWriter } from 'vscode-ws-jsonrpc'; +import { createConnection, createServerProcess, forward } from 'vscode-ws-jsonrpc/server'; +import { Message, InitializeRequest, InitializeParams } from 'vscode-languageserver'; +import { getLocalDirectory } from '../server/fs-utils.js'; + +const launchLanguageServer = (socket: IWebSocket) => { + const reader = new WebSocketMessageReader(socket); + const writer = new WebSocketMessageWriter(socket); + + // start the language server as an external process + const socketConnection = createConnection(reader, writer, () => socket.dispose()); + const serverConnection = createServerProcess('PYRIGHT', 'npx', ['pyright', '--verbose', '--watch', '/tmp']); + if (serverConnection) { + forward(socketConnection, serverConnection, message => { + if (Message.isRequest(message)) { + console.log(message); + if (message.method === InitializeRequest.type.method) { + const initializeParams = message.params as InitializeParams; + initializeParams.processId = process.pid; + } + } + return message; + }); + } +}; + +const run = () => { + process.on('uncaughtException', function(err: any) { + console.error('Uncaught Exception: ', err.toString()); + if (err.stack) { + console.error(err.stack); + } + }); + + // create the express application + const app = express(); + // server the static content, i.e. index.html + const dir = getLocalDirectory(import.meta.url); + app.use(express.static(dir)); + // start the server + const server = app.listen(30000); + // create the web socket + const wss = new WebSocketServer({ + noServer: true, + perMessageDeflate: false + }); + server.on('upgrade', (request: IncomingMessage, socket: Socket, head: Buffer) => { + const baseURL = `http://${request.headers.host}/`; + const pathname = request.url ? new URL(request.url, baseURL).pathname : undefined; + if (pathname === '/pyright') { + wss.handleUpgrade(request, socket, head, webSocket => { + const socket: IWebSocket = { + send: content => webSocket.send(content, error => { + if (error) { + throw error; + } + }), + onMessage: cb => webSocket.on('message', (data) => { + console.log(data.toString()); + cb(data); + }), + onError: cb => webSocket.on('error', cb), + onClose: cb => webSocket.on('close', cb), + dispose: () => webSocket.close() + }; + // launch the server when the web socket is opened + if (webSocket.readyState === webSocket.OPEN) { + launchLanguageServer(socket); + } else { + webSocket.on('open', () => launchLanguageServer(socket)); + } + }); + } + }); +}; + +run(); diff --git a/packages/examples/main/src/python/workspace/src/hello.py b/packages/examples/main/src/python/workspace/src/hello.py new file mode 100644 index 000000000..d2c66e8ce --- /dev/null +++ b/packages/examples/main/src/python/workspace/src/hello.py @@ -0,0 +1,2 @@ + +print("Hello, World!") diff --git a/packages/examples/main/src/server/fs-utils.ts b/packages/examples/main/src/server/fs-utils.ts index 9fc01e3e9..4e4fdd7bd 100644 --- a/packages/examples/main/src/server/fs-utils.ts +++ b/packages/examples/main/src/server/fs-utils.ts @@ -8,7 +8,7 @@ import { fileURLToPath } from 'url'; /** * Solves: __dirname is not defined in ES module scope */ -export function getLocalDirectory() { - const __filename = fileURLToPath(import.meta.url); +export const getLocalDirectory = (referenceUrl: string | URL) => { + const __filename = fileURLToPath(referenceUrl); return dirname(__filename); -} +}; diff --git a/packages/examples/main/src/server/json-server-launcher.ts b/packages/examples/main/src/server/json-server-launcher.ts index e4d08c21b..38cfe16a2 100644 --- a/packages/examples/main/src/server/json-server-launcher.ts +++ b/packages/examples/main/src/server/json-server-launcher.ts @@ -15,7 +15,7 @@ export function launch(socket: IWebSocket) { const asExternalProccess = process.argv.findIndex(value => value === '--external') !== -1; if (asExternalProccess) { // start the language server as an external process - const extJsonServerPath = resolve(getLocalDirectory(), '../../dist/server/ext-json-server.js'); + const extJsonServerPath = resolve(getLocalDirectory(import.meta.url), '../../dist/server/ext-json-server.js'); const socketConnection = createConnection(reader, writer, () => socket.dispose()); const serverConnection = createServerProcess('JSON', 'node', [extJsonServerPath]); if (serverConnection) { diff --git a/packages/examples/main/src/server/main.ts b/packages/examples/main/src/server/main.ts index 6d4597740..339488f55 100644 --- a/packages/examples/main/src/server/main.ts +++ b/packages/examples/main/src/server/main.ts @@ -21,7 +21,8 @@ process.on('uncaughtException', function(err: any) { // create the express application const app = express(); // server the static content, i.e. index.html -app.use(express.static(getLocalDirectory())); +const dir = getLocalDirectory(import.meta.url); +app.use(express.static(dir)); // start the server const server = app.listen(3000); // create the web socket diff --git a/vite.config.ts b/vite.config.ts index e0936d467..4887891da 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -12,7 +12,8 @@ export default defineConfig(() => { langiumClient: resolve(__dirname, 'packages/examples/main/langium_client.html'), statemachineClient: resolve(__dirname, 'packages/examples/main/statemachine_client.html'), browser: resolve(__dirname, 'packages/examples/main/browser.html'), - react: resolve(__dirname, 'packages/examples/main/react.html') + react: resolve(__dirname, 'packages/examples/main/react.html'), + python: resolve(__dirname, 'packages/examples/main/python.html') } } },