diff --git a/lsp-log-streaming-sample/README.md b/lsp-log-streaming-sample/README.md index c43cce31..a9b6b7e9 100644 --- a/lsp-log-streaming-sample/README.md +++ b/lsp-log-streaming-sample/README.md @@ -1,10 +1,29 @@ -# LSP Example +# LSP Example for Log Streaming + +This is a repository adapted from [lsp-sample](https://github.com/Microsoft/vscode-extension-samples/tree/master/lsp-sample) to demonstrate + +- Usage of the JSON output +- Streaming the JSON into [LSP Inspector](https://github.com/Microsoft/language-server-protocol-inspector) + +## Synopsis + +- With `vscode-languageclient@5.1.0-next.9`, you can specify a JSON log output format with `[langId].trace.server` as follows: + ```json + "languageServerExample.trace.server": { + "format": "json", + "verbosity": "verbose" + } + ``` +- A [webview](https://github.com/Microsoft/language-server-protocol-inspector/tree/master/lsp-inspector-webview) build of the LSP Inspector can be downloaded here: +- When using the Webview LSP Inspector, it will open a WebSocket Server taking incoming connection that sends logs following [this format](https://github.com/Microsoft/language-server-protocol-inspector#log-format). +- You can stream the JSON log of any Language Server using `vscode-languageclient` to the LSP Inspector, and it will show a live view of the LSP connection. + -Heavily documented sample code for https://code.visualstudio.com/docs/extensions/example-language-server. ## Functionality This Language Server works for plain text file. It has the following language features: + - Completions - Diagnostics regenerated on each file change or configuration change @@ -35,4 +54,4 @@ It also includes an End-to-End test. - If you want to debug the server as well use the launch configuration `Attach to Server` - In the [Extension Development Host] instance of VSCode, open a document in 'plain text' language mode. - Type `j` or `t` to see `Javascript` and `TypeScript` completion. - - Enter text content such as `AAA aaa BBB`. The extension will emit diagnostics for all words in all-uppercase. \ No newline at end of file + - Enter text content such as `AAA aaa BBB`. The extension will emit diagnostics for all words in all-uppercase. diff --git a/lsp-log-streaming-sample/client/package-lock.json b/lsp-log-streaming-sample/client/package-lock.json index d652478e..485dc6c3 100644 --- a/lsp-log-streaming-sample/client/package-lock.json +++ b/lsp-log-streaming-sample/client/package-lock.json @@ -4,6 +4,28 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/events": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "dev": true + }, + "@types/node": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.9.4.tgz", + "integrity": "sha512-fCHV45gS+m3hH17zgkgADUSi2RR1Vht6wOZ0jyHP8rjiQra9f+mIcgwPQHllmDocYOstIEbKlxbFDYlgrTPYqw==", + "dev": true + }, + "@types/ws": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.0.tgz", + "integrity": "sha512-i/dVaSjmTM92EFFFhmGL6AmHzvJ70XpAXmMLvNKh3JrRTGOiXvejfxe5+OSxcJK0paGOYHDaRLS8nXW6/FxSxg==", + "dev": true, + "requires": { + "@types/events": "1.2.0", + "@types/node": "10.9.4" + } + }, "ajv": { "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", @@ -101,6 +123,11 @@ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, + "async-limiter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", + "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1801,6 +1828,14 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, + "ws": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", + "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", + "requires": { + "async-limiter": "1.0.0" + } + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/lsp-log-streaming-sample/client/package.json b/lsp-log-streaming-sample/client/package.json index d72a94c4..9736b10a 100644 --- a/lsp-log-streaming-sample/client/package.json +++ b/lsp-log-streaming-sample/client/package.json @@ -18,6 +18,10 @@ }, "dependencies": { "vscode": "^1.1.21", - "vscode-languageclient": "^5.1.0-next.9" + "vscode-languageclient": "^5.1.0-next.9", + "ws": "^6.0.0" + }, + "devDependencies": { + "@types/ws": "^6.0.0" } } diff --git a/lsp-log-streaming-sample/client/src/extension.ts b/lsp-log-streaming-sample/client/src/extension.ts index 52b0cc87..99f4c83a 100644 --- a/lsp-log-streaming-sample/client/src/extension.ts +++ b/lsp-log-streaming-sample/client/src/extension.ts @@ -5,7 +5,8 @@ 'use strict'; import * as path from 'path'; -import { workspace, ExtensionContext } from 'vscode'; +import { workspace, commands, ExtensionContext, OutputChannel } from 'vscode'; +import * as WebSocket from 'ws'; import { LanguageClient, @@ -17,6 +18,14 @@ import { let client: LanguageClient; export function activate(context: ExtensionContext) { + const socketPort = workspace.getConfiguration('languageServerExample').get('port', 7000); + let socket: WebSocket | null = null + + commands.registerCommand('languageServerExample.startStreaming', () => { + // Establish websocket connection + socket = new WebSocket(`ws://localhost:${socketPort}`) + }) + // The server is implemented in node let serverModule = context.asAbsolutePath( path.join('server', 'out', 'server.js') @@ -36,6 +45,29 @@ export function activate(context: ExtensionContext) { } }; + // The log to send + let log = '' + const websocketOutputChannel: OutputChannel = { + name: 'websocket', + // Only append the logs but send them later + append(value: string) { + log += value + console.log(value) + }, + appendLine(value: string) { + log += value + // Don't send logs until WebSocket initialization + if (socket && socket.readyState === WebSocket.OPEN) { + socket.send(log) + } + log = '' + }, + clear() {}, + show() {}, + hide() {}, + dispose() {} + } + // Options to control the language client let clientOptions: LanguageClientOptions = { // Register the server for plain text documents @@ -43,7 +75,9 @@ export function activate(context: ExtensionContext) { synchronize: { // Notify the server about file changes to '.clientrc files contained in the workspace fileEvents: workspace.createFileSystemWatcher('**/.clientrc') - } + }, + // Hijacks all LSP logs and redirect them to a specific port through WebSocket connection + outputChannel: websocketOutputChannel }; // Create the language client and start the client. diff --git a/lsp-log-streaming-sample/package.json b/lsp-log-streaming-sample/package.json index 17e3dfb1..3b43851e 100644 --- a/lsp-log-streaming-sample/package.json +++ b/lsp-log-streaming-sample/package.json @@ -21,6 +21,12 @@ ], "main": "./client/out/extension", "contributes": { + "commands": [ + { + "command": "languageServerExample.startStreaming", + "title": "Start Stream Logs into languageServerExample.port" + } + ], "configuration": { "type": "object", "title": "Example configuration", @@ -31,6 +37,12 @@ "default": 100, "description": "Controls the maximum number of problems produced by the server." }, + "languageServerExample.port": { + "type": "number", + "default": 7000, + "scope": "window", + "description": "The WebSocket port to stream LSP log data into." + }, "languageServerExample.trace.server": { "scope": "window", "type": "object",