From 7ba50545d933a9e1e2627384414be2816661a10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Sch=C3=A4fer?= Date: Tue, 24 Sep 2019 19:54:16 +0200 Subject: [PATCH 1/4] [FEATURE][VSC] Add basic language client functionality The Saros implementation will be based on the Language Server Protocol (see https://microsoft.github.io/language-server-protocol) for data exchange. In order to enable VS Code to understand that protocol the VS Code language client has been added. Furthermore starting of the Saros LSP Server has been encapsulated. --- .gitignore | 7 + vscode/.gitignore | 105 ++++++++++++++ vscode/.vscode/extensions.json | 7 + vscode/.vscode/launch.json | 36 +++++ vscode/.vscode/tasks.json | 20 +++ vscode/.vscodeignore | 10 ++ vscode/CHANGELOG.md | 9 ++ vscode/README.md | 65 +++++++++ vscode/package.json | 64 +++++++++ vscode/src/account/activator.ts | 20 +++ vscode/src/core/saros-client.ts | 46 ++++++ vscode/src/core/saros-extension.ts | 104 ++++++++++++++ vscode/src/core/saros-server.ts | 177 ++++++++++++++++++++++++ vscode/src/core/types.ts | 0 vscode/src/extension.ts | 51 +++++++ vscode/src/test/runTest.ts | 23 +++ vscode/src/test/suite/extension.test.ts | 18 +++ vscode/src/test/suite/index.ts | 37 +++++ vscode/tsconfig.json | 21 +++ vscode/tslint.json | 15 ++ vscode/vsc-extension-quickstart.md | 42 ++++++ 21 files changed, 877 insertions(+) create mode 100644 vscode/.gitignore create mode 100644 vscode/.vscode/extensions.json create mode 100644 vscode/.vscode/launch.json create mode 100644 vscode/.vscode/tasks.json create mode 100644 vscode/.vscodeignore create mode 100644 vscode/CHANGELOG.md create mode 100644 vscode/README.md create mode 100644 vscode/package.json create mode 100644 vscode/src/account/activator.ts create mode 100644 vscode/src/core/saros-client.ts create mode 100644 vscode/src/core/saros-extension.ts create mode 100644 vscode/src/core/saros-server.ts create mode 100644 vscode/src/core/types.ts create mode 100644 vscode/src/extension.ts create mode 100644 vscode/src/test/runTest.ts create mode 100644 vscode/src/test/suite/extension.test.ts create mode 100644 vscode/src/test/suite/index.ts create mode 100644 vscode/tsconfig.json create mode 100644 vscode/tslint.json create mode 100644 vscode/vsc-extension-quickstart.md diff --git a/.gitignore b/.gitignore index 875bff955c..3a1f05199a 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,10 @@ buildSrc/.gradle # LSP lsp/.classpath lsp/build + +# VS Code +vscode*/.classpath +vscode*/.project +vscode*/build +vscode*/package-lock.json +vscode*/.vscode diff --git a/vscode/.gitignore b/vscode/.gitignore new file mode 100644 index 0000000000..3e84417c23 --- /dev/null +++ b/vscode/.gitignore @@ -0,0 +1,105 @@ +# VS Code +.vscode/* +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +out/* + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# gatsby files +.cache/ +public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ diff --git a/vscode/.vscode/extensions.json b/vscode/.vscode/extensions.json new file mode 100644 index 0000000000..0a18b9c4bd --- /dev/null +++ b/vscode/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "ms-vscode.vscode-typescript-tslint-plugin" + ] +} \ No newline at end of file diff --git a/vscode/.vscode/launch.json b/vscode/.vscode/launch.json new file mode 100644 index 0000000000..f5197783a4 --- /dev/null +++ b/vscode/.vscode/launch.json @@ -0,0 +1,36 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "npm: watch" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" + ], + "outFiles": [ + "${workspaceFolder}/out/test/**/*.js" + ], + "preLaunchTask": "npm: watch" + } + ] +} diff --git a/vscode/.vscode/tasks.json b/vscode/.vscode/tasks.json new file mode 100644 index 0000000000..3b17e53b62 --- /dev/null +++ b/vscode/.vscode/tasks.json @@ -0,0 +1,20 @@ +// See https://go.microsoft.com/fwlink/?LinkId=733558 +// for the documentation about the tasks.json format +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/vscode/.vscodeignore b/vscode/.vscodeignore new file mode 100644 index 0000000000..ed3f9d37c1 --- /dev/null +++ b/vscode/.vscodeignore @@ -0,0 +1,10 @@ +.vscode/** +.vscode-test/** +out/test/** +src/** +.gitignore +vsc-extension-quickstart.md +**/tsconfig.json +**/tslint.json +**/*.map +**/*.ts \ No newline at end of file diff --git a/vscode/CHANGELOG.md b/vscode/CHANGELOG.md new file mode 100644 index 0000000000..14e49f2bbf --- /dev/null +++ b/vscode/CHANGELOG.md @@ -0,0 +1,9 @@ +# Change Log + +All notable changes to the "saros" extension will be documented in this file. + +Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. + +## [Unreleased] + +- Initial release \ No newline at end of file diff --git a/vscode/README.md b/vscode/README.md new file mode 100644 index 0000000000..d081f1ac79 --- /dev/null +++ b/vscode/README.md @@ -0,0 +1,65 @@ +# saros README + +This is the README for your extension "saros". After writing up a brief description, we recommend including the following sections. + +## Features + +Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file. + +For example if there is an image subfolder under your extension project workspace: + +\!\[feature X\]\(images/feature-x.png\) + +> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow. + +## Requirements + +If you have any requirements or dependencies, add a section describing those and how to install and configure them. + +## Extension Settings + +Include if your extension adds any VS Code settings through the `contributes.configuration` extension point. + +For example: + +This extension contributes the following settings: + +* `myExtension.enable`: enable/disable this extension +* `myExtension.thing`: set to `blah` to do something + +## Known Issues + +Calling out known issues can help limit users opening duplicate issues against your extension. + +## Release Notes + +Users appreciate release notes as you update your extension. + +### 1.0.0 + +Initial release of ... + +### 1.0.1 + +Fixed issue #. + +### 1.1.0 + +Added features X, Y, and Z. + +----------------------------------------------------------------------------------------------------------- + +## Working with Markdown + +**Note:** You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts: + +* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux) +* Toggle preview (`Shift+CMD+V` on macOS or `Shift+Ctrl+V` on Windows and Linux) +* Press `Ctrl+Space` (Windows, Linux) or `Cmd+Space` (macOS) to see a list of Markdown snippets + +### For more information + +* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown) +* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/) + +**Enjoy!** diff --git a/vscode/package.json b/vscode/package.json new file mode 100644 index 0000000000..7087063046 --- /dev/null +++ b/vscode/package.json @@ -0,0 +1,64 @@ +{ + "name": "saros", + "displayName": "saros", + "description": "Saros implementation for VS Code.", + "version": "0.0.1", + "engines": { + "vscode": "^1.38.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "workspaceContains:*" + ], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "saros.account.add", + "title": "Add Account", + "category": "Saros" + } + ], + "configuration": { + "type": "object", + "title": "Example configuration", + "properties": { + "sarosServer.trace.server": { + "scope": "window", + "type": "string", + "enum": [ + "off", + "messages", + "verbose" + ], + "default": "verbose", + "description": "Traces the communication between VS Code and the Saros server." + } + } + } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "pretest": "npm run compile", + "test": "node ./out/test/runTest.js" + }, + "devDependencies": { + "@types/glob": "^7.1.1", + "@types/mocha": "^5.2.6", + "@types/node": "^10.12.21", + "@types/vscode": "^1.38.0", + "glob": "^7.1.4", + "mocha": "^6.1.4", + "typescript": "^3.3.1", + "tslint": "^5.12.1", + "vscode-test": "^1.2.0" + }, + "dependencies": { + "node-jre": "^0.2.3", + "vscode-languageclient": "^5.2.1" + } +} diff --git a/vscode/src/account/activator.ts b/vscode/src/account/activator.ts new file mode 100644 index 0000000000..449d204b59 --- /dev/null +++ b/vscode/src/account/activator.ts @@ -0,0 +1,20 @@ +import { SarosExtension } from "../core/saros-extension"; +import { commands } from "vscode"; + +/** + * Activation function of the account module. + * + * @export + * @param {SarosExtension} extension - The instance of the extension + */ +export function activateAccounts(extension: SarosExtension) { + commands.registerCommand('saros.account.add', () => { + extension.onReady() + .then(resolve => { + return extension.client.addAccount('micha@jabber.com'); + }) + .then(r => { + console.log('Response was: ' + r.Response); + }); + }); +} \ No newline at end of file diff --git a/vscode/src/core/saros-client.ts b/vscode/src/core/saros-client.ts new file mode 100644 index 0000000000..d5d05010ed --- /dev/null +++ b/vscode/src/core/saros-client.ts @@ -0,0 +1,46 @@ +import { LanguageClient } from "vscode-languageclient"; + +/** + * Response for adding new accounts. + * + * @export + * @interface AddAccountResponse + */ +export interface AddAccountResponse { + Response: boolean; +} + +/** + * Request for adding new accounts. + * + * @export + * @interface AddAccountRequest + */ +export interface AddAccountRequest { + +} + +/** + * Custom language client for Saros protocol. + * + * @export + * @class SarosClient + * @extends {LanguageClient} + */ +export class SarosClient extends LanguageClient { + + /** + * Adds a new account. + * + * @param {string} name - Account identifier + * @returns {Thenable} The result + * @memberof SarosClient + */ + addAccount(name: string): Thenable { + const request: AddAccountRequest = { + + }; + + return this.sendRequest("saros/account/add", request); + } +} \ No newline at end of file diff --git a/vscode/src/core/saros-extension.ts b/vscode/src/core/saros-extension.ts new file mode 100644 index 0000000000..e13395940e --- /dev/null +++ b/vscode/src/core/saros-extension.ts @@ -0,0 +1,104 @@ +import { ExtensionContext, workspace, window } from "vscode"; +import { SarosServer } from "./saros-server"; +import { SarosClient } from "./saros-client"; +import { LanguageClientOptions, RevealOutputChannelOn } from "vscode-languageclient"; + +/** + * The Saros extension. + * + * @export + * @class SarosExtension + */ +export class SarosExtension { + private context!: ExtensionContext; + public client!: SarosClient; + + /** + * Creates an instance of SarosExtension. + * + * @memberof SarosExtension + */ + constructor() { + + } + + /** + * Sets the context the extension runs on. + * + * @param {ExtensionContext} context - The extension context + * @returns {SarosExtension} Itself + * @memberof SarosExtension + */ + setContext(context: ExtensionContext): SarosExtension { + this.context = context; + + return this; + } + + /** + * Initializes the extension. + * + * @returns + * @memberof SarosExtension + */ + async init() { + if(!this.context) { + return Promise.reject('Context not set'); + } + + try { + let self = this; + + return new Promise((resolve, reject) => { + const server = new SarosServer(self.context); + self.client = new SarosClient('sarosServer', 'Saros Server', server.getStartFunc(), this.createClientOptions()); + self.context.subscriptions.push(self.client.start()); + + resolve(); + }); + } catch(ex) { + const msg = "Error while activating plugin. " + (ex.message ? ex.message : ex); + return Promise.reject(msg); + } + } + + /** + * Callback when extension is ready. + * + * @returns + * @memberof SarosExtension + */ + async onReady() { + if(!this.client) { + return Promise.reject('SarosExtension is not initialized'); + } + + return this.client.onReady(); + } + + /** + * Creates the client options. + * + * @private + * @returns {LanguageClientOptions} The client options + * @memberof SarosExtension + */ + private createClientOptions(): LanguageClientOptions { + let clientOptions: LanguageClientOptions = { + // Register the server for plain text documents + documentSelector: ['plaintext'], + synchronize: { + // Synchronize the setting section 'languageServerExample' to the server + //configurationSection: 'sarosServer', + // Notify the server about file changes to '.clientrc files contain in the workspace + fileEvents: workspace.createFileSystemWatcher('**/.clientrc') + }, + outputChannel: window.createOutputChannel('Saros'), + revealOutputChannelOn: RevealOutputChannelOn.Info + }; + + return clientOptions; + } +} + +export const sarosExtensionInstance = new SarosExtension(); \ No newline at end of file diff --git a/vscode/src/core/saros-server.ts b/vscode/src/core/saros-server.ts new file mode 100644 index 0000000000..982ece6761 --- /dev/null +++ b/vscode/src/core/saros-server.ts @@ -0,0 +1,177 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as process from 'child_process'; +import * as net from 'net'; +import { StreamInfo } from 'vscode-languageclient'; + +/** + * Encapsulation of the Saros server. + * + * @export + * @class SarosServer + */ +export class SarosServer { + + /** + * Started process of the server. + * + * @private + * @type {process.ChildProcess} + * @memberof SarosServer + */ + private process?: process.ChildProcess; + + /** + * Creates an instance of SarosServer. + * + * @param {vscode.ExtensionContext} context - The extension context + * @memberof SarosServer + */ + constructor(private context: vscode.ExtensionContext) { + + } + + /** + * Starts the server process. + * + * @param {number} port - The port the server listens on for connection + * @memberof SarosServer + */ + public start(port: number): void { + + if(this.process !== undefined) { + throw new Error('Server process is still running'); + } + + this.startProcess(port) + .withDebug(true) + .withExitAware(); + } + + /** + * Provides access to the start function. + * + * @remarks A free port will be determined and used. + * @returns {() => Thenable} Function that starts the server and retuns the io information + * @memberof SarosServer + */ + public getStartFunc(): () => Thenable { + + let self = this; + function createServer(): Thenable { + return new Promise((resolve, reject) => { + var server = net.createServer((socket) => { + console.log("Creating server"); + + resolve({ + reader: socket, + writer: socket + }); + + socket.on('end', () => console.log("Disconnected")); + }).on('error', (err) => { + // handle errors here + throw err; + }); + + // grab a random port. + server.listen(() => { + let port = (server.address() as net.AddressInfo).port; + + self.start(port); + }); + }); + } + + return createServer; + } + + /** + * Starts the Saros server jar as process. + * + * @private + * @param {...any[]} args - Additional command line arguments for the server + * @returns {SarosServer} Itself + * @memberof SarosServer + */ + private startProcess(...args: any[]): SarosServer { + + var pathToJar = path.resolve(this.context.extensionPath, 'out', 'saros.vscode.java.jar'); + var jre = require('node-jre'); + + console.log('spawning jar process'); + this.process = jre.spawn( + [pathToJar], + 'saros.lsp.SarosLauncher', + args, + { encoding: 'utf8' } + ) as process.ChildProcess; + + return this; + } + + /** + * Attaches listeners for debug informations and prints + * retrieved data to a newly created [output channel](#vscode.OutputChannel). + * + * @private + * @param {boolean} isEnabled - Wether debug output is redirected or not + * @returns {SarosServer} Itself + * @memberof SarosServer + */ + private withDebug(isEnabled: boolean): SarosServer { + + if(this.process === undefined) { + throw new Error('Server process is undefined'); + } + + if(!isEnabled) { + return this; + } + + let output = vscode.window.createOutputChannel('Saros (Debug)'); + + this.process.stdout.on("data", (data) => { + output.appendLine(data); + }); + + this.process.stderr.on("data", (data) => { + output.appendLine(data); + }); + + return this; + } + + /** + * Attaches listeners to observe termination of the server. + * + * @private + * @returns {SarosServer} Itself + * @memberof SarosServer + */ + private withExitAware(): SarosServer { + + if(this.process === undefined) { + throw new Error('Server process is undefined'); + } + + this.process.on('error', (error) => { + vscode.window.showErrorMessage(`child process creating error with error ${error}`); + }); + + let self = this; + this.process.on('close', (code) => { + var showMessageFunc; + if(code === 0) { + showMessageFunc = vscode.window.showInformationMessage; + } else { + showMessageFunc = vscode.window.showWarningMessage; + } + + self.process = undefined; + showMessageFunc(`child process exited with code ${code}`); + }); + + return this; + } +} \ No newline at end of file diff --git a/vscode/src/core/types.ts b/vscode/src/core/types.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts new file mode 100644 index 0000000000..83066c448b --- /dev/null +++ b/vscode/src/extension.ts @@ -0,0 +1,51 @@ +import * as vscode from 'vscode'; +import { Disposable } from 'vscode-jsonrpc'; +import { sarosExtensionInstance } from './core/saros-extension'; +import { activateAccounts } from './account/activator'; + +/** + * Activation function of the extension. + * + * @export + * @param {vscode.ExtensionContext} context - The extension context + */ +export function activate(context: vscode.ExtensionContext) { + + sarosExtensionInstance.setContext(context) + .init() + .then(() => { + activateAccounts(sarosExtensionInstance); + + console.log('Extension "Saros" is now active!'); + }) + .catch(reason => { + vscode.window.showErrorMessage('Saros extension did not start propertly.' + + 'Reason: ' + reason); //TODO: restart feature + }); + + context.subscriptions.push(createStatusBar()); +} + +/** + * Creates the status bar. + * + * @returns {Disposable} The status bar item as [disposable](#Disposable) + */ +function createStatusBar(): Disposable { + + let statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, Number.MAX_VALUE); + statusBarItem.text = "Saros"; + statusBarItem.command = "saros.start"; + statusBarItem.show(); + + return statusBarItem; +} + +/** + * Deactivation function of the extension. + * + * @export + */ +export function deactivate() { + console.log("deactivated"); +} diff --git a/vscode/src/test/runTest.ts b/vscode/src/test/runTest.ts new file mode 100644 index 0000000000..1eabfa33d2 --- /dev/null +++ b/vscode/src/test/runTest.ts @@ -0,0 +1,23 @@ +import * as path from 'path'; + +import { runTests } from 'vscode-test'; + +async function main() { + try { + // The folder containing the Extension Manifest package.json + // Passed to `--extensionDevelopmentPath` + const extensionDevelopmentPath = path.resolve(__dirname, '../../'); + + // The path to test runner + // Passed to --extensionTestsPath + const extensionTestsPath = path.resolve(__dirname, './suite/index'); + + // Download VS Code, unzip it and run the integration test + await runTests({ extensionDevelopmentPath, extensionTestsPath }); + } catch (err) { + console.error('Failed to run tests'); + process.exit(1); + } +} + +main(); diff --git a/vscode/src/test/suite/extension.test.ts b/vscode/src/test/suite/extension.test.ts new file mode 100644 index 0000000000..820cf9065a --- /dev/null +++ b/vscode/src/test/suite/extension.test.ts @@ -0,0 +1,18 @@ +import * as assert from 'assert'; +import { before } from 'mocha'; + +// 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 '../extension'; + +suite('Extension Test Suite', () => { + before(() => { + vscode.window.showInformationMessage('Start all tests.'); + }); + + test('Sample test', () => { + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(0)); + }); +}); diff --git a/vscode/src/test/suite/index.ts b/vscode/src/test/suite/index.ts new file mode 100644 index 0000000000..2cd152c295 --- /dev/null +++ b/vscode/src/test/suite/index.ts @@ -0,0 +1,37 @@ +import * as path from 'path'; +import * as Mocha from 'mocha'; +import * as glob from 'glob'; + +export function run(): Promise { + // Create the mocha test + const mocha = new Mocha({ + ui: 'tdd', + }); + mocha.useColors(true); + + const testsRoot = path.resolve(__dirname, '..'); + + return new Promise((c, e) => { + glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { + if (err) { + return e(err); + } + + // Add files to the test suite + files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); + + try { + // Run the mocha test + mocha.run(failures => { + if (failures > 0) { + e(new Error(`${failures} tests failed.`)); + } else { + c(); + } + }); + } catch (err) { + e(err); + } + }); + }); +} diff --git a/vscode/tsconfig.json b/vscode/tsconfig.json new file mode 100644 index 0000000000..b65c745109 --- /dev/null +++ b/vscode/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": "src", + "strict": true /* enable all strict type-checking options */ + /* Additional Checks */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} diff --git a/vscode/tslint.json b/vscode/tslint.json new file mode 100644 index 0000000000..c81ff28fca --- /dev/null +++ b/vscode/tslint.json @@ -0,0 +1,15 @@ +{ + "rules": { + "no-string-throw": true, + "no-unused-expression": true, + "no-duplicate-variable": true, + "curly": true, + "class-name": true, + "semicolon": [ + true, + "always" + ], + "triple-equals": true + }, + "defaultSeverity": "warning" +} diff --git a/vscode/vsc-extension-quickstart.md b/vscode/vsc-extension-quickstart.md new file mode 100644 index 0000000000..b510bff34d --- /dev/null +++ b/vscode/vsc-extension-quickstart.md @@ -0,0 +1,42 @@ +# Welcome to your VS Code Extension + +## What's in the folder + +* This folder contains all of the files necessary for your extension. +* `package.json` - this is the manifest file in which you declare your extension and command. + * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. +* `src/extension.ts` - this is the main file where you will provide the implementation of your command. + * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. + * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. + +## Get up and running straight away + +* Press `F5` to open a new window with your extension loaded. +* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. +* Set breakpoints in your code inside `src/extension.ts` to debug your extension. +* Find output from your extension in the debug console. + +## Make changes + +* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. +* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. + + +## Explore the API + +* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. + +## Run tests + +* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. +* Press `F5` to run the tests in a new window with your extension loaded. +* See the output of the test result in the debug console. +* Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. + * The provided test runner will only consider files matching the name pattern `**.test.ts`. + * You can create folders inside the `test` folder to structure your tests any way you want. + +## Go further + + * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). + * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. + * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). From a00b0b3521d927d6fb715cfcb649bfe4eb93ef4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Sch=C3=A4fer?= Date: Mon, 21 Oct 2019 18:31:03 +0200 Subject: [PATCH 2/4] [BUILD][VSC] Replace npm workflow by gradle Unintentionally removed parts have been readded (lsp) and code has been improved regarding guidelines. Overall quality has been improved. Vscodes extension build, run and publish workflow has been shifted from npm based to gradle base to embed it better into the gradle environment and to enable easier CI builds. --- .gitignore | 7 +- build.gradle | 28 ++++++ docs/contribute/development-environment.md | 46 ++++++++- docs/contribute/guidelines.md | 1 - lsp/build.gradle | 1 - settings.gradle | 2 +- vscode/.gitignore | 81 +-------------- vscode/.vscode/extensions.json | 7 -- vscode/.vscode/launch.json | 36 ------- vscode/.vscode/tasks.json | 20 ---- vscode/.vscodeignore | 10 -- vscode/CHANGELOG.md | 9 -- vscode/README.md | 56 ++--------- vscode/build.gradle | 111 +++++++++++++++++++++ vscode/package.json | 9 +- vscode/src/account/activator.ts | 4 +- vscode/src/core/saros-client.ts | 2 +- vscode/src/core/saros-server.ts | 2 +- vscode/vsc-extension-quickstart.md | 42 -------- 19 files changed, 211 insertions(+), 263 deletions(-) delete mode 100644 vscode/.vscode/extensions.json delete mode 100644 vscode/.vscode/launch.json delete mode 100644 vscode/.vscode/tasks.json delete mode 100644 vscode/.vscodeignore delete mode 100644 vscode/CHANGELOG.md create mode 100644 vscode/build.gradle delete mode 100644 vscode/vsc-extension-quickstart.md diff --git a/.gitignore b/.gitignore index 3a1f05199a..c53797088f 100644 --- a/.gitignore +++ b/.gitignore @@ -48,7 +48,9 @@ buildSrc/.gradle /stf/.project /stf.test/.project .DS_Store -/.gradle/ +docs/_site +docs/Gemfile.lock +.gradle/ /build/ # Common @@ -64,3 +66,6 @@ vscode*/.project vscode*/build vscode*/package-lock.json vscode*/.vscode +*.vscode +*.code-workspace +*.vscodeignore diff --git a/build.gradle b/build.gradle index 6624db3846..78bab93672 100644 --- a/build.gradle +++ b/build.gradle @@ -196,6 +196,34 @@ task sarosLsp(type: Copy, dependsOn: [ into 'build/distribution/lsp' } +task sarosVSCode(type: Copy, dependsOn: [ + 'sarosLsp', + ':saros.vscode:packageExtension' + ]) { + group 'Delivery' + description 'Builds and tests all modules required by the Saros VS Code Extension' + + from project(':saros.lsp').jar + from 'vscode/vsix' + into 'build/distribution/vscode' +} + +task prepareVSCode(dependsOn: [ + 'sarosLsp', + 'saros.vscode:buildExtension' +]) { + group 'VS Code' + description 'Builds the Saros VS Code plugin' +} + +task runVSCode(dependsOn: [ + 'sarosLsp', + 'saros.vscode:runExtension' +]) { + group 'VS Code' + description 'Builds and runs the Saros VS Code plugin' +} + task sarosIntellij(type: Copy, dependsOn: [ ':saros.picocontainer:test', ':saros.core:test', diff --git a/docs/contribute/development-environment.md b/docs/contribute/development-environment.md index f25113976b..bbf63fae72 100644 --- a/docs/contribute/development-environment.md +++ b/docs/contribute/development-environment.md @@ -86,6 +86,45 @@ This is necessary in order to allow IntelliJ to execute all build and test actio * Click on `Open` * Select the repository root as project root and click `OK` +## Develop with VS Code + +When developing with VS Code you rely on your installed extensions. When opening a file with an extension that has currently no support VS Code will ask for installing necessary plugins. When being asked either install what is suggested or install by your own preference. + +### Developing the VS Code Extension + +When developing the extension you have two ways to start the debugger: + +#### Using a launch configuration (preferred) + +Create the `launch.json` in your `.vscode` directory of the folder you opened with vscode. When using a `workspace` you have to reference your launch file there also. The `launch.json` has the following content: + +```json +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/out/**/*.js" + ], + "preLaunchTask": "gradle: prepareVSCode" + } + ] +} +``` + +#### Using gradle + +Using gradle tasks directly has the benefits of starting multiple instances of the `VS Code Extension Host` which is useful for testing multiple at once. Just open the available tasks (usually `CTRL+SHIFT+T`) and run `runVSCode` in order to start the extension in an `Extension Host`. When debugging is needed just hit `F5` and choose the NodeJs debugger and attach to the desired process. + +To make it more comfortable a keybinding can be assigned to that task (`keybindings.json`) or the launch configuration of the NodeJs debugger can be saved (`launch.json`) in order to attach via `F5`. + ## Develop Without an IDE If you prefer to develop with a text editor (like Vim or Emacs) you can build and test @@ -97,14 +136,17 @@ The following tasks are used to build and test the different Saros components: * `sarosEclipse` - Triggers the build and test of the Saros Eclipse Plugin * `sarosIntellij` - Triggers the build and test of the Saros IntelliJ Plugin * `sarosServer` - Triggers the build and test of the Saros Server +* `sarosLsp` - Triggers the build and test of the Saros Language Server +* `sarosVSCode` - Triggers the build and test of the Saros VS Code Extension * `prepareEclipse` - Executes all tasks which are required before developing in Eclipse * `runIde` - Starts a IntelliJ IDE containing the Saros Plugin. The IDE version depends on the value of `INTELLIJ_HOME` or the `intellijVersion` specified in the build file of the IntelliJ package. +* `runVSCode` - Starts an VS Code (Extension Host) with the Saros Extension. Use the node debugger to attach to the process for debugging. -In order to build the whole project without using existing build artifacts simply call `./gradlew cleanAll sarosEclipse sarosIntellij sarosServer`. +In order to build the whole project without using existing build artifacts simply call `./gradlew cleanAll sarosEclipse sarosIntellij sarosServer sarosVSCode`. Gradle checks whether the component specific sources are changed. Therefore a task become a NOP if nothing changed and the build results still exist. If you want to force Gradle to re-execute the tasks, you have to call `./gradlew --rerun-tasks ...` or call the `cleanAll` task before other tasks. -The final build results are copied into the directory `/build/distribute/(eclipse|intellij)`. +The final build results are copied into the directory `/build/distribute/(eclipse|intellij|vscode|lsp)`. ### Formatting via Standalone Google Java Formatter diff --git a/docs/contribute/guidelines.md b/docs/contribute/guidelines.md index b53c46b922..cd1812eb84 100644 --- a/docs/contribute/guidelines.md +++ b/docs/contribute/guidelines.md @@ -51,7 +51,6 @@ title: Coding and Commit Guidelines |---|---------------------------- |`[E]`| This commit ONLY affects the Eclipse version of Saros |`[I]`| IntelliJ version of Saros -|`[VSC]`| Visual Studio Code version of Saros |`[S]`| Saros Server |`[HTML]`| Saros HTML UI |`[CORE]`| Saros core diff --git a/lsp/build.gradle b/lsp/build.gradle index f48f4aecc4..223c2d682a 100644 --- a/lsp/build.gradle +++ b/lsp/build.gradle @@ -4,7 +4,6 @@ plugins { dependencies { compile project(':saros.core') - compile project(':saros.server') compile 'org.apache.commons:commons-collections4:4.2' compile 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.8.1' compile 'org.eclipse.lsp4j:org.eclipse.lsp4j.jsonrpc:0.8.1' diff --git a/settings.gradle b/settings.gradle index 26bbd39055..5de937a1fe 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,7 +3,7 @@ plugins { } String prefix = 'saros.' -List projectDirs = ['core', 'eclipse', 'intellij', 'server', 'lsp', 'stf', 'stf.test'].each { dir -> +List projectDirs = ['core', 'eclipse', 'vscode', 'intellij', 'server', 'lsp', 'stf', 'stf.test'].each { dir -> String projectName = prefix + dir include projectName project(":$projectName").projectDir = file(dir) diff --git a/vscode/.gitignore b/vscode/.gitignore index 3e84417c23..7330d84c5b 100644 --- a/vscode/.gitignore +++ b/vscode/.gitignore @@ -5,41 +5,12 @@ !.vscode/extensions.json out/* +# VSCE +*.vsix + # Logs -logs *.log npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release @@ -58,48 +29,4 @@ typings/ .npm # Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# gatsby files -.cache/ -public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ +.eslintcache \ No newline at end of file diff --git a/vscode/.vscode/extensions.json b/vscode/.vscode/extensions.json deleted file mode 100644 index 0a18b9c4bd..0000000000 --- a/vscode/.vscode/extensions.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "ms-vscode.vscode-typescript-tslint-plugin" - ] -} \ No newline at end of file diff --git a/vscode/.vscode/launch.json b/vscode/.vscode/launch.json deleted file mode 100644 index f5197783a4..0000000000 --- a/vscode/.vscode/launch.json +++ /dev/null @@ -1,36 +0,0 @@ -// A launch configuration that compiles the extension and then opens it inside a new window -// Use IntelliSense to learn about possible attributes. -// Hover to view descriptions of existing attributes. -// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "npm: watch" - }, - { - "name": "Extension Tests", - "type": "extensionHost", - "request": "launch", - "runtimeExecutable": "${execPath}", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}", - "--extensionTestsPath=${workspaceFolder}/out/test/suite/index" - ], - "outFiles": [ - "${workspaceFolder}/out/test/**/*.js" - ], - "preLaunchTask": "npm: watch" - } - ] -} diff --git a/vscode/.vscode/tasks.json b/vscode/.vscode/tasks.json deleted file mode 100644 index 3b17e53b62..0000000000 --- a/vscode/.vscode/tasks.json +++ /dev/null @@ -1,20 +0,0 @@ -// See https://go.microsoft.com/fwlink/?LinkId=733558 -// for the documentation about the tasks.json format -{ - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] -} diff --git a/vscode/.vscodeignore b/vscode/.vscodeignore deleted file mode 100644 index ed3f9d37c1..0000000000 --- a/vscode/.vscodeignore +++ /dev/null @@ -1,10 +0,0 @@ -.vscode/** -.vscode-test/** -out/test/** -src/** -.gitignore -vsc-extension-quickstart.md -**/tsconfig.json -**/tslint.json -**/*.map -**/*.ts \ No newline at end of file diff --git a/vscode/CHANGELOG.md b/vscode/CHANGELOG.md deleted file mode 100644 index 14e49f2bbf..0000000000 --- a/vscode/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Change Log - -All notable changes to the "saros" extension will be documented in this file. - -Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. - -## [Unreleased] - -- Initial release \ No newline at end of file diff --git a/vscode/README.md b/vscode/README.md index d081f1ac79..458f49e972 100644 --- a/vscode/README.md +++ b/vscode/README.md @@ -1,65 +1,25 @@ -# saros README +# saros -This is the README for your extension "saros". After writing up a brief description, we recommend including the following sections. +This is the Saros implementation for Visual Studio Code. ## Features -Describe specific features of your extension including screenshots of your extension in action. Image paths are relative to this README file. - -For example if there is an image subfolder under your extension project workspace: - -\!\[feature X\]\(images/feature-x.png\) - -> Tip: Many popular extensions utilize animations. This is an excellent way to show off your extension! We recommend short, focused animations that are easy to follow. +**TBD** ## Requirements -If you have any requirements or dependencies, add a section describing those and how to install and configure them. +**TBD** ## Extension Settings -Include if your extension adds any VS Code settings through the `contributes.configuration` extension point. - -For example: - -This extension contributes the following settings: - -* `myExtension.enable`: enable/disable this extension -* `myExtension.thing`: set to `blah` to do something +**TBD** ## Known Issues -Calling out known issues can help limit users opening duplicate issues against your extension. +**TBD** ## Release Notes -Users appreciate release notes as you update your extension. - -### 1.0.0 - -Initial release of ... - -### 1.0.1 - -Fixed issue #. - -### 1.1.0 - -Added features X, Y, and Z. - ------------------------------------------------------------------------------------------------------------ - -## Working with Markdown - -**Note:** You can author your README using Visual Studio Code. Here are some useful editor keyboard shortcuts: - -* Split the editor (`Cmd+\` on macOS or `Ctrl+\` on Windows and Linux) -* Toggle preview (`Shift+CMD+V` on macOS or `Shift+Ctrl+V` on Windows and Linux) -* Press `Ctrl+Space` (Windows, Linux) or `Cmd+Space` (macOS) to see a list of Markdown snippets - -### For more information - -* [Visual Studio Code's Markdown Support](http://code.visualstudio.com/docs/languages/markdown) -* [Markdown Syntax Reference](https://help.github.com/articles/markdown-basics/) +### 0.0.1 -**Enjoy!** +**TBD** \ No newline at end of file diff --git a/vscode/build.gradle b/vscode/build.gradle new file mode 100644 index 0000000000..be36f0d8ae --- /dev/null +++ b/vscode/build.gradle @@ -0,0 +1,111 @@ +import org.apache.tools.ant.taskdefs.condition.Os +import groovy.json.JsonSlurper + +plugins { + id "com.github.node-gradle.node" version "2.2.0" +} + +apply plugin: 'com.github.node-gradle.node' + +def packageSlurper = new JsonSlurper() +def packageJson = packageSlurper.parse file('package.json') +version = packageJson.version + +def vscePath = './node_modules/vsce/out/vsce' + +node { + version = '10.14.1' + npmVersion = '6.4.1' + download = true +} + + +// npm_run_compile { + // inputs.files fileTree( + // dir: "src", + // excludes: ["bin/**", "node_modules/**"]) + // inputs.file 'package.json' + // inputs.file 'package-lock.json' + // inputs.file 'tsconfig.json' + // outputs.dir 'out' +// } + +npmInstall { + inputs.files fileTree("./") +} + +task copyLsp(type: Copy) { + from("$rootProject.projectDir/build/distribution/lsp") + into('out') +} + +task buildExtension(dependsOn: [ + 'copyLsp', + 'npmInstall', + 'npm_run_compile' + ]) { + group 'VS Code' + description 'Builds the extension' +} + +task runExtension(type: Exec, dependsOn: [ + 'buildExtension' +]) { + group 'VS Code' + description 'Builds and runs the extension' + + def execArgs = "code --extensionDevelopmentPath=${file('./').absolutePath}" + + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + executable = 'cmd' + args = ["/c ${execArgs}"] + } else { + executable = 'sh' + args = [execArgs] + } + + workingDir = file('./out').absolutePath +} + +task packageExtension(type: NodeTask, dependsOn: [ + 'copyLsp' +]) { + group 'VS Code' + description 'Packages the extension' + + delete 'vsix/*' + file('./vsix').mkdirs() + + ext.archiveName = "$project.name-${project.version}.vsix" + ext.destPath = "./vsix/$archiveName" + + script = file(vscePath) + args = ['package', '--out', destPath] + // execOverrides { + // workingDir = file('./') + // } +} + +task publishExtension(type: NodeTask, dependsOn: [ + 'copyLsp' +]) { + group 'VS Code' + description 'Publishes the extension' + + script = file(vscePath) + args = ['publish', 'patch'] + execOverrides { + workingDir = file('./') + } +} + +task unpublishExtension(type: NodeTask) { + group 'VS Code' + description 'Unpublishes the extension' + + script = file(vscePath) + args = ['unpublish', "${packageJson.publisher}.${packageJson.name}"] + execOverrides { + workingDir = file('./') + } +} \ No newline at end of file diff --git a/vscode/package.json b/vscode/package.json index 7087063046..9a463ff99c 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -2,7 +2,7 @@ "name": "saros", "displayName": "saros", "description": "Saros implementation for VS Code.", - "version": "0.0.1", + "version": "0.0.2", "engines": { "vscode": "^1.38.0" }, @@ -10,7 +10,7 @@ "Other" ], "activationEvents": [ - "workspaceContains:*" + "*" ], "main": "./out/extension.js", "contributes": { @@ -18,7 +18,7 @@ { "command": "saros.account.add", "title": "Add Account", - "category": "Saros" + "category": "Saros" } ], "configuration": { @@ -55,7 +55,8 @@ "mocha": "^6.1.4", "typescript": "^3.3.1", "tslint": "^5.12.1", - "vscode-test": "^1.2.0" + "vscode-test": "^1.2.0", + "vsce": "^1.71.0" }, "dependencies": { "node-jre": "^0.2.3", diff --git a/vscode/src/account/activator.ts b/vscode/src/account/activator.ts index 449d204b59..16bb947ff1 100644 --- a/vscode/src/account/activator.ts +++ b/vscode/src/account/activator.ts @@ -11,10 +11,10 @@ export function activateAccounts(extension: SarosExtension) { commands.registerCommand('saros.account.add', () => { extension.onReady() .then(resolve => { - return extension.client.addAccount('micha@jabber.com'); + return extension.client.addAccount('foo@bar.com'); }) .then(r => { - console.log('Response was: ' + r.Response); + console.log('Response was: ' + r.response); }); }); } \ No newline at end of file diff --git a/vscode/src/core/saros-client.ts b/vscode/src/core/saros-client.ts index d5d05010ed..0d8512c6b1 100644 --- a/vscode/src/core/saros-client.ts +++ b/vscode/src/core/saros-client.ts @@ -7,7 +7,7 @@ import { LanguageClient } from "vscode-languageclient"; * @interface AddAccountResponse */ export interface AddAccountResponse { - Response: boolean; + response: boolean; } /** diff --git a/vscode/src/core/saros-server.ts b/vscode/src/core/saros-server.ts index 982ece6761..09386febc8 100644 --- a/vscode/src/core/saros-server.ts +++ b/vscode/src/core/saros-server.ts @@ -96,7 +96,7 @@ export class SarosServer { */ private startProcess(...args: any[]): SarosServer { - var pathToJar = path.resolve(this.context.extensionPath, 'out', 'saros.vscode.java.jar'); + var pathToJar = path.resolve(this.context.extensionPath, 'out', 'saros.lsp.jar'); var jre = require('node-jre'); console.log('spawning jar process'); diff --git a/vscode/vsc-extension-quickstart.md b/vscode/vsc-extension-quickstart.md deleted file mode 100644 index b510bff34d..0000000000 --- a/vscode/vsc-extension-quickstart.md +++ /dev/null @@ -1,42 +0,0 @@ -# Welcome to your VS Code Extension - -## What's in the folder - -* This folder contains all of the files necessary for your extension. -* `package.json` - this is the manifest file in which you declare your extension and command. - * The sample plugin registers a command and defines its title and command name. With this information VS Code can show the command in the command palette. It doesn’t yet need to load the plugin. -* `src/extension.ts` - this is the main file where you will provide the implementation of your command. - * The file exports one function, `activate`, which is called the very first time your extension is activated (in this case by executing the command). Inside the `activate` function we call `registerCommand`. - * We pass the function containing the implementation of the command as the second parameter to `registerCommand`. - -## Get up and running straight away - -* Press `F5` to open a new window with your extension loaded. -* Run your command from the command palette by pressing (`Ctrl+Shift+P` or `Cmd+Shift+P` on Mac) and typing `Hello World`. -* Set breakpoints in your code inside `src/extension.ts` to debug your extension. -* Find output from your extension in the debug console. - -## Make changes - -* You can relaunch the extension from the debug toolbar after changing code in `src/extension.ts`. -* You can also reload (`Ctrl+R` or `Cmd+R` on Mac) the VS Code window with your extension to load your changes. - - -## Explore the API - -* You can open the full set of our API when you open the file `node_modules/@types/vscode/index.d.ts`. - -## Run tests - -* Open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Extension Tests`. -* Press `F5` to run the tests in a new window with your extension loaded. -* See the output of the test result in the debug console. -* Make changes to `src/test/suite/extension.test.ts` or create new test files inside the `test/suite` folder. - * The provided test runner will only consider files matching the name pattern `**.test.ts`. - * You can create folders inside the `test` folder to structure your tests any way you want. - -## Go further - - * Reduce the extension size and improve the startup time by [bundling your extension](https://code.visualstudio.com/api/working-with-extensions/bundling-extension). - * [Publish your extension](https://code.visualstudio.com/api/working-with-extensions/publishing-extension) on the VSCode extension marketplace. - * Automate builds by setting up [Continuous Integration](https://code.visualstudio.com/api/working-with-extensions/continuous-integration). From ea566d93717aff79984c33a18f6aaaf42fe3a6a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Sch=C3=A4fer?= Date: Fri, 31 Jan 2020 13:23:52 +0100 Subject: [PATCH 3/4] [BUILD][VSC] Use webpack as building method In order to keep the size of the extension rather small the building method has been changed to webpack. This also brings the benefit of excluding files that aren't really needed. --- .gitignore | 1 + vscode/build.gradle | 4 +-- vscode/package.json | 23 +++++++++++------ vscode/src/core/saros-extension.ts | 8 ++++-- vscode/src/core/saros-server.ts | 12 +++------ vscode/src/extension.ts | 2 +- vscode/webpack.config.js | 41 ++++++++++++++++++++++++++++++ 7 files changed, 69 insertions(+), 22 deletions(-) create mode 100644 vscode/webpack.config.js diff --git a/.gitignore b/.gitignore index c53797088f..b23b47a7d8 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,4 @@ vscode*/.vscode *.vscode *.code-workspace *.vscodeignore +dist/ diff --git a/vscode/build.gradle b/vscode/build.gradle index be36f0d8ae..51d5cca412 100644 --- a/vscode/build.gradle +++ b/vscode/build.gradle @@ -36,13 +36,13 @@ npmInstall { task copyLsp(type: Copy) { from("$rootProject.projectDir/build/distribution/lsp") - into('out') + into('dist') } task buildExtension(dependsOn: [ 'copyLsp', 'npmInstall', - 'npm_run_compile' + 'npm_run_webpack' ]) { group 'VS Code' description 'Builds the extension' diff --git a/vscode/package.json b/vscode/package.json index 9a463ff99c..5e874d0292 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -2,7 +2,11 @@ "name": "saros", "displayName": "saros", "description": "Saros implementation for VS Code.", - "version": "0.0.2", + "version": "0.0.1", + "publisher": "mschaefer", + "repository": { + "url": "https://github.com/saros-project/saros" + }, "engines": { "vscode": "^1.38.0" }, @@ -12,7 +16,7 @@ "activationEvents": [ "*" ], - "main": "./out/extension.js", + "main": "./dist/extension", "contributes": { "commands": [ { @@ -40,9 +44,10 @@ } }, "scripts": { - "vscode:prepublish": "npm run compile", - "compile": "tsc -p ./", - "watch": "tsc -watch -p ./", + "vscode:prepublish": "webpack --mode production", + "webpack": "webpack --mode development", + "webpack-dev": "webpack --mode development --watch", + "test-compile": "tsc -p ./", "pretest": "npm run compile", "test": "node ./out/test/runTest.js" }, @@ -53,13 +58,15 @@ "@types/vscode": "^1.38.0", "glob": "^7.1.4", "mocha": "^6.1.4", - "typescript": "^3.3.1", + "ts-loader": "^6.2.1", "tslint": "^5.12.1", + "typescript": "^3.3.1", + "vsce": "^1.71.0", "vscode-test": "^1.2.0", - "vsce": "^1.71.0" + "webpack": "^4.41.5", + "webpack-cli": "^3.3.10" }, "dependencies": { - "node-jre": "^0.2.3", "vscode-languageclient": "^5.2.1" } } diff --git a/vscode/src/core/saros-extension.ts b/vscode/src/core/saros-extension.ts index e13395940e..d757db6577 100644 --- a/vscode/src/core/saros-extension.ts +++ b/vscode/src/core/saros-extension.ts @@ -42,16 +42,18 @@ export class SarosExtension { * @memberof SarosExtension */ async init() { + if(!this.context) { return Promise.reject('Context not set'); } - try { + try { let self = this; return new Promise((resolve, reject) => { + const server = new SarosServer(self.context); - self.client = new SarosClient('sarosServer', 'Saros Server', server.getStartFunc(), this.createClientOptions()); + self.client = new SarosClient('sarosServer', 'Saros Server', server.getStartFunc(), this.createClientOptions()); self.context.subscriptions.push(self.client.start()); resolve(); @@ -70,9 +72,11 @@ export class SarosExtension { */ async onReady() { if(!this.client) { + console.log("onReady.reject"); return Promise.reject('SarosExtension is not initialized'); } + console.log("onReady"); return this.client.onReady(); } diff --git a/vscode/src/core/saros-server.ts b/vscode/src/core/saros-server.ts index 09386febc8..9614b93845 100644 --- a/vscode/src/core/saros-server.ts +++ b/vscode/src/core/saros-server.ts @@ -96,16 +96,10 @@ export class SarosServer { */ private startProcess(...args: any[]): SarosServer { - var pathToJar = path.resolve(this.context.extensionPath, 'out', 'saros.lsp.jar'); - var jre = require('node-jre'); + var pathToJar = path.resolve(this.context.extensionPath, 'dist', 'saros.lsp.jar'); console.log('spawning jar process'); - this.process = jre.spawn( - [pathToJar], - 'saros.lsp.SarosLauncher', - args, - { encoding: 'utf8' } - ) as process.ChildProcess; + this.process = process.spawn('java', ['-jar', pathToJar].concat(args)); return this; } @@ -120,7 +114,7 @@ export class SarosServer { * @memberof SarosServer */ private withDebug(isEnabled: boolean): SarosServer { - + if(this.process === undefined) { throw new Error('Server process is undefined'); } diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 83066c448b..46d39b318d 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -19,6 +19,7 @@ export function activate(context: vscode.ExtensionContext) { console.log('Extension "Saros" is now active!'); }) .catch(reason => { + console.log(reason); vscode.window.showErrorMessage('Saros extension did not start propertly.' + 'Reason: ' + reason); //TODO: restart feature }); @@ -35,7 +36,6 @@ function createStatusBar(): Disposable { let statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, Number.MAX_VALUE); statusBarItem.text = "Saros"; - statusBarItem.command = "saros.start"; statusBarItem.show(); return statusBarItem; diff --git a/vscode/webpack.config.js b/vscode/webpack.config.js new file mode 100644 index 0000000000..a76643bba5 --- /dev/null +++ b/vscode/webpack.config.js @@ -0,0 +1,41 @@ +//@ts-check + +'use strict'; + +const path = require('path'); + +/**@type {import('webpack').Configuration}*/ +const config = { + target: 'node', // vscode extensions run in a Node.js-context πŸ“– -> https://webpack.js.org/configuration/node/ + + entry: './src/extension.ts', // the entry point of this extension, πŸ“– -> https://webpack.js.org/configuration/entry-context/ + output: { + // the bundle is stored in the 'dist' folder (check package.json), πŸ“– -> https://webpack.js.org/configuration/output/ + path: path.resolve(__dirname, 'dist'), + filename: 'extension.js', + libraryTarget: 'commonjs2', + devtoolModuleFilenameTemplate: '../[resource-path]' + }, + devtool: 'source-map', + externals: { + vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, πŸ“– -> https://webpack.js.org/configuration/externals/ + }, + resolve: { + // support reading TypeScript and JavaScript files, πŸ“– -> https://github.com/TypeStrong/ts-loader + extensions: ['.ts', '.js'] + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: [ + { + loader: 'ts-loader' + } + ] + } + ] + } +}; +module.exports = config; \ No newline at end of file From af792c253ac81aea7a434e68526363100b58d872 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20Sch=C3=A4fer?= Date: Thu, 20 Feb 2020 15:04:06 +0100 Subject: [PATCH 4/4] [REFACTOR][VSC] Introduce eslint Introduce eslint and modify code accordingly. Furthermore gradle has been modified in order to seperate config and execute statements. --- build.gradle | 2 +- docs/contribute/guidelines.md | 1 + vscode/.eslintrc.json | 32 ++++ vscode/build.gradle | 62 +++--- vscode/package.json | 10 +- vscode/src/account/activator.ts | 20 +- vscode/src/account/index.ts | 1 + vscode/src/core/index.ts | 3 + vscode/src/core/saros-extension.ts | 93 +++++---- .../{saros-client.ts => saros-lang-client.ts} | 21 +-- vscode/src/core/saros-lang-server.ts | 176 ++++++++++++++++++ vscode/src/core/saros-server.ts | 171 ----------------- vscode/src/extension.ts | 48 ++--- vscode/src/test/runTest.ts | 23 --- vscode/src/test/suite/extension.test.ts | 18 -- vscode/src/test/suite/index.ts | 37 ---- vscode/tslint.json | 15 -- vscode/webpack.config.js | 43 +++-- 18 files changed, 362 insertions(+), 414 deletions(-) create mode 100644 vscode/.eslintrc.json create mode 100644 vscode/src/account/index.ts create mode 100644 vscode/src/core/index.ts rename vscode/src/core/{saros-client.ts => saros-lang-client.ts} (59%) create mode 100644 vscode/src/core/saros-lang-server.ts delete mode 100644 vscode/src/core/saros-server.ts delete mode 100644 vscode/src/test/runTest.ts delete mode 100644 vscode/src/test/suite/extension.test.ts delete mode 100644 vscode/src/test/suite/index.ts delete mode 100644 vscode/tslint.json diff --git a/build.gradle b/build.gradle index 78bab93672..f195b74e1a 100644 --- a/build.gradle +++ b/build.gradle @@ -208,7 +208,7 @@ task sarosVSCode(type: Copy, dependsOn: [ into 'build/distribution/vscode' } -task prepareVSCode(dependsOn: [ +task buildVSCode(dependsOn: [ 'sarosLsp', 'saros.vscode:buildExtension' ]) { diff --git a/docs/contribute/guidelines.md b/docs/contribute/guidelines.md index cd1812eb84..b53c46b922 100644 --- a/docs/contribute/guidelines.md +++ b/docs/contribute/guidelines.md @@ -51,6 +51,7 @@ title: Coding and Commit Guidelines |---|---------------------------- |`[E]`| This commit ONLY affects the Eclipse version of Saros |`[I]`| IntelliJ version of Saros +|`[VSC]`| Visual Studio Code version of Saros |`[S]`| Saros Server |`[HTML]`| Saros HTML UI |`[CORE]`| Saros core diff --git a/vscode/.eslintrc.json b/vscode/.eslintrc.json new file mode 100644 index 0000000000..39887df00f --- /dev/null +++ b/vscode/.eslintrc.json @@ -0,0 +1,32 @@ +{ + "env": { + "es6": true, + "node": true, + "commonjs": true + }, + "extends": [ + "eslint:recommended", + "google" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + }, + "project": "tsconfig.json" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars-experimental": "error" + }, + "root": true +} \ No newline at end of file diff --git a/vscode/build.gradle b/vscode/build.gradle index 51d5cca412..d785b13d22 100644 --- a/vscode/build.gradle +++ b/vscode/build.gradle @@ -14,29 +14,20 @@ version = packageJson.version def vscePath = './node_modules/vsce/out/vsce' node { - version = '10.14.1' - npmVersion = '6.4.1' - download = true + version = '10.14.1' + npmVersion = '6.4.1' + download = true } - -// npm_run_compile { - // inputs.files fileTree( - // dir: "src", - // excludes: ["bin/**", "node_modules/**"]) - // inputs.file 'package.json' - // inputs.file 'package-lock.json' - // inputs.file 'tsconfig.json' - // outputs.dir 'out' -// } - npmInstall { - inputs.files fileTree("./") + inputs.files fileTree(projectDir) } task copyLsp(type: Copy) { - from("$rootProject.projectDir/build/distribution/lsp") - into('dist') + doFirst { + from("$rootDir/build/distribution/lsp") + into('dist') + } } task buildExtension(dependsOn: [ @@ -45,16 +36,16 @@ task buildExtension(dependsOn: [ 'npm_run_webpack' ]) { group 'VS Code' - description 'Builds the extension' + description 'Builds the extension' } task runExtension(type: Exec, dependsOn: [ 'buildExtension' ]) { group 'VS Code' - description 'Builds and runs the extension' + description 'Builds and runs the extension' - def execArgs = "code --extensionDevelopmentPath=${file('./').absolutePath}" + def execArgs = "code --extensionDevelopmentPath=${projectDir.absolutePath}" if (Os.isFamily(Os.FAMILY_WINDOWS)) { executable = 'cmd' @@ -64,48 +55,47 @@ task runExtension(type: Exec, dependsOn: [ args = [execArgs] } - workingDir = file('./out').absolutePath + workingDir = file('./dist').absolutePath } task packageExtension(type: NodeTask, dependsOn: [ 'copyLsp' ]) { group 'VS Code' - description 'Packages the extension' + description 'Packages the extension' - delete 'vsix/*' - file('./vsix').mkdirs() + doFirst { + delete 'vsix/*' + file('./vsix').mkdirs() + } ext.archiveName = "$project.name-${project.version}.vsix" - ext.destPath = "./vsix/$archiveName" + ext.destPath = "./vsix" script = file(vscePath) - args = ['package', '--out', destPath] - // execOverrides { - // workingDir = file('./') - // } + args = ['package', '--out', destPath] } task publishExtension(type: NodeTask, dependsOn: [ 'copyLsp' ]) { group 'VS Code' - description 'Publishes the extension' + description 'Publishes the extension' script = file(vscePath) - args = ['publish', 'patch'] + args = ['publish', 'patch'] execOverrides { - workingDir = file('./') + workingDir = file('./') } } task unpublishExtension(type: NodeTask) { group 'VS Code' - description 'Unpublishes the extension' + description 'Unpublishes the extension' script = file(vscePath) - args = ['unpublish', "${packageJson.publisher}.${packageJson.name}"] + args = ['unpublish', "${packageJson.publisher}.${packageJson.name}"] execOverrides { - workingDir = file('./') - } + workingDir = file('./') + } } \ No newline at end of file diff --git a/vscode/package.json b/vscode/package.json index 5e874d0292..65e4cb6646 100644 --- a/vscode/package.json +++ b/vscode/package.json @@ -49,21 +49,25 @@ "webpack-dev": "webpack --mode development --watch", "test-compile": "tsc -p ./", "pretest": "npm run compile", - "test": "node ./out/test/runTest.js" + "test": "node ./out/test/runTest.js", + "lint": "eslint */**/*.ts --quiet" }, "devDependencies": { "@types/glob": "^7.1.1", "@types/mocha": "^5.2.6", "@types/node": "^10.12.21", "@types/vscode": "^1.38.0", + "@typescript-eslint/eslint-plugin": "^2.19.2", + "@typescript-eslint/parser": "^2.19.2", + "eslint": "^6.8.0", + "eslint-config-google": "^0.14.0", "glob": "^7.1.4", "mocha": "^6.1.4", "ts-loader": "^6.2.1", - "tslint": "^5.12.1", "typescript": "^3.3.1", "vsce": "^1.71.0", "vscode-test": "^1.2.0", - "webpack": "^4.41.5", + "webpack": "^4.41.6", "webpack-cli": "^3.3.10" }, "dependencies": { diff --git a/vscode/src/account/activator.ts b/vscode/src/account/activator.ts index 16bb947ff1..26c98226a5 100644 --- a/vscode/src/account/activator.ts +++ b/vscode/src/account/activator.ts @@ -1,5 +1,5 @@ -import { SarosExtension } from "../core/saros-extension"; -import { commands } from "vscode"; +import {commands} from 'vscode'; +import {SarosExtension} from '../core'; /** * Activation function of the account module. @@ -8,13 +8,13 @@ import { commands } from "vscode"; * @param {SarosExtension} extension - The instance of the extension */ export function activateAccounts(extension: SarosExtension) { - commands.registerCommand('saros.account.add', () => { - extension.onReady() - .then(resolve => { - return extension.client.addAccount('foo@bar.com'); + commands.registerCommand('saros.account.add', () => { + extension.onReady() + .then(() => { + return extension.client.addAccount(); }) - .then(r => { - console.log('Response was: ' + r.response); + .then((r) => { + console.log('Response was: ' + r.response); }); - }); -} \ No newline at end of file + }); +} diff --git a/vscode/src/account/index.ts b/vscode/src/account/index.ts new file mode 100644 index 0000000000..147d32672f --- /dev/null +++ b/vscode/src/account/index.ts @@ -0,0 +1 @@ +export * from './activator'; diff --git a/vscode/src/core/index.ts b/vscode/src/core/index.ts new file mode 100644 index 0000000000..2596f1a132 --- /dev/null +++ b/vscode/src/core/index.ts @@ -0,0 +1,3 @@ +export * from './saros-lang-client'; +export * from './saros-lang-server'; +export * from './saros-extension'; diff --git a/vscode/src/core/saros-extension.ts b/vscode/src/core/saros-extension.ts index d757db6577..2c3b4e9bb6 100644 --- a/vscode/src/core/saros-extension.ts +++ b/vscode/src/core/saros-extension.ts @@ -1,7 +1,8 @@ -import { ExtensionContext, workspace, window } from "vscode"; -import { SarosServer } from "./saros-server"; -import { SarosClient } from "./saros-client"; -import { LanguageClientOptions, RevealOutputChannelOn } from "vscode-languageclient"; +import {workspace, window, ExtensionContext} from 'vscode'; +import {SarosLangServer} from './saros-lang-server'; +import {SarosLangClient} from './saros-lang-client'; +import {LanguageClientOptions, + RevealOutputChannelOn} from 'vscode-languageclient'; /** * The Saros extension. @@ -11,7 +12,7 @@ import { LanguageClientOptions, RevealOutputChannelOn } from "vscode-languagecli */ export class SarosExtension { private context!: ExtensionContext; - public client!: SarosClient; + public client!: SarosLangClient; /** * Creates an instance of SarosExtension. @@ -19,90 +20,84 @@ export class SarosExtension { * @memberof SarosExtension */ constructor() { - + } /** * Sets the context the extension runs on. * * @param {ExtensionContext} context - The extension context - * @returns {SarosExtension} Itself + * @return {SarosExtension} Itself * @memberof SarosExtension */ setContext(context: ExtensionContext): SarosExtension { - this.context = context; + this.context = context; - return this; + return this; } /** * Initializes the extension. * - * @returns * @memberof SarosExtension */ async init() { - - if(!this.context) { - return Promise.reject('Context not set'); - } + if (!this.context) { + return Promise.reject(new Error('Context not set')); + } - try { - let self = this; + try { + const self = this; - return new Promise((resolve, reject) => { - - const server = new SarosServer(self.context); - self.client = new SarosClient('sarosServer', 'Saros Server', server.getStartFunc(), this.createClientOptions()); - self.context.subscriptions.push(self.client.start()); + return new Promise((resolve) => { + const server = new SarosLangServer(self.context); + self.client = new SarosLangClient('sarosServer', 'Saros Server', + server.getStartFunc(), this.createClientOptions()); + this.context.subscriptions.push(self.client.start()); - resolve(); - }); - } catch(ex) { - const msg = "Error while activating plugin. " + (ex.message ? ex.message : ex); - return Promise.reject(msg); - } + resolve(); + }); + } catch (ex) { + const msg = 'Error while activating plugin. ' + + (ex.message ? ex.message : ex); + return Promise.reject(new Error(msg)); + } } /** * Callback when extension is ready. * - * @returns * @memberof SarosExtension */ async onReady() { - if(!this.client) { - console.log("onReady.reject"); - return Promise.reject('SarosExtension is not initialized'); - } + if (!this.client) { + console.log('onReady.reject'); + return Promise.reject(new Error('SarosExtension is not initialized')); + } - console.log("onReady"); - return this.client.onReady(); + console.log('onReady'); + return this.client.onReady(); } /** * Creates the client options. * * @private - * @returns {LanguageClientOptions} The client options + * @return {LanguageClientOptions} The client options * @memberof SarosExtension */ private createClientOptions(): LanguageClientOptions { - let clientOptions: LanguageClientOptions = { - // Register the server for plain text documents - documentSelector: ['plaintext'], - synchronize: { - // Synchronize the setting section 'languageServerExample' to the server - //configurationSection: 'sarosServer', - // Notify the server about file changes to '.clientrc files contain in the workspace - fileEvents: workspace.createFileSystemWatcher('**/.clientrc') - }, - outputChannel: window.createOutputChannel('Saros'), - revealOutputChannelOn: RevealOutputChannelOn.Info - }; + const clientOptions: LanguageClientOptions = { + documentSelector: ['plaintext'], + synchronize: { + fileEvents: workspace.createFileSystemWatcher('**/.clientrc'), + }, + outputChannel: window.createOutputChannel('Saros'), + revealOutputChannelOn: RevealOutputChannelOn.Info, + }; - return clientOptions; + return clientOptions; } } -export const sarosExtensionInstance = new SarosExtension(); \ No newline at end of file +export const sarosExtensionInstance = new SarosExtension(); diff --git a/vscode/src/core/saros-client.ts b/vscode/src/core/saros-lang-client.ts similarity index 59% rename from vscode/src/core/saros-client.ts rename to vscode/src/core/saros-lang-client.ts index 0d8512c6b1..1e89c967fe 100644 --- a/vscode/src/core/saros-client.ts +++ b/vscode/src/core/saros-lang-client.ts @@ -1,4 +1,4 @@ -import { LanguageClient } from "vscode-languageclient"; +import {LanguageClient} from 'vscode-languageclient'; /** * Response for adding new accounts. @@ -27,20 +27,19 @@ export interface AddAccountRequest { * @class SarosClient * @extends {LanguageClient} */ -export class SarosClient extends LanguageClient { - - /** +export class SarosLangClient extends LanguageClient { + /** * Adds a new account. * * @param {string} name - Account identifier - * @returns {Thenable} The result + * @return {Thenable} The result * @memberof SarosClient */ - addAccount(name: string): Thenable { - const request: AddAccountRequest = { + addAccount(): Thenable { + const request: AddAccountRequest = { - }; + }; - return this.sendRequest("saros/account/add", request); - } -} \ No newline at end of file + return this.sendRequest('saros/account/add', request); + } +} diff --git a/vscode/src/core/saros-lang-server.ts b/vscode/src/core/saros-lang-server.ts new file mode 100644 index 0000000000..e35b941983 --- /dev/null +++ b/vscode/src/core/saros-lang-server.ts @@ -0,0 +1,176 @@ +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as process from 'child_process'; +import * as net from 'net'; +import {StreamInfo} from 'vscode-languageclient'; + +/** + * Encapsulation of the Saros Language server. + * + * @export + * @class SarosServer + */ +export class SarosLangServer { + /** + * Started process of the server. + * + * @private + * @type {process.ChildProcess} + * @memberof SarosServer + */ + private process?: process.ChildProcess; + + /** + * Creates an instance of SarosServer. + * + * @param {vscode.ExtensionContext} context - The extension context + * @memberof SarosServer + */ + constructor(private context: vscode.ExtensionContext) { + + } + + /** + * Starts the server process. + * + * @param {number} port - The port the server listens on for connection + * @memberof SarosServer + */ + public start(port: number): void { + if (this.process !== undefined) { + throw new Error('Server process is still running'); + } + + this.startProcess(port) + .withDebug(true) + .withExitAware(); + } + + /** + * Provides access to the start function. + * + * @remarks A free port will be determined and used. + * @return {function():Thenable} Function that starts the + * server and retuns the stream information + * @memberof SarosServer + */ + public getStartFunc(): () => Thenable { + return this.startServer; + } + + /** + * Starts the server on a random port. + * + * @return {Thenable} Stream information + * the server listens on + * @memberof SarosServer + */ + private startServer(): Thenable { + const self = this; + return new Promise((resolve) => { + const server = net.createServer((socket) => { + console.log('Creating server'); + + resolve({ + reader: socket, + writer: socket, + }); + + socket.on('end', () => console.log('Disconnected')); + }).on('error', (err) => { + // handle errors here + throw err; + }); + + // grab a random port. + server.listen(() => { + const port = (server.address() as net.AddressInfo).port; + + self.start(port); + }); + }); + } + + /** + * Starts the Saros server jar as process. + * + * @private + * @param {...any[]} args - Additional command line arguments for the server + * @return {SarosServer} Itself + * @memberof SarosServer + */ + private startProcess(...args: any[]): SarosLangServer { + const pathToJar = path.resolve(this.context.extensionPath, + 'dist', 'saros.lsp.jar'); + + console.log('spawning jar process'); + this.process = process.spawn('java', ['-jar', pathToJar].concat(args)); + + return this; + } + + /** + * Attaches listeners for debug informations and prints + * retrieved data to a newly created + * [output channel](#vscode.OutputChannel). + * + * @private + * @param {boolean} isEnabled - Wether debug output is redirected or not + * @return {SarosServer} Itself + * @memberof SarosServer + */ + private withDebug(isEnabled: boolean): SarosLangServer { + if (this.process === undefined) { + throw new Error('Server process is undefined'); + } + + if (!isEnabled) { + return this; + } + + const output = vscode.window.createOutputChannel('Saros (Debug)'); + + this.process.stdout.on('data', (data) => { + output.appendLine(data); + }); + + this.process.stderr.on('data', (data) => { + output.appendLine(data); + }); + + return this; + } + + /** + * Attaches listeners to observe termination of the server. + * + * @private + * @return {SarosServer} Itself + * @memberof SarosServer + */ + private withExitAware(): SarosLangServer { + if (this.process === undefined) { + throw new Error('Server process is undefined'); + } + + this.process.on('error', (error) => { + vscode.window.showErrorMessage( + `child process creating error with error ${error}`); + }); + + const self = this; + this.process.on('close', (code) => { + let showMessageFunc; + if (code === 0) { + showMessageFunc = vscode.window.showInformationMessage; + } else { + showMessageFunc = vscode.window.showWarningMessage; + } + + self.process = undefined; + showMessageFunc(`child process exited with code ${code}`); + }); + + return this; + } +} diff --git a/vscode/src/core/saros-server.ts b/vscode/src/core/saros-server.ts deleted file mode 100644 index 9614b93845..0000000000 --- a/vscode/src/core/saros-server.ts +++ /dev/null @@ -1,171 +0,0 @@ -import * as vscode from 'vscode'; -import * as path from 'path'; -import * as process from 'child_process'; -import * as net from 'net'; -import { StreamInfo } from 'vscode-languageclient'; - -/** - * Encapsulation of the Saros server. - * - * @export - * @class SarosServer - */ -export class SarosServer { - - /** - * Started process of the server. - * - * @private - * @type {process.ChildProcess} - * @memberof SarosServer - */ - private process?: process.ChildProcess; - - /** - * Creates an instance of SarosServer. - * - * @param {vscode.ExtensionContext} context - The extension context - * @memberof SarosServer - */ - constructor(private context: vscode.ExtensionContext) { - - } - - /** - * Starts the server process. - * - * @param {number} port - The port the server listens on for connection - * @memberof SarosServer - */ - public start(port: number): void { - - if(this.process !== undefined) { - throw new Error('Server process is still running'); - } - - this.startProcess(port) - .withDebug(true) - .withExitAware(); - } - - /** - * Provides access to the start function. - * - * @remarks A free port will be determined and used. - * @returns {() => Thenable} Function that starts the server and retuns the io information - * @memberof SarosServer - */ - public getStartFunc(): () => Thenable { - - let self = this; - function createServer(): Thenable { - return new Promise((resolve, reject) => { - var server = net.createServer((socket) => { - console.log("Creating server"); - - resolve({ - reader: socket, - writer: socket - }); - - socket.on('end', () => console.log("Disconnected")); - }).on('error', (err) => { - // handle errors here - throw err; - }); - - // grab a random port. - server.listen(() => { - let port = (server.address() as net.AddressInfo).port; - - self.start(port); - }); - }); - } - - return createServer; - } - - /** - * Starts the Saros server jar as process. - * - * @private - * @param {...any[]} args - Additional command line arguments for the server - * @returns {SarosServer} Itself - * @memberof SarosServer - */ - private startProcess(...args: any[]): SarosServer { - - var pathToJar = path.resolve(this.context.extensionPath, 'dist', 'saros.lsp.jar'); - - console.log('spawning jar process'); - this.process = process.spawn('java', ['-jar', pathToJar].concat(args)); - - return this; - } - - /** - * Attaches listeners for debug informations and prints - * retrieved data to a newly created [output channel](#vscode.OutputChannel). - * - * @private - * @param {boolean} isEnabled - Wether debug output is redirected or not - * @returns {SarosServer} Itself - * @memberof SarosServer - */ - private withDebug(isEnabled: boolean): SarosServer { - - if(this.process === undefined) { - throw new Error('Server process is undefined'); - } - - if(!isEnabled) { - return this; - } - - let output = vscode.window.createOutputChannel('Saros (Debug)'); - - this.process.stdout.on("data", (data) => { - output.appendLine(data); - }); - - this.process.stderr.on("data", (data) => { - output.appendLine(data); - }); - - return this; - } - - /** - * Attaches listeners to observe termination of the server. - * - * @private - * @returns {SarosServer} Itself - * @memberof SarosServer - */ - private withExitAware(): SarosServer { - - if(this.process === undefined) { - throw new Error('Server process is undefined'); - } - - this.process.on('error', (error) => { - vscode.window.showErrorMessage(`child process creating error with error ${error}`); - }); - - let self = this; - this.process.on('close', (code) => { - var showMessageFunc; - if(code === 0) { - showMessageFunc = vscode.window.showInformationMessage; - } else { - showMessageFunc = vscode.window.showWarningMessage; - } - - self.process = undefined; - showMessageFunc(`child process exited with code ${code}`); - }); - - return this; - } -} \ No newline at end of file diff --git a/vscode/src/extension.ts b/vscode/src/extension.ts index 46d39b318d..6ad916acc6 100644 --- a/vscode/src/extension.ts +++ b/vscode/src/extension.ts @@ -1,7 +1,6 @@ import * as vscode from 'vscode'; -import { Disposable } from 'vscode-jsonrpc'; -import { sarosExtensionInstance } from './core/saros-extension'; -import { activateAccounts } from './account/activator'; +import {sarosExtensionInstance} from './core'; +import {activateAccounts} from './account'; /** * Activation function of the extension. @@ -10,35 +9,36 @@ import { activateAccounts } from './account/activator'; * @param {vscode.ExtensionContext} context - The extension context */ export function activate(context: vscode.ExtensionContext) { + sarosExtensionInstance.setContext(context) + .init() + .then(() => { + activateAccounts(sarosExtensionInstance); - sarosExtensionInstance.setContext(context) - .init() - .then(() => { - activateAccounts(sarosExtensionInstance); + console.log('Extension "Saros" is now active!'); + }) + .catch((reason) => { + console.log(reason); + vscode.window.showErrorMessage( + 'Saros extension did not start propertly.' + + 'Reason: ' + reason); + }); - console.log('Extension "Saros" is now active!'); - }) - .catch(reason => { - console.log(reason); - vscode.window.showErrorMessage('Saros extension did not start propertly.' - + 'Reason: ' + reason); //TODO: restart feature - }); - - context.subscriptions.push(createStatusBar()); + context.subscriptions.push(createStatusBar()); } /** * Creates the status bar. * - * @returns {Disposable} The status bar item as [disposable](#Disposable) + * @return {Disposable} The status bar item as [disposable](#Disposable) */ -function createStatusBar(): Disposable { +function createStatusBar(): vscode.Disposable { + const statusBarItem = + vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, + Number.MAX_VALUE); + statusBarItem.text = 'Saros'; + statusBarItem.show(); - let statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, Number.MAX_VALUE); - statusBarItem.text = "Saros"; - statusBarItem.show(); - - return statusBarItem; + return statusBarItem; } /** @@ -47,5 +47,5 @@ function createStatusBar(): Disposable { * @export */ export function deactivate() { - console.log("deactivated"); + console.log('deactivated'); } diff --git a/vscode/src/test/runTest.ts b/vscode/src/test/runTest.ts deleted file mode 100644 index 1eabfa33d2..0000000000 --- a/vscode/src/test/runTest.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as path from 'path'; - -import { runTests } from 'vscode-test'; - -async function main() { - try { - // The folder containing the Extension Manifest package.json - // Passed to `--extensionDevelopmentPath` - const extensionDevelopmentPath = path.resolve(__dirname, '../../'); - - // The path to test runner - // Passed to --extensionTestsPath - const extensionTestsPath = path.resolve(__dirname, './suite/index'); - - // Download VS Code, unzip it and run the integration test - await runTests({ extensionDevelopmentPath, extensionTestsPath }); - } catch (err) { - console.error('Failed to run tests'); - process.exit(1); - } -} - -main(); diff --git a/vscode/src/test/suite/extension.test.ts b/vscode/src/test/suite/extension.test.ts deleted file mode 100644 index 820cf9065a..0000000000 --- a/vscode/src/test/suite/extension.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as assert from 'assert'; -import { before } from 'mocha'; - -// 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 '../extension'; - -suite('Extension Test Suite', () => { - before(() => { - vscode.window.showInformationMessage('Start all tests.'); - }); - - test('Sample test', () => { - assert.equal(-1, [1, 2, 3].indexOf(5)); - assert.equal(-1, [1, 2, 3].indexOf(0)); - }); -}); diff --git a/vscode/src/test/suite/index.ts b/vscode/src/test/suite/index.ts deleted file mode 100644 index 2cd152c295..0000000000 --- a/vscode/src/test/suite/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as path from 'path'; -import * as Mocha from 'mocha'; -import * as glob from 'glob'; - -export function run(): Promise { - // Create the mocha test - const mocha = new Mocha({ - ui: 'tdd', - }); - mocha.useColors(true); - - const testsRoot = path.resolve(__dirname, '..'); - - return new Promise((c, e) => { - glob('**/**.test.js', { cwd: testsRoot }, (err, files) => { - if (err) { - return e(err); - } - - // Add files to the test suite - files.forEach(f => mocha.addFile(path.resolve(testsRoot, f))); - - try { - // Run the mocha test - mocha.run(failures => { - if (failures > 0) { - e(new Error(`${failures} tests failed.`)); - } else { - c(); - } - }); - } catch (err) { - e(err); - } - }); - }); -} diff --git a/vscode/tslint.json b/vscode/tslint.json deleted file mode 100644 index c81ff28fca..0000000000 --- a/vscode/tslint.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "rules": { - "no-string-throw": true, - "no-unused-expression": true, - "no-duplicate-variable": true, - "curly": true, - "class-name": true, - "semicolon": [ - true, - "always" - ], - "triple-equals": true - }, - "defaultSeverity": "warning" -} diff --git a/vscode/webpack.config.js b/vscode/webpack.config.js index a76643bba5..d62c3663e1 100644 --- a/vscode/webpack.config.js +++ b/vscode/webpack.config.js @@ -1,28 +1,39 @@ -//@ts-check +// @ts-check 'use strict'; const path = require('path'); -/**@type {import('webpack').Configuration}*/ +/** + * @type {import('webpack').Configuration} + */ const config = { - target: 'node', // vscode extensions run in a Node.js-context πŸ“– -> https://webpack.js.org/configuration/node/ + // vscode extensions run in a Node.js-context + // πŸ“– -> https://webpack.js.org/configuration/node/ + target: 'node', - entry: './src/extension.ts', // the entry point of this extension, πŸ“– -> https://webpack.js.org/configuration/entry-context/ + // the entry point of this extension, + // πŸ“– -> https://webpack.js.org/configuration/entry-context/ + entry: './src/extension.ts', output: { - // the bundle is stored in the 'dist' folder (check package.json), πŸ“– -> https://webpack.js.org/configuration/output/ + // the bundle is stored in the 'dist' folder (check package.json), + // πŸ“– -> https://webpack.js.org/configuration/output/ path: path.resolve(__dirname, 'dist'), filename: 'extension.js', libraryTarget: 'commonjs2', - devtoolModuleFilenameTemplate: '../[resource-path]' + devtoolModuleFilenameTemplate: '../[resource-path]', }, devtool: 'source-map', externals: { - vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, πŸ“– -> https://webpack.js.org/configuration/externals/ + // the vscode-module is created on-the-fly and must be excluded. + // Add other modules that cannot be webpack'ed, + // πŸ“– -> https://webpack.js.org/configuration/externals/ + vscode: 'commonjs vscode', }, resolve: { - // support reading TypeScript and JavaScript files, πŸ“– -> https://github.com/TypeStrong/ts-loader - extensions: ['.ts', '.js'] + // support reading TypeScript and JavaScript files, + // πŸ“– -> https://github.com/TypeStrong/ts-loader + extensions: ['.ts', '.js'], }, module: { rules: [ @@ -31,11 +42,11 @@ const config = { exclude: /node_modules/, use: [ { - loader: 'ts-loader' - } - ] - } - ] - } + loader: 'ts-loader', + }, + ], + }, + ], + }, }; -module.exports = config; \ No newline at end of file +module.exports = config;