diff --git a/vscode-extensions/vscode-application-properties/.gitignore b/vscode-extensions/vscode-application-properties/.gitignore new file mode 100644 index 000000000..d027a79a1 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/.gitignore @@ -0,0 +1,14 @@ +out +node_modules +target +*.log +*.log.* +classpath.txt +*.vsix +repo +.idea +.project +.classpath +*.iml +.DS_Store +**/.DS_Store \ No newline at end of file diff --git a/vscode-extensions/vscode-application-properties/.settings/org.eclipse.jdt.core.prefs b/vscode-extensions/vscode-application-properties/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..714351aec --- /dev/null +++ b/vscode-extensions/vscode-application-properties/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,5 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/vscode-extensions/vscode-application-properties/.settings/org.eclipse.m2e.core.prefs b/vscode-extensions/vscode-application-properties/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 000000000..f897a7f1c --- /dev/null +++ b/vscode-extensions/vscode-application-properties/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/vscode-extensions/vscode-application-properties/.vscode/launch.json b/vscode-extensions/vscode-application-properties/.vscode/launch.json new file mode 100644 index 000000000..52ebf9f9e --- /dev/null +++ b/vscode-extensions/vscode-application-properties/.vscode/launch.json @@ -0,0 +1,28 @@ +// A launch configuration that compiles the extension and then opens it inside a new window +{ + "version": "0.1.0", + "configurations": [ + { + "name": "Launch Extension", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}" ], + "stopOnEntry": false, + "sourceMaps": true, + "outDir": "${workspaceRoot}/out/lib", + "preLaunchTask": "npm" + }, + { + "name": "Launch Tests", + "type": "extensionHost", + "request": "launch", + "runtimeExecutable": "${execPath}", + "args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ], + "stopOnEntry": false, + "sourceMaps": true, + "outDir": "${workspaceRoot}/out/test", + "preLaunchTask": "npm" + } + ] +} diff --git a/vscode-extensions/vscode-application-properties/.vscode/settings.json b/vscode-extensions/vscode-application-properties/.vscode/settings.json new file mode 100644 index 000000000..c5592bee9 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/.vscode/settings.json @@ -0,0 +1,12 @@ +// Place your settings in this file to overwrite default and user settings. +{ + "files.exclude": { + "out": true, // set this to true to hide the "out" folder with the compiled JS files + "node_modules": false, + "target": true + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + "typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version +} \ No newline at end of file diff --git a/vscode-extensions/vscode-application-properties/.vscode/tasks.json b/vscode-extensions/vscode-application-properties/.vscode/tasks.json new file mode 100644 index 000000000..1cbb9fd45 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/.vscode/tasks.json @@ -0,0 +1,30 @@ +// Available variables which can be used inside of strings. +// ${workspaceRoot}: the root folder of the team +// ${file}: the current opened file +// ${fileBasename}: the current opened file's basename +// ${fileDirname}: the current opened file's dirname +// ${fileExtname}: the current opened file's extension +// ${cwd}: the current working directory of the spawned process + +// A task runner that calls a custom npm script that compiles the extension. +{ + "version": "0.1.0", + + // we want to run npm + "command": "npm", + + // the command is a shell script + "isShellCommand": true, + + // show the output window only if unrecognized errors occur. + "showOutput": "silent", + + // we run the custom script "compile" as defined in package.json + "args": ["run", "compile"], //, "--loglevel", "silent"], + + // The tsc compiler is started in watching mode + "isWatching": true, + + // use the standard tsc in watch mode problem matcher to find compile problems in the output. + "problemMatcher": "$tsc-watch" +} \ No newline at end of file diff --git a/vscode-extensions/vscode-application-properties/.vscodeignore b/vscode-extensions/vscode-application-properties/.vscodeignore new file mode 100644 index 000000000..71d147879 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/.vscodeignore @@ -0,0 +1,30 @@ +# IDE configs +.vscode/** +.idea/** +*.iml +javaconfig.json +classpath.txt +tsconfig.json +tsd.json +*.xml + +# Logs +*.log* + +# Sources +typings/** +src/** +test/** +lib/** +!lib/javaconfig.schema.json +repo/** +scripts/** + +# Compiler output +out/test/** +target/** +!target/fat-jar.jar + +# Extensions +.gitignore +**/*.map diff --git a/vscode-extensions/vscode-application-properties/README.md b/vscode-extensions/vscode-application-properties/README.md new file mode 100644 index 000000000..d409f5863 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/README.md @@ -0,0 +1,39 @@ +# VS Code Language Server for Spring Boot Application Properties + +Initial implementation of the Language Server for Spring Boot application properties. + +# Running + +The extension implemented in this example consists out of two pieces: + + - client: a typescript js app that launches and connects to the language server. + - server: server app, implemented in Java. + +First build the server: + + mvn clean package + +The server will be produced in `out/fat-jar.jar`. + +Then build the client: + + npm clean install + +Now you can open the client-app in vscode. From the root of this project. + + code . + +To launch the language server in a vscode runtime, press F5. + +# Debugging + +To debug the language server, open `lib/Main.ts` and edit to set the +`DEBUG` constant to `true`. When you laucnh the app next by pressing +`F5` it will launch with debug options being passed to the JVM. + +You can then connect a 'Remote Java' Eclipse debugger on port 8000. + +Note that in debug mode we launch not from the 'fatjar' produced by the +maven build, but instead use the classes from 'target/classes' directory. +This allows you to edit the server code in Eclipse and relaunch the +client from vscode without rebuilding the fatjar. diff --git a/vscode-extensions/vscode-application-properties/lib/.gitignore b/vscode-extensions/vscode-application-properties/lib/.gitignore new file mode 100644 index 000000000..a6c7c2852 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/lib/.gitignore @@ -0,0 +1 @@ +*.js diff --git a/vscode-extensions/vscode-application-properties/lib/Main.ts b/vscode-extensions/vscode-application-properties/lib/Main.ts new file mode 100644 index 000000000..7a5dc0e84 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/lib/Main.ts @@ -0,0 +1,187 @@ +'use strict'; +// The module 'vscode' contains the VS Code extensibility API +// Import the module and reference it with the alias vscode in your code below + +import * as VSCode from 'vscode'; +import * as Path from 'path'; +import * as FS from 'fs'; +import * as PortFinder from 'portfinder'; +import * as Net from 'net'; +import * as ChildProcess from 'child_process'; +import {LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo} from 'vscode-languageclient'; +import {TextDocument} from 'vscode'; + +PortFinder.basePort = 55282; + +var DEBUG = true; +const DEBUG_ARG = '-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=n'; + //If DEBUG is falsy then + // we launch from the 'fat jar' (which has to be built by running mvn package) + //if DEBUG is truthy then + // - we launch the Java project directly from the classes folder produced by Eclipse JDT compiler + // - we add DEBUG_ARG to the launch so that remote debugger can attach on port 8000 + +function getClasspath(context: VSCode.ExtensionContext):string { + if (DEBUG) { + try { + let projectDir = context.extensionPath; + let classpathFile = Path.resolve(projectDir, "classpath.txt"); + //TODO: async read? + let classpath = FS.readFileSync(classpathFile, 'utf8'); + classpath = Path.resolve(projectDir, 'target/classes') + ':' + classpath; + return classpath; + } catch (e) { + //Expected if you classpath.txt file isn't packaged. So this means we are running in packaged mode. + VSCode.window.showInformationMessage('classpath file not found, so disabling DEBUG mode '+e); + //Nasty! Be careful, this assumes 'getClasspath' is called before computing debug args. + DEBUG = false; + } + } + return Path.resolve(context.extensionPath, "out", "fat-jar.jar"); +} + +/** Called when extension is activated */ +export function activate(context: VSCode.ExtensionContext) { + let javaExecutablePath = findJavaExecutable('java'); + + if (javaExecutablePath == null) { + VSCode.window.showErrorMessage("Couldn't locate java in $JAVA_HOME or $PATH"); + return; + } + + isJava8(javaExecutablePath).then(eight => { + if (!eight) { + VSCode.window.showErrorMessage('Java language support requires Java 8 (using ' + javaExecutablePath + ')'); + return; + } + + // Options to control the language client + let clientOptions: LanguageClientOptions = { + + // HACK!!! documentSelector only takes string|string[] where string is language id, but DocumentFilter object is passed instead + // Reasons: + // 1. documentSelector is just passed over to functions like #registerHoverProvider(documentSelector, ...) that take documentSelector + // parameter in string | DocumentFilter | string[] | DocumentFilter[] format + // 2. Combination of non string|string[] documentSelector parameter and synchronize.textDocumentFilter function makes doc synchronization + // events pass on to Language Server only for documents for which function passed via textDocumentFilter property return true + + // TODO: Remove cast ones https://github.com/Microsoft/vscode-languageserver-node/issues/9 is resolved + documentSelector: [ + {language: 'ini', pattern: '**/application*.properties'}, + {language: 'java-properties', pattern: '**/application*.properties'} + ], + synchronize: { + // Synchronize the setting section to the server: + configurationSection: 'languageServerExample', + // Notify the server about file changes to 'javaconfig.json' files contain in the workspace + fileEvents: [ + //What's this for? Don't think it does anything useful for this example: + VSCode.workspace.createFileSystemWatcher('**/.clientrc') + ], + + // TODO: Remove textDocumentFilter property ones https://github.com/Microsoft/vscode-languageserver-node/issues/9 is resolved + textDocumentFilter: function(textDocument : TextDocument) : boolean { + return /^(.*\/)?application[^\s\\/]*.properties$/i.test(textDocument.fileName); + } + } + } + + function createServer(): Promise { + return new Promise((resolve, reject) => { + PortFinder.getPort((err, port) => { + Net.createServer(socket => { + console.log('Child process connected on port ' + port); + + resolve({ + reader: socket, + writer: socket + }); + }).listen(port, () => { + let options = { + cwd: VSCode.workspace.rootPath + }; + let child: ChildProcess.ChildProcess; + let classpath = getClasspath(context); + let args = [ + '-Dserver.port=' + port, + '-cp', classpath, + 'org.springframework.ide.vscode.yaml.Main' + ]; + if (DEBUG) { + args.unshift(DEBUG_ARG); + } + console.log(javaExecutablePath + ' ' + args.join(' ')); + + // Start the child java process + child = ChildProcess.execFile(javaExecutablePath, args, options); + child.stdout.on('data', (data) => { + console.log(data); + }); + child.stderr.on('data', (data) => { + console.error(data); + }) + }); + }); + }); + } + + // Create the language client and start the client. + let client = new LanguageClient('lsapi-example', 'Language Server Example', + createServer, clientOptions); + 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); + }); +} + +function isJava8(javaExecutablePath: string): Promise { + return new Promise((resolve, reject) => { + let result = ChildProcess.execFile(javaExecutablePath, ['-version'], { }, (error, stdout, stderr) => { + let eight = stderr.indexOf('1.8') >= 0; + + resolve(eight); + }); + }); +} + +function findJavaExecutable(binname: string) { + binname = correctBinname(binname); + + // First search each JAVA_HOME bin folder + if (process.env['JAVA_HOME']) { + let workspaces = process.env['JAVA_HOME'].split(Path.delimiter); + for (let i = 0; i < workspaces.length; i++) { + let binpath = Path.join(workspaces[i], 'bin', binname); + if (FS.existsSync(binpath)) { + return binpath; + } + } + } + + // Then search PATH parts + if (process.env['PATH']) { + let pathparts = process.env['PATH'].split(Path.delimiter); + for (let i = 0; i < pathparts.length; i++) { + let binpath = Path.join(pathparts[i], binname); + if (FS.existsSync(binpath)) { + return binpath; + } + } + } + + // Else return the binary name directly (this will likely always fail downstream) + return null; +} + +function correctBinname(binname: string) { + if (process.platform === 'win32') + return binname + '.exe'; + else + return binname; +} + +// this method is called when your extension is deactivated +export function deactivate() { +} diff --git a/vscode-extensions/vscode-application-properties/package.json b/vscode-extensions/vscode-application-properties/package.json new file mode 100644 index 000000000..a9fbcea37 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/package.json @@ -0,0 +1,60 @@ +{ + "name": "application-properties", + "displayName": "Spring Boot Application Properties Support", + "description": "Provides validation and content assist for Spring Boot application.properties file", + "icon": "spring.gif", + "version": "0.0.1", + "publisher": "pivotal", + "repository": { + "type": "git", + "url": "https://github.com/spring-projects/sts4.git" + }, + "license": "EPL-1.0", + "engines": { + "vscode": "^0.10.10" + }, + "categories": [ + "Languages", + "Linters" + ], + "keywords": [ + "java-properties", "spring-boot", "application-properties" + ], + "activationEvents": [ + "onLanguage:ini", + "onLanguage:java-properties" + ], + "main": "./out/lib/Main", + "files": [ + "target/fat-jar.jar" + ], + "contributes": { + "configuration": { + "type": "object", + "title": "Example configuration", + "properties": { + "languageServerExample.maxNumberOfProblems": { + "type": "number", + "default": 100, + "description": "Controls the maximum number of problems produced by the server." + } + } + } + }, + "preview": "true", + "scripts": { + "vscode:prepublish": "node ./node_modules/vscode/bin/compile", + "compile": "node ./node_modules/vscode/bin/compile -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install", + "test": "mocha out/test" + }, + "dependencies": { + "portfinder": "^0.4.0", + "vscode-languageclient": "^2.2.1" + }, + "devDependencies": { + "typescript": "^1.8.5", + "vscode": "^0.11.0", + "mocha": "^2.4.5" + } +} diff --git a/vscode-extensions/vscode-application-properties/package.sh b/vscode-extensions/vscode-application-properties/package.sh new file mode 100755 index 000000000..3cda60f54 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/package.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +## run this script to package up this extension using vsce tool. +## This assumes you have vsce tool installed. +## You can install it via npm + +set -e # fail at the first sign of trouble + +#Ensure commons are built and uptodate in local maven cache +mvn -f ../commons/pom.xml clean install +mvn clean package +npm install +vsce package + + diff --git a/vscode-extensions/vscode-application-properties/pom.xml b/vscode-extensions/vscode-application-properties/pom.xml new file mode 100644 index 000000000..93cef0be8 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/pom.xml @@ -0,0 +1,128 @@ + + + 4.0.0 + vscode-application-properties + jar + + + org.springframework.ide.vscode + commons-parent + 0.0.1-SNAPSHOT + ../commons/pom.xml + + + + + + jars-repository + Local repository for JAR files + file://${basedir}/repo + + + + + oss-sonatype + oss-sonatype + https://oss.sonatype.org/content/repositories/snapshots/ + + true + + + + + + + distribution-repository + Temporary Staging Repository + file://${basedir}/dist + + + + + + + com.google.guava + guava + 18.0 + + + + org.springframework.ide.vscode + java-properties + ${project.version} + + + + org.springframework.ide.vscode + language-server-commons + ${project.version} + + + + org.springframework.ide.vscode + language-server-test-harness + ${project.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.3 + + 1.8 + 1.8 + + + + + org.apache.maven.plugins + maven-dependency-plugin + 2.9 + + + build-classpath + generate-sources + + build-classpath + + + + + classpath.txt + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.4.1 + + ${project.basedir}/out/fat-jar.jar + + + *:* + + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + package + + shade + + + + + + + diff --git a/vscode-extensions/vscode-application-properties/spring.gif b/vscode-extensions/vscode-application-properties/spring.gif new file mode 100644 index 000000000..70b850977 Binary files /dev/null and b/vscode-extensions/vscode-application-properties/spring.gif differ diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/boot/properties/ApplicationPropertiesLanguageServer.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/boot/properties/ApplicationPropertiesLanguageServer.java new file mode 100644 index 000000000..4dd9c9fae --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/boot/properties/ApplicationPropertiesLanguageServer.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.properties; + +import java.util.stream.Collectors; + +import org.springframework.ide.vscode.properties.antlr.parser.AntlrParser; +import org.springframework.ide.vscode.properties.parser.ParseResults; +import org.springframework.ide.vscode.properties.parser.Parser; +import org.springframework.ide.vscode.util.SimpleLanguageServer; +import org.springframework.ide.vscode.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.util.TextDocument; + +import io.typefox.lsapi.Diagnostic; +import io.typefox.lsapi.DiagnosticImpl; +import io.typefox.lsapi.ServerCapabilities; +import io.typefox.lsapi.ServerCapabilitiesImpl; + +/** + * Language Server for Spring Boot Application Properties files + * + * @author Alex Boyko + * + */ +public class ApplicationPropertiesLanguageServer extends SimpleLanguageServer { + + private static final String SYNTAX_ERROR_HEADER_MSG = "Syntax Error: "; + private static final String YNTAX_ERROR_MSG__UNEXPECTED_END_OF_INPUT = "Unexpected end of input, value identifier is expected"; + private static final String SYNTAX_ERROR_MSG__UNEXPECTED_END_OF_LINE = "Unexpected end of line, value identifier is expected"; + + private ParseResults parseResults; + private Parser parser; + + public ApplicationPropertiesLanguageServer() { + this.parser = new AntlrParser(); + SimpleTextDocumentService documents = getTextDocumentService(); + + documents.onDidChangeContent(params -> { + System.out.println("Document changed: "+params); + TextDocument doc = params.getDocument(); + parseResults = parser.parse(doc.getText()); + validateDocument(documents, doc); + }); + + } + + private void validateDocument(SimpleTextDocumentService documents, TextDocument doc) { + documents.publishDiagnostics(doc, parseResults.syntaxErrors.stream().map(problem -> { + DiagnosticImpl diagnostic = new DiagnosticImpl(); + diagnostic.setMessage(createSyntaxErrorMessage(problem.getMessage())); + diagnostic.setCode(problem.getCode()); + diagnostic.setSeverity(Diagnostic.SEVERITY_ERROR); + diagnostic.setSource("java-properties"); + diagnostic.setRange(doc.toRange(problem.getOffset(), problem.getLength())); + return diagnostic; + }).collect(Collectors.toList())); + } + + private static String createSyntaxErrorMessage(String parserMessage) { + String message = parserMessage; + if (parserMessage.contains("extraneous input '\\n' expecting")) { + message = SYNTAX_ERROR_MSG__UNEXPECTED_END_OF_LINE; + } else if (parserMessage.contains("mismatched input '' expecting")) { + message = YNTAX_ERROR_MSG__UNEXPECTED_END_OF_INPUT; + } + return SYNTAX_ERROR_HEADER_MSG + message; + } + + @Override + protected ServerCapabilitiesImpl getServerCapabilities() { + ServerCapabilitiesImpl c = new ServerCapabilitiesImpl(); + + c.setTextDocumentSync(ServerCapabilities.SYNC_FULL); + + return c; + } +} diff --git a/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/boot/properties/Main.java b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/boot/properties/Main.java new file mode 100644 index 000000000..4662c3d13 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/main/java/org/springframework/ide/vscode/boot/properties/Main.java @@ -0,0 +1,145 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.properties; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.springframework.ide.vscode.util.LoggingFormat; + +import io.typefox.lsapi.services.json.LoggingJsonAdapter; + +/** + * Starts up Language Server process + * + * @author Alex Boyko + * + */ +public class Main { + private static final Logger LOG = Logger.getLogger("main"); + + public static void main(String[] args) throws IOException { + LOG.info("Starting LS"); + Connection connection = null; + try { + LoggingFormat.startLogging(); + + connection = connectToNode(); + + run(connection); + } catch (Throwable t) { + LOG.log(Level.SEVERE, t.getMessage(), t); + System.exit(1); + } finally { + if (connection != null) { + connection.dispose(); + } + } + } + + private static Connection connectToNode() throws IOException { + String port = System.getProperty("server.port"); + + if (port != null) { + Socket socket = new Socket("localhost", Integer.parseInt(port)); + + InputStream in = socket.getInputStream(); + OutputStream out = socket.getOutputStream(); + + OutputStream intercept = new OutputStream() { + + @Override + public void write(int b) throws IOException { + out.write(b); + } + }; + + LOG.info("Connected to parent using socket on port " + port); + + return new Connection(in, intercept, socket); + } + else { + InputStream in = System.in; + PrintStream out = System.out; + + LOG.info("Connected to parent using stdio"); + + return new Connection(in, out, null); + } + } + + private static class Connection { + final InputStream in; + final OutputStream out; + final Socket socket; + + private Connection(InputStream in, OutputStream out, Socket socket) { + this.in = in; + this.out = out; + this.socket = socket; + } + + void dispose() { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + LOG.log(Level.SEVERE, e.getMessage(), e); + } + } + if (out != null) { + try { + out.close(); + } catch (IOException e) { + LOG.log(Level.SEVERE, e.getMessage(), e); + } + } + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + LOG.log(Level.SEVERE, e.getMessage(), e); + } + } + } + } + + /** + * Listen for requests from the parent node process. + * Send replies asynchronously. + * When the request stream is closed, wait for 5s for all outstanding responses to compute, then return. + */ + public static void run(Connection connection) { + ApplicationPropertiesLanguageServer server = new ApplicationPropertiesLanguageServer(); + LoggingJsonAdapter jsonServer = new LoggingJsonAdapter(server); + jsonServer.setMessageLog(new PrintWriter(System.out)); + + jsonServer.connect(connection.in, connection.out); + jsonServer.getProtocol().addErrorListener((message, err) -> { + LOG.log(Level.SEVERE, message, err); + + server.onError(message, err); + }); + + try { + jsonServer.join(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } +} diff --git a/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/boot/properties/test/ApplicationPropertiesEditorTest.java b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/boot/properties/test/ApplicationPropertiesEditorTest.java new file mode 100644 index 000000000..d02e1742c --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/boot/properties/test/ApplicationPropertiesEditorTest.java @@ -0,0 +1,55 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.properties.test; + +import org.junit.Test; +import org.springframework.ide.vscode.boot.properties.ApplicationPropertiesLanguageServer; +import org.springframework.ide.vscode.testharness.Editor; +import org.springframework.ide.vscode.testharness.LanguageServerHarness; + +/** + * Boot App Properties Editor tests + * + * @author Alex Boyko + * + */ +public class ApplicationPropertiesEditorTest { + + private static final String SYNTAX_ERROR__UNEXPECTED_END_OF_INPUT = "Unexpected end of input, value identifier is expected"; + private static final String SYNTAX_ERROR__UNEXPECTED_END_OF_LINE = "Unexpected end of line, value identifier is expected"; + + @Test + public void testReconcileCatchesParseError() throws Exception { + LanguageServerHarness harness = new LanguageServerHarness(ApplicationPropertiesLanguageServer::new); + harness.intialize(null); + + Editor editor = harness.newEditor("key\n"); + editor.assertProblems("key|" + SYNTAX_ERROR__UNEXPECTED_END_OF_LINE); + } + + @Test public void linterRunsOnDocumentOpenAndChange() throws Exception { + LanguageServerHarness harness = new LanguageServerHarness(ApplicationPropertiesLanguageServer::new); + harness.intialize(null); + + Editor editor = harness.newEditor("key"); + + editor.assertProblems("key|" + SYNTAX_ERROR__UNEXPECTED_END_OF_INPUT); + + editor.setText( + "problem\n" + + "key=value\n" + + "another" + ); + + editor.assertProblems("problem|" + SYNTAX_ERROR__UNEXPECTED_END_OF_LINE, "another|" + SYNTAX_ERROR__UNEXPECTED_END_OF_INPUT); + } + +} diff --git a/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/boot/properties/test/ApplicationPropertiesLanguageServerTest.java b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/boot/properties/test/ApplicationPropertiesLanguageServerTest.java new file mode 100644 index 000000000..d9dedd35a --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/test/java/org/springframework/ide/vscode/boot/properties/test/ApplicationPropertiesLanguageServerTest.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2016 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.properties.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.Paths; + +import org.junit.Test; +import org.springframework.ide.vscode.boot.properties.ApplicationPropertiesLanguageServer; +import org.springframework.ide.vscode.testharness.LanguageServerHarness; + +import io.typefox.lsapi.InitializeResult; +import io.typefox.lsapi.ServerCapabilities; + +/** + * Boot app properties file language server tests + * + * @author Alex Boyko + * + */ +public class ApplicationPropertiesLanguageServerTest { + + public static File getTestResource(String name) throws URISyntaxException { + return Paths.get(ApplicationPropertiesLanguageServer.class.getResource(name).toURI()).toFile(); + } + + @Test + public void createAndInitializeServerWithWorkspace() throws Exception { + LanguageServerHarness harness = new LanguageServerHarness(ApplicationPropertiesLanguageServer::new); + File workspaceRoot = getTestResource("/workspace/"); + assertExpectedInitResult(harness.intialize(workspaceRoot)); + } + + @Test + public void createAndInitializeServerWithoutWorkspace() throws Exception { + File workspaceRoot = null; + LanguageServerHarness harness = new LanguageServerHarness(ApplicationPropertiesLanguageServer::new); + assertExpectedInitResult(harness.intialize(workspaceRoot)); + } + + private void assertExpectedInitResult(InitializeResult initResult) { + assertThat(initResult.getCapabilities().getTextDocumentSync()).isEqualTo(ServerCapabilities.SYNC_FULL); + } + +} diff --git a/vscode-extensions/vscode-application-properties/src/test/resources/workspace/testfile.properties b/vscode-extensions/vscode-application-properties/src/test/resources/workspace/testfile.properties new file mode 100644 index 000000000..e1823ca8f --- /dev/null +++ b/vscode-extensions/vscode-application-properties/src/test/resources/workspace/testfile.properties @@ -0,0 +1,4 @@ +#There are 2 syntax errors in this file +abc +key: value +sdcdsc diff --git a/vscode-extensions/vscode-application-properties/tsconfig.json b/vscode-extensions/vscode-application-properties/tsconfig.json new file mode 100644 index 000000000..afb8e79d2 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "outDir": "out", + "sourceMap": true, + "rootDir": "." + }, + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/vscode-extensions/vscode-application-properties/tsd.json b/vscode-extensions/vscode-application-properties/tsd.json new file mode 100644 index 000000000..50cd71dcf --- /dev/null +++ b/vscode-extensions/vscode-application-properties/tsd.json @@ -0,0 +1,12 @@ +{ + "version": "v4", + "repo": "borisyankov/DefinitelyTyped", + "ref": "master", + "path": "typings", + "bundle": "typings/tsd.d.ts", + "installed": { + "node/node.d.ts": { + "commit": "d22516f9f089de107d7e7d5938566377370631f6" + } + } +} diff --git a/vscode-extensions/vscode-application-properties/typings/portfinder.d.ts b/vscode-extensions/vscode-application-properties/typings/portfinder.d.ts new file mode 100644 index 000000000..554fa8db2 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/typings/portfinder.d.ts @@ -0,0 +1,7 @@ + + +declare module 'portfinder' { + var basePort: number; + + function getPort(callback: (err: any, port: number) => void); +} \ No newline at end of file diff --git a/vscode-extensions/vscode-application-properties/typings/tsd.d.ts b/vscode-extensions/vscode-application-properties/typings/tsd.d.ts new file mode 100644 index 000000000..2916e4c11 --- /dev/null +++ b/vscode-extensions/vscode-application-properties/typings/tsd.d.ts @@ -0,0 +1,2 @@ +/// +/// \ No newline at end of file