diff --git a/multi-lsp-sample/.gitignore b/multi-lsp-sample/.gitignore new file mode 100644 index 00000000..4228f913 --- /dev/null +++ b/multi-lsp-sample/.gitignore @@ -0,0 +1,3 @@ +out +node_modules +client/server \ No newline at end of file diff --git a/multi-lsp-sample/client/.vscode/launch.json b/multi-lsp-sample/client/.vscode/launch.json new file mode 100644 index 00000000..82f0b92d --- /dev/null +++ b/multi-lsp-sample/client/.vscode/launch.json @@ -0,0 +1,17 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Launch Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": ["${workspaceRoot}/out/**/*.js"], + "preLaunchTask": "npm: watch" + } + ] +} \ No newline at end of file diff --git a/multi-lsp-sample/client/.vscode/settings.json b/multi-lsp-sample/client/.vscode/settings.json new file mode 100644 index 00000000..3734074a --- /dev/null +++ b/multi-lsp-sample/client/.vscode/settings.json @@ -0,0 +1,13 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true + }, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "typescript.tsdk": "./node_modules/typescript/lib", "testbed.trace.server": "verbose" + , "testbed.server.trace": "verbose" +} \ No newline at end of file diff --git a/multi-lsp-sample/client/.vscode/tasks.json b/multi-lsp-sample/client/.vscode/tasks.json new file mode 100644 index 00000000..8562f02a --- /dev/null +++ b/multi-lsp-sample/client/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "isBackground": true, + "problemMatcher": [ + "$tsc-watch" + ], + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/multi-lsp-sample/client/.vscodeignore b/multi-lsp-sample/client/.vscodeignore new file mode 100644 index 00000000..9ca85fe6 --- /dev/null +++ b/multi-lsp-sample/client/.vscodeignore @@ -0,0 +1,7 @@ +.vscode/** +typings/** +**/*.ts +**/*.map +.gitignore +tsconfig.json +vsc-extension-quickstart.md diff --git a/multi-lsp-sample/client/extension.ts b/multi-lsp-sample/client/extension.ts new file mode 100644 index 00000000..bd1c823d --- /dev/null +++ b/multi-lsp-sample/client/extension.ts @@ -0,0 +1,100 @@ +import * as path from 'path'; +import { + workspace as Workspace, window as Window, ExtensionContext, TextDocument, OutputChannel +} from 'vscode'; + +import { + LanguageClient, LanguageClientOptions, TransportKind, ProposedProtocol +} from 'vscode-languageclient'; + +let defaultClient: LanguageClient; +let clients: Map = new Map(); + +export function activate(_context: ExtensionContext) { + + + let module = path.join(__dirname, '..', 'server', 'server.js'); + let outputChannel: OutputChannel = Window.createOutputChannel('Multi-LSP-Example'); + + function didOpenTextDocument(document: TextDocument): void { + // We are only interested in language mode text + if (document.languageId !== 'plaintext' || (document.uri.scheme !== 'file' && document.uri.scheme !== 'untitled')) { + return; + } + + let uri = document.uri; + // Untitled files go to a default client. + if (uri.scheme === 'untitled' && !defaultClient) { + let debugOptions = { execArgv: ["--nolazy", "--inspect=6010"] }; + let serverOptions = { + run: { module, transport: TransportKind.ipc }, + debug: { module, transport: TransportKind.ipc, options: debugOptions} + }; + let clientOptions: LanguageClientOptions = { + documentSelector: [ + { scheme: 'untitled', language: 'plaintext' } + ], + synchronize: { + configurationSection: 'multi-lsp' + }, + diagnosticCollectionName: 'multi-lsp', + outputChannel: outputChannel + } + defaultClient = new LanguageClient('multi-lsp', 'Multi-LSP', serverOptions, clientOptions); + defaultClient.registerFeatures(ProposedProtocol(defaultClient)); + defaultClient.start(); + } + let folder = Workspace.getWorkspaceFolder(uri); + // Files outside a folder can't be handled. This might depend on the language + // Single file languages like JSON might handle files outside the workspace folders. + if (!folder) { + return; + } + if (!clients.has(folder.uri.toString())) { + let debugOptions = { execArgv: ["--nolazy", `--inspect=${6011 + clients.size}`] }; + let serverOptions = { + run: { module, transport: TransportKind.ipc }, + debug: { module, transport: TransportKind.ipc, options: debugOptions} + }; + let clientOptions: LanguageClientOptions = { + documentSelector: [ + { scheme: 'file', language: 'plaintext', pattern: `${folder.uri.fsPath}/**/*` } + ], + synchronize: { + configurationSection: 'multi-lsp' + }, + diagnosticCollectionName: 'multi-lsp', + workspaceFolder: folder, + outputChannel: outputChannel + } + let client = new LanguageClient('multi-lsp', 'Multi-LSP', serverOptions, clientOptions); + client.registerFeatures(ProposedProtocol(client)); + client.start(); + clients.set(folder.uri.toString(), client); + + } + } + + Workspace.onDidOpenTextDocument(didOpenTextDocument); + Workspace.textDocuments.forEach(didOpenTextDocument); + Workspace.onDidChangeWorkspaceFolders((event) => { + for (let folder of event.removed) { + let client = clients.get(folder.uri.toString()); + if (client) { + clients.delete(folder.uri.toString()); + client.stop(); + } + } + }); +} + +export function deactivate(): Thenable { + let promises: Thenable[] = []; + if (defaultClient) { + promises.push(defaultClient.stop()); + } + for (let client of clients.values()) { + promises.push(client.stop()); + } + return Promise.all(promises).then(() => undefined); +} \ No newline at end of file diff --git a/multi-lsp-sample/client/package.json b/multi-lsp-sample/client/package.json new file mode 100644 index 00000000..e2f9d897 --- /dev/null +++ b/multi-lsp-sample/client/package.json @@ -0,0 +1,46 @@ +{ + "name": "multi-lsp", + "description": "Example showing multiple LSP servers running", + "version": "0.0.1", + "publisher": "vscode", + "engines": { + "vscode": "^1.15.0" + }, + "activationEvents": [ + "onLanguage:plaintext" + ], + "contributes": { + "configuration": { + "type": "object", + "title": "Multi LSP configuration", + "properties": { + "multi-lsp.enable": { + "scope": "resource", + "type": "boolean", + "default": true, + "description": "Controls the enablement." + }, + "multi-lsp.options": { + "scope": "resource", + "type": "object", + "default": {}, + "description": "Additional options." + } + } + } + }, + "main": "./out/extension", + "scripts": { + "vscode:prepublish": "node ./node_modules/vscode/bin/compile", + "watch": "tsc -watch -p ./", + "update-vscode": "node ./node_modules/vscode/bin/install" + }, + "devDependencies": { + "vscode": "^1.1.5", + "typescript": "^2.4.2", + "@types/node": "^6.0.87" + }, + "dependencies": { + "vscode-languageclient": "next" + } +} \ No newline at end of file diff --git a/multi-lsp-sample/client/tsconfig.json b/multi-lsp-sample/client/tsconfig.json new file mode 100644 index 00000000..63d77358 --- /dev/null +++ b/multi-lsp-sample/client/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ "es6"], + "sourceMap": true + }, + "exclude": [ + "node_modules", + "server" + ] +} \ No newline at end of file diff --git a/multi-lsp-sample/server/.vscode/launch.json b/multi-lsp-sample/server/.vscode/launch.json new file mode 100644 index 00000000..204d763d --- /dev/null +++ b/multi-lsp-sample/server/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.1.0", + // List of configurations. Add new configurations or edit existing ones. + "configurations": [ + { + "name": "Attach", + "type": "node", + "request": "attach", + // TCP/IP address. Default is "localhost". + "address": "localhost", + // Port to attach to. + "port": 6012, + "protocol": "inspector", + "sourceMaps": false, + "outDir": "${workspaceRoot}/../testbed/server" + } + ] +} \ No newline at end of file diff --git a/multi-lsp-sample/server/.vscode/settings.json b/multi-lsp-sample/server/.vscode/settings.json new file mode 100644 index 00000000..76145fca --- /dev/null +++ b/multi-lsp-sample/server/.vscode/settings.json @@ -0,0 +1,8 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "javascript.validate.enable": false, + "files.trimTrailingWhitespace": true, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "typescript.tsdk": "./node_modules/typescript/lib" +} \ No newline at end of file diff --git a/multi-lsp-sample/server/.vscode/tasks.json b/multi-lsp-sample/server/.vscode/tasks.json new file mode 100644 index 00000000..13f1bf14 --- /dev/null +++ b/multi-lsp-sample/server/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "isBackground": true, + "problemMatcher": [ + "$tsc-watch" + ], + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} \ No newline at end of file diff --git a/multi-lsp-sample/server/package.json b/multi-lsp-sample/server/package.json new file mode 100644 index 00000000..703ea029 --- /dev/null +++ b/multi-lsp-sample/server/package.json @@ -0,0 +1,21 @@ +{ + "name": "multi-lsp-server", + "version": "0.1.0", + "description": "Multi server example", + "engines": { + "node": "*" + }, + "private": true, + "main": "./lib/server.js", + "dependencies": { + "vscode-languageserver": "next" + }, + "devDependencies": { + "typescript": "^2.4.2", + "@types/node": "^6.0.87" + }, + "scripts": { + "compile": "installServerIntoExtension ../client ./package.json ./src/tsconfig.json && tsc -p ./src", + "watch": "installServerIntoExtension ../client ./package.json ./src/tsconfig.json && tsc --watch -p ./src" + } +} \ No newline at end of file diff --git a/multi-lsp-sample/server/src/server.ts b/multi-lsp-sample/server/src/server.ts new file mode 100644 index 00000000..75d90320 --- /dev/null +++ b/multi-lsp-sample/server/src/server.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ +'use strict'; + +import { + createConnection, TextDocuments, ProposedProtocol, TextDocumentSyncKind +} from 'vscode-languageserver'; + +let connection = createConnection(ProposedProtocol); +let documents = new TextDocuments(); +let rootUri: string; + +documents.onDidOpen((event) => { + connection.console.log(`[Server ${rootUri}] Document opened: ${event.document.uri}`); +}) +documents.listen(connection); + +connection.onInitialize((params) => { + rootUri = params.rootUri; + connection.console.log(`Server started for folder: ${rootUri}`); + return { + capabilities: { + textDocumentSync: { + openClose: true, + change: TextDocumentSyncKind.None + } + } + } +}); +connection.listen(); \ No newline at end of file diff --git a/multi-lsp-sample/server/src/tsconfig.json b/multi-lsp-sample/server/src/tsconfig.json new file mode 100644 index 00000000..c60be84b --- /dev/null +++ b/multi-lsp-sample/server/src/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitAny": true, + "noImplicitReturns": true, + "module": "commonjs", + "moduleResolution": "node", + "target": "es6", + "lib": ["es6"], + "sourceMap": true, + "outDir": "../../client/server" + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file