diff --git a/multi-lsp-sample/.gitignore b/lsp-multi-root-sample/.gitignore similarity index 100% rename from multi-lsp-sample/.gitignore rename to lsp-multi-root-sample/.gitignore diff --git a/lsp-multi-root-sample/.vscode/launch.json b/lsp-multi-root-sample/.vscode/launch.json new file mode 100644 index 00000000..9615044b --- /dev/null +++ b/lsp-multi-root-sample/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + // List of configurations. Add new configurations or edit existing ones. + "configurations": [ + { + "name": "Launch Client", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}/client" ], + "stopOnEntry": false, + "sourceMaps": true, + "outFiles": [ "${workspaceRoot}/client/out/src/**/*.js" ], + "preLaunchTask": "Client Watch" + }, + { + "name": "Attach to Server", + "type": "node", + "request": "attach", + "port": 6009, + "sourceMaps": true, + "outFiles": [ "${workspaceRoot}/client/server/**/*.js" ], + "preLaunchTask": "Server Watch" + } + ] +} diff --git a/lsp-multi-root-sample/.vscode/settings.json b/lsp-multi-root-sample/.vscode/settings.json new file mode 100644 index 00000000..ed5b51ff --- /dev/null +++ b/lsp-multi-root-sample/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + "typescript.tsdk": "./node_modules/typescript/lib" +} \ No newline at end of file diff --git a/lsp-multi-root-sample/.vscode/tasks.json b/lsp-multi-root-sample/.vscode/tasks.json new file mode 100644 index 00000000..c3bccf46 --- /dev/null +++ b/lsp-multi-root-sample/.vscode/tasks.json @@ -0,0 +1,80 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "taskName": "Compile", + "dependsOn": [ + "Client Compile", + "Server Compile" + ], + "problemMatcher": [] + }, + { + "taskName": "Client Compile", + "type": "shell", + "command": "npm run client-compile", + "group": "build", + "presentation": { + "panel": "dedicated", + "reveal": "never" + + }, + "problemMatcher": [ + "$tsc" + ] + }, + { + "taskName": "Server Compile", + "type": "shell", + "command": "npm run server-compile", + "group": "build", + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": [ + "$tsc" + ] + }, + { + "taskName": "Watch", + "dependsOn": [ + "Client Watch", + "Server Watch" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + }, + { + "taskName": "Client Watch", + "type": "shell", + "command": "npm run client-watch", + "isBackground": true, + "group": "build", + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": [ + "$tsc-watch" + ] + }, + { + "taskName": "Server Watch", + "type": "shell", + "command": "npm run server-watch", + "isBackground": true, + "group": "build", + "presentation": { + "panel": "dedicated", + "reveal": "never" + }, + "problemMatcher": [ + "$tsc-watch" + ] + } + ] +} \ No newline at end of file diff --git a/lsp-multi-root-sample/License.txt b/lsp-multi-root-sample/License.txt new file mode 100644 index 00000000..dcdd1210 --- /dev/null +++ b/lsp-multi-root-sample/License.txt @@ -0,0 +1,17 @@ +Copyright (c) Microsoft Corporation + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lsp-multi-root-sample/README.md b/lsp-multi-root-sample/README.md new file mode 100644 index 00000000..c3373dfe --- /dev/null +++ b/lsp-multi-root-sample/README.md @@ -0,0 +1,16 @@ +# LSP Multi Root Example + +A language server example that demonstrates how to handle configuration settings in a workspace that uses multi root folders. Since settings in VS Code in this setupare typically scoped to a resource the example reads the resource settings from the client using the new proposed API getConfiguration. This is analogous to a reading settings in a multi root folder setup directly in the extension host. + +The example uses proposed Language Server protocol. So the code demoed here might change when the final version of the configuration and workspace folder protocol is released. + +## Compile and Run + +- run `npm install` in this folder. This installs all necessary npm modules in both the client and server folder +- open VS Code on this folder. +- Press Ctrl+Shift+B to compile the client and server +- Switch to the Debug viewlet +- Select `Launch Client` from the drop down +- Run the lauch config +- If you want to debug the server as well use the launch configuration `Attach to Server` + diff --git a/lsp-multi-root-sample/ThirdPartyNotices.txt b/lsp-multi-root-sample/ThirdPartyNotices.txt new file mode 100644 index 00000000..114129b2 --- /dev/null +++ b/lsp-multi-root-sample/ThirdPartyNotices.txt @@ -0,0 +1,31 @@ +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION +For Microsoft vscode-languageserver-node-example + +This project incorporates material from the project(s) listed below (collectively, “Third Party Code”). +Microsoft is not the original author of the Third Party Code. The original copyright notice and license +under which Microsoft received such Third Party Code are set out below. This Third Party Code is licensed +to you under their original license terms set forth below. Microsoft reserves all other rights not expressly +granted, whether by implication, estoppel or otherwise. + +1. DefinitelyTyped version 0.0.1 (https://github.com/borisyankov/DefinitelyTyped) + +This project is licensed under the MIT license. +Copyrights are respective of each contributor listed at the beginning of each definition file. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/multi-lsp-sample/client/.vscodeignore b/lsp-multi-root-sample/client/.vscodeignore similarity index 70% rename from multi-lsp-sample/client/.vscodeignore rename to lsp-multi-root-sample/client/.vscodeignore index 9ca85fe6..795e7143 100644 --- a/multi-lsp-sample/client/.vscodeignore +++ b/lsp-multi-root-sample/client/.vscodeignore @@ -1,6 +1,8 @@ .vscode/** typings/** -**/*.ts +out/test/** +test/** +src/** **/*.map .gitignore tsconfig.json diff --git a/lsp-multi-root-sample/client/package.json b/lsp-multi-root-sample/client/package.json new file mode 100644 index 00000000..c9ed8acb --- /dev/null +++ b/lsp-multi-root-sample/client/package.json @@ -0,0 +1,58 @@ +{ + "name": "lsp-mulit-root-sample-client-part", + "description": "VSCode part of a language server", + "author": "Microsoft Corporation", + "license": "MIT", + "version": "0.0.1", + "publisher": "vscode", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "engines": { + "vscode": "^1.15.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onLanguage:plaintext" + ], + "main": "./out/src/extension", + "contributes": { + "configuration": { + "type": "object", + "title": "Example configuration", + "properties": { + "lsp-multi-root-sample.maxNumberOfProblems": { + "scope": "resource", + "type": "number", + "default": 100, + "description": "Controls the maximum number of problems produced by the server." + }, + "lsp-multi-root-sample.trace.server": { + "scope": "window", + "type": "string", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "off", + "description": "Traces the communication between VSCode and the languageServerExample service." + } + } + } + }, + "scripts": { + "vscode:prepublish": "tsc -p ./", + "compile": "tsc -p ./", + "watch": "tsc -w -p ./", + "update-vscode": "node ./node_modules/vscode/bin/install", + "postinstall": "node ./node_modules/vscode/bin/install" + }, + "dependencies": { + "vscode": "^1.1.5", + "vscode-languageclient": "next" + } +} \ No newline at end of file diff --git a/lsp-multi-root-sample/client/src/extension.ts b/lsp-multi-root-sample/client/src/extension.ts new file mode 100644 index 00000000..45931b30 --- /dev/null +++ b/lsp-multi-root-sample/client/src/extension.ts @@ -0,0 +1,89 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +'use strict'; + +import * as path from 'path'; + +import { workspace, Disposable, ExtensionContext, WorkspaceConfiguration } from 'vscode'; +import { + LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, TransportKind, ProposedProtocol, Middleware, CancellationToken, + ConfigurationMiddleware, GetConfigurationParams +} from 'vscode-languageclient'; + +// The example settings +interface MultiRootExampleSettings { + maxNumberOfProblems: number; +} + +export function activate(context: ExtensionContext) { + + // The server is implemented in node + let serverModule = context.asAbsolutePath(path.join('server', 'server.js')); + // The debug options for the server + let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; + + // If the extension is launched in debug mode then the debug server options are used + // Otherwise the run options are used + let serverOptions: ServerOptions = { + run : { module: serverModule, transport: TransportKind.ipc }, + debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } + } + + // Convert VS Code specific settings to a format acceptable by the server. Since + // both client and server do use JSON the conversion is trivial. + let configurationMiddleware: ConfigurationMiddleware = { + configuration: (params: GetConfigurationParams, _token: CancellationToken, _next: Function): any[] => { + if (!params.items) { + return null; + } + let result: (MultiRootExampleSettings | null)[] = []; + for (let item of params.items) { + // The server asks the client for configuration settings without a section + // If a section is present we return null to indicate that the configuration + // is not supported. + if (item.section) { + result.push(null); + continue; + } + let config: WorkspaceConfiguration; + if (item.scopeUri) { + config = workspace.getConfiguration('lsp-multi-root-sample', client.protocol2CodeConverter.asUri(item.scopeUri)); + } else { + config = workspace.getConfiguration('lsp-multi-root-sample'); + } + result.push({ + maxNumberOfProblems: config.get('maxNumberOfProblems') + }); + } + return result; + + } + }; + + // Options to control the language client + let clientOptions: LanguageClientOptions = { + // Register the server for plain text documents + documentSelector: [{scheme: 'file', language: 'plaintext'}], + synchronize: { + // Notify the server about file changes to '.clientrc files contain in the workspace + fileEvents: workspace.createFileSystemWatcher('**/.clientrc') + }, + middleware: { + workspace: configurationMiddleware as any // cast to any due to proposed API + } + } + + // Create the language client and start the client. + let client = new LanguageClient('languageServerExample', 'Language Server Example', serverOptions, clientOptions); + // Register new propose protocol if available. + client.registerFeatures(ProposedProtocol(client)); + + // Start the client. This will also launch the server + let disposable = client.start(); + + // Push the disposable to the context's subscriptions so that the + // client can be deactivated on extension deactivation + context.subscriptions.push(disposable); +} diff --git a/lsp-multi-root-sample/client/test/extension.test.ts b/lsp-multi-root-sample/client/test/extension.test.ts new file mode 100644 index 00000000..1cec6f47 --- /dev/null +++ b/lsp-multi-root-sample/client/test/extension.test.ts @@ -0,0 +1,22 @@ +// +// Note: This example test is leveraging the Mocha test framework. +// Please refer to their documentation on https://mochajs.org/ for help. +// + +// The module 'assert' provides assertion methods from node +import * as assert from 'assert'; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +import * as vscode from 'vscode'; +import * as myExtension from '../src/extension'; + +// Defines a Mocha test suite to group tests of similar kind together +suite("Extension Tests", () => { + + // Defines a Mocha unit test + test("Something 1", () => { + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(0)); + }); +}); \ No newline at end of file diff --git a/lsp-multi-root-sample/client/test/index.ts b/lsp-multi-root-sample/client/test/index.ts new file mode 100644 index 00000000..c438886b --- /dev/null +++ b/lsp-multi-root-sample/client/test/index.ts @@ -0,0 +1,22 @@ +// +// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING +// +// This file is providing the test runner to use when running extension tests. +// By default the test runner in use is Mocha based. +// +// You can provide your own test runner if you want to override it by exporting +// a function run(testRoot: string, clb: (error:Error) => void) that the extension +// host can call to run the tests. The test runner is expected to use console.log +// to report the results back to the caller. When the tests are finished, return +// a possible error to the callback or null if none. + +var testRunner = require('vscode/lib/testrunner'); + +// You can directly control Mocha options by uncommenting the following lines +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info +testRunner.configure({ + ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) + useColors: true // colored output from test results +}); + +module.exports = testRunner; \ No newline at end of file diff --git a/multi-lsp-sample/client/tsconfig.json b/lsp-multi-root-sample/client/tsconfig.json similarity index 51% rename from multi-lsp-sample/client/tsconfig.json rename to lsp-multi-root-sample/client/tsconfig.json index 63d77358..7d7667e1 100644 --- a/multi-lsp-sample/client/tsconfig.json +++ b/lsp-multi-root-sample/client/tsconfig.json @@ -1,13 +1,10 @@ { "compilerOptions": { - "noUnusedLocals": true, - "noUnusedParameters": true, - "noImplicitAny": true, - "noImplicitReturns": true, - "module": "commonjs", "target": "es6", + "module": "commonjs", + "moduleResolution": "node", "outDir": "out", - "lib": [ "es6"], + "lib": [ "es2016" ], "sourceMap": true }, "exclude": [ diff --git a/lsp-multi-root-sample/package.json b/lsp-multi-root-sample/package.json new file mode 100644 index 00000000..df23ba91 --- /dev/null +++ b/lsp-multi-root-sample/package.json @@ -0,0 +1,25 @@ +{ + "name": "lsp-mulit-root-sample", + "description": "A language server example demoing multi root workspaces", + "author": "Microsoft Corporation", + "license": "MIT", + "version": "0.0.1", + "publisher": "vscode", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "scripts": { + "postinstall": "cd server && npm install && cd ../client && npm install && cd ..", + "compile": "tsc -p client/tsconfig.json && cd server && npm run installServer && cd .. && tsc -p server/tsconfig.json", + "client-compile": "tsc -p client/tsconfig.json", + "client-watch": "tsc -w -p client/tsconfig.json", + "server-compile": "cd server && npm run installServer && cd .. && tsc -p server/tsconfig.json", + "server-watch": "cd server && npm run installServer && cd .. && tsc -w -p server/tsconfig.json" + }, + "devDependencies": { + "@types/mocha": "^2.2.42", + "@types/node": "^6.0.88", + "typescript": "^2.5.2" + } +} \ No newline at end of file diff --git a/lsp-multi-root-sample/server/package.json b/lsp-multi-root-sample/server/package.json new file mode 100644 index 00000000..7f6b994e --- /dev/null +++ b/lsp-multi-root-sample/server/package.json @@ -0,0 +1,22 @@ +{ + "name": "lsp-mulit-root-sample-server-part", + "description": "Example implementation of a language server in node.", + "version": "0.0.1", + "author": "Microsoft Corporation", + "license": "MIT", + "engines": { + "node": "*" + }, + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/vscode-extension-samples" + }, + "dependencies": { + "vscode-languageserver": "next" + }, + "scripts": { + "installServer": "installServerIntoExtension ../client ./package.json ./tsconfig.json", + "compile": "installServerIntoExtension ../client ./package.json ./tsconfig.json && tsc -p .", + "watch": "installServerIntoExtension ../client ./package.json ./tsconfig.json && tsc -w -p ." + } +} \ No newline at end of file diff --git a/lsp-multi-root-sample/server/src/server.ts b/lsp-multi-root-sample/server/src/server.ts new file mode 100644 index 00000000..ac4de968 --- /dev/null +++ b/lsp-multi-root-sample/server/src/server.ts @@ -0,0 +1,125 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + * ------------------------------------------------------------------------------------------ */ +'use strict'; + +import { + IPCMessageReader, IPCMessageWriter, + createConnection, IConnection, TextDocumentSyncKind, + TextDocuments, TextDocument, Diagnostic, DiagnosticSeverity, + InitializeParams, InitializeResult, TextDocumentPositionParams, + DidChangeConfigurationNotification, ClientCapabilities, + CompletionItem, CompletionItemKind, StreamMessageReader, StreamMessageWriter, ProposedProtocol, + ConfigurationItem +} from 'vscode-languageserver'; + +// Create a connection for the server. The connection uses Node's IPC as a transport +let connection = createConnection(ProposedProtocol); + +// Create a simple text document manager. The text document manager +// supports full document sync only +let documents: TextDocuments = new TextDocuments(); + +connection.onInitialize((params): InitializeResult => { + return { + capabilities: { + // Tell the client that the server works in FULL text document sync mode + textDocumentSync: documents.syncKind, + // Tell the client that the server support code complete + completionProvider: { + resolveProvider: true + } + } + } +}); + +// The example settings +interface MultiRootExampleSettings { + maxNumberOfProblems: number; +} + +let settings: Map> = new Map(); + +function getConfiguration(resource: string): Thenable { + let result = settings.get(resource); + if (!result) { + result = connection.workspace.getConfiguration({ section: '', scopeUri: resource }); + settings.set(resource, result); + } + return result; +} + +connection.onInitialized(() => { + // Register to configuration change events. + connection.client.register(DidChangeConfigurationNotification.type); +}); + + +connection.onNotification(DidChangeConfigurationNotification.type, () => { + let toRequest: ConfigurationItem[] = []; + for (let resource of settings.keys()) { + toRequest.push({ section: '', scopeUri: resource}); + } + settings.clear(); + // Reread all cached configuration + connection.workspace.getConfiguration(toRequest).then((values: MultiRootExampleSettings[]) => { + let toRevalidate: string[] = []; + for (let i = 0; i < values.length; i++) { + let resource = toRequest[i].scopeUri; + let value = values[i]; + // If the value got already added to the settings cache then a change has + // occurred before the configuration request got return. Ignore the value + // in this case. + if (value && !settings.has(resource)) { + settings.set(resource, Promise.resolve(value)); + toRevalidate.push(resource); + } + } + for (let resource of toRevalidate) { + validateTextDocument(documents.get(resource)); + } + }); +}); + + +// The content of a text document has changed. This event is emitted +// when the text document first opened or when its content has changed. +documents.onDidChangeContent((change) => { + validateTextDocument(change.document); +}); + + +function validateTextDocument(textDocument: TextDocument): void { + // In this simple example we get the settings for every validate run. + getConfiguration(textDocument.uri).then((settings: MultiRootExampleSettings) => { + let diagnostics: Diagnostic[] = []; + let lines = textDocument.getText().split(/\r?\n/g); + let problems = 0; + for (var i = 0; i < lines.length && problems < settings.maxNumberOfProblems; i++) { + let line = lines[i]; + let index = line.indexOf('typescript'); + if (index >= 0) { + problems++; + diagnostics.push({ + severity: DiagnosticSeverity.Warning, + range: { + start: { line: i, character: index}, + end: { line: i, character: index + 10 } + }, + message: `${line.substr(index, 10)} should be spelled TypeScript`, + source: 'ex' + }); + } + } + // Send the computed diagnostics to VSCode. + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + }); +} + +// Make the text document manager listen on the connection +// for open, change and close text document events +documents.listen(connection); + +// Listen on the connection +connection.listen(); \ No newline at end of file diff --git a/lsp-multi-root-sample/server/tsconfig.json b/lsp-multi-root-sample/server/tsconfig.json new file mode 100644 index 00000000..6032b82b --- /dev/null +++ b/lsp-multi-root-sample/server/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "lib" : [ "es2016" ], + "outDir": "../client/server" + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/package.json b/package.json index 2e5d5ea9..e4c4cf38 100644 --- a/package.json +++ b/package.json @@ -11,4 +11,4 @@ "compile-languageprovider": "cd languageprovider-sample && npm run compile && cd ..", "compile-statusbar": "cd statusbar-sample && npm run compile && cd .." } -} +} \ No newline at end of file