Skip to content

Commit

Permalink
#521 Launch pyright-languageserver
Browse files Browse the repository at this point in the history
extended ws-jsonrpc serverlaunch to expose process
  • Loading branch information
kaisalmen committed Aug 18, 2023
1 parent 59504de commit d955d2f
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 30 deletions.
15 changes: 12 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"version": "0.2.0",
"configurations": [
{
"name": "Launch Sample Server",
"name": "Launch JSON LS",
"type": "node",
"request": "launch",
"args": ["${workspaceRoot}/packages/examples/main/src/server/main.ts"],
Expand All @@ -14,7 +14,7 @@
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": "Launch Sample Server (external)",
"name": "Launch JSON LS (external)",
"type": "node",
"request": "launch",
"args": ["${workspaceRoot}/packages/examples/main/src/server/main.ts", "--external"],
Expand All @@ -23,9 +23,18 @@
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": "Launch Python LS",
"type": "node",
"request": "launch",
"args": ["${workspaceRoot}/packages/examples/main/src/python/server.ts"],
"runtimeArgs": ["--nolazy", "--loader", "ts-node/esm"],
"cwd": "${workspaceRoot}/packages/examples/main",
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": "Chrome: monaco-languageclient",
"type": "chrome",
"request": "launch",
"name": "Launch Chrome against localhost",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
}
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"build:verify:vite": "npm run build --workspace packages/verify/vite",
"start:example:server": "npm run start --workspace packages/examples/main",
"start:example:server:ext": "npm run start:ext --workspace packages/examples/main",
"start:example:server:python": "npm run start:python --workspace packages/examples/main",
"start:example:angular": "npm run start --workspace packages/examples/angular-client",
"start:verify:webpack": "npm run start --workspace packages/verify/webpack",
"start:verify:vite": "npm run start --workspace packages/verify/vite",
Expand All @@ -55,4 +56,4 @@
"packages/verify/webpack",
"packages/verify/vite"
]
}
}
2 changes: 1 addition & 1 deletion packages/client/src/monaco-language-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class MonacoLanguageClient extends BaseLanguageClient {
this.connectionProvider = connectionProvider;
}

protected createMessageTransports(encoding: string): Promise<MessageTransports> {
protected override createMessageTransports(encoding: string): Promise<MessageTransports> {
return this.connectionProvider.get(encoding);
}

Expand Down
48 changes: 34 additions & 14 deletions packages/examples/main/src/python/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,25 @@ import { RegisteredFileSystemProvider, registerFileSystemOverlay, RegisteredMemo
import { buildWorkerDefinition } from 'monaco-editor-workers';
buildWorkerDefinition('../../../node_modules/monaco-editor-workers/dist/workers/', new URL('', window.location.href).href, false);

const languageId = 'python';
let languageClient: MonacoLanguageClient;

const createWebSocket = (url: string): WebSocket => {
const webSocket = new WebSocket(url);
webSocket.onopen = () => {
webSocket.onopen = async () => {
const socket = toSocket(webSocket);
const reader = new WebSocketMessageReader(socket);
const writer = new WebSocketMessageWriter(socket);
const languageClient = createLanguageClient({
languageClient = createLanguageClient({
reader,
writer
});
languageClient.start();
await languageClient.start();
reader.onClose(() => languageClient.stop());

vscode.commands.registerCommand('pyright.restartserver', (...args: unknown[]) => {
languageClient.sendRequest('workspace/executeCommand', { command: 'pyright.restartserver', arguments: args });
});
};
return webSocket;
};
Expand All @@ -40,7 +47,7 @@ const createLanguageClient = (transports: MessageTransports): MonacoLanguageClie
name: 'Pyright Language Client',
clientOptions: {
// use a language id as a document selector
documentSelector: ['json'],
documentSelector: [languageId],
// disable the default error handler
errorHandler: {
error: () => ({ action: ErrorAction.Continue }),
Expand All @@ -65,10 +72,11 @@ const run = async () => {
enableModelService: true,
enableThemeService: true,
enableTextmateService: true,
enableKeybindingsService: true,
configureConfigurationServiceConfig: {
defaultWorkspaceUri: '/tmp'
},
enableKeybindingsService: true,
enableQuickaccessService: true,
debugLogging: true
});

Expand All @@ -77,22 +85,24 @@ const run = async () => {
"workbench.colorTheme": "Default Dark Modern"
}`);

// register the JSON language with Monaco
monaco.languages.register({
id: languageId,
extensions: [
'.py',
'.pyi'
],
aliases: ['Python', 'python']
});

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
// use the file create before
const modelRef = await createModelReference(monaco.Uri.file('/tmp/hello.py'));
modelRef.object.setLanguageId(languageId);

Expand All @@ -103,4 +113,14 @@ const run = async () => {
});
};

// const executeCommand = (cmd: string, ...args: unknown[]): Thenable<unknown> => {
// return vscode.commands.executeCommand(cmd, ...args);
// };

// // just test if sending commands works
// setTimeout(async () => {
// console.log('Firing restart after 5000ms.');
// await executeCommand('pyright.restartserver');
// }, 5000);

run();
23 changes: 17 additions & 6 deletions packages/examples/main/src/python/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,28 @@ import { IncomingMessage } from 'http';
import { URL } from 'url';
import { Socket } from 'net';
import express from 'express';
import { resolve } from 'path';
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) => {
// start the language server as an external process
const serverName = 'PYRIGHT';
const ls = resolve(getLocalDirectory(import.meta.url), '../../../../../node_modules/pyright/dist/pyright-langserver.js');
const serverProcesses = createServerProcess(serverName, 'node', [ls, '--stdio']);
if (serverProcesses?.serverProcess?.stdout !== null) {
serverProcesses?.serverProcess?.stdout.on('data', data =>
console.log(`${serverName} Server: ${data}`)
);
}

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 (serverProcesses?.connection) {
forward(socketConnection, serverProcesses.connection, message => {
if (Message.isRequest(message)) {
console.log(message);
if (message.method === InitializeRequest.type.method) {
Expand Down Expand Up @@ -53,6 +61,7 @@ const run = () => {
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;
Expand All @@ -76,7 +85,9 @@ const run = () => {
if (webSocket.readyState === webSocket.OPEN) {
launchLanguageServer(socket);
} else {
webSocket.on('open', () => launchLanguageServer(socket));
webSocket.on('open', () => {
launchLanguageServer(socket);
});
}
});
}
Expand Down
14 changes: 11 additions & 3 deletions packages/examples/main/src/server/json-server-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@ export function launch(socket: IWebSocket) {
const writer = new WebSocketMessageWriter(socket);
const asExternalProccess = process.argv.findIndex(value => value === '--external') !== -1;
if (asExternalProccess) {
const serverName = 'JSON';
// start the language server as an external process
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) {
forward(socketConnection, serverConnection, message => {
const result = createServerProcess(serverName, 'node', [extJsonServerPath]);

if (result?.serverProcess?.stdout !== null) {
result?.serverProcess?.stdout.on('data', data =>
console.log(`${serverName} Server: ${data}`)
);
}

if (result.connection) {
forward(socketConnection, result.connection, message => {
if (Message.isRequest(message)) {
if (message.method === InitializeRequest.type.method) {
const initializeParams = message.params as InitializeParams;
Expand Down
11 changes: 9 additions & 2 deletions packages/vscode-ws-jsonrpc/src/server/launch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ import { IWebSocket, IWebSocketConnection } from '../socket/socket.js';
import { WebSocketMessageReader } from '../socket/reader.js';
import { WebSocketMessageWriter } from '../socket/writer.js';

export function createServerProcess(serverName: string, command: string, args?: string[], options?: cp.SpawnOptions): IConnection | undefined {
export function createServerProcess(serverName: string, command: string, args?: string[],
options?: cp.SpawnOptions): {
connection: IConnection | undefined,
serverProcess: cp.ChildProcess
} {
const serverProcess = cp.spawn(command, args || [], options || {});
serverProcess.on('error', error =>
console.error(`Launching ${serverName} Server failed: ${error}`)
Expand All @@ -22,7 +26,10 @@ export function createServerProcess(serverName: string, command: string, args?:
console.error(`${serverName} Server: ${data}`)
);
}
return createProcessStreamConnection(serverProcess);
return {
connection: createProcessStreamConnection(serverProcess),
serverProcess
};
}

export function createWebSocketConnection(socket: IWebSocket): IWebSocketConnection {
Expand Down

0 comments on commit d955d2f

Please sign in to comment.