Spring Boot App Properties extension - initial commit
This commit is contained in:
14
vscode-extensions/vscode-application-properties/.gitignore
vendored
Normal file
14
vscode-extensions/vscode-application-properties/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
out
|
||||
node_modules
|
||||
target
|
||||
*.log
|
||||
*.log.*
|
||||
classpath.txt
|
||||
*.vsix
|
||||
repo
|
||||
.idea
|
||||
.project
|
||||
.classpath
|
||||
*.iml
|
||||
.DS_Store
|
||||
**/.DS_Store
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
28
vscode-extensions/vscode-application-properties/.vscode/launch.json
vendored
Normal file
28
vscode-extensions/vscode-application-properties/.vscode/launch.json
vendored
Normal file
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
12
vscode-extensions/vscode-application-properties/.vscode/settings.json
vendored
Normal file
12
vscode-extensions/vscode-application-properties/.vscode/settings.json
vendored
Normal file
@@ -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
|
||||
}
|
||||
30
vscode-extensions/vscode-application-properties/.vscode/tasks.json
vendored
Normal file
30
vscode-extensions/vscode-application-properties/.vscode/tasks.json
vendored
Normal file
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
39
vscode-extensions/vscode-application-properties/README.md
Normal file
39
vscode-extensions/vscode-application-properties/README.md
Normal file
@@ -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.
|
||||
1
vscode-extensions/vscode-application-properties/lib/.gitignore
vendored
Normal file
1
vscode-extensions/vscode-application-properties/lib/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.js
|
||||
187
vscode-extensions/vscode-application-properties/lib/Main.ts
Normal file
187
vscode-extensions/vscode-application-properties/lib/Main.ts
Normal file
@@ -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 <any> cast ones https://github.com/Microsoft/vscode-languageserver-node/issues/9 is resolved
|
||||
documentSelector: [
|
||||
<any> {language: 'ini', pattern: '**/application*.properties'},
|
||||
<any> {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<StreamInfo> {
|
||||
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<boolean> {
|
||||
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() {
|
||||
}
|
||||
60
vscode-extensions/vscode-application-properties/package.json
Normal file
60
vscode-extensions/vscode-application-properties/package.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
15
vscode-extensions/vscode-application-properties/package.sh
Executable file
15
vscode-extensions/vscode-application-properties/package.sh
Executable file
@@ -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
|
||||
|
||||
|
||||
128
vscode-extensions/vscode-application-properties/pom.xml
Normal file
128
vscode-extensions/vscode-application-properties/pom.xml
Normal file
@@ -0,0 +1,128 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>vscode-application-properties</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>commons-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../commons/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<repositories>
|
||||
<!-- Local repository with tools.jar -->
|
||||
<repository>
|
||||
<id>jars-repository</id>
|
||||
<name>Local repository for JAR files</name>
|
||||
<url>file://${basedir}/repo</url>
|
||||
</repository>
|
||||
|
||||
<!-- Sonatype snapshots repository -->
|
||||
<repository>
|
||||
<id>oss-sonatype</id>
|
||||
<name>oss-sonatype</name>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<distributionManagement>
|
||||
<repository>
|
||||
<id>distribution-repository</id>
|
||||
<name>Temporary Staging Repository</name>
|
||||
<url>file://${basedir}/dist</url>
|
||||
</repository>
|
||||
</distributionManagement>
|
||||
|
||||
<dependencies>
|
||||
<!-- Guava collections -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>18.0</version>
|
||||
</dependency>
|
||||
<!-- Java Properties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>java-properties</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- Language Servers -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>language-server-commons</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<!-- Test harness -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>language-server-test-harness</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<plugins>
|
||||
<!-- Set source 1.8 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.3</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Generate classpath.txt for VS Code -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>2.9</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>build-classpath</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>build-classpath</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<outputFile>classpath.txt</outputFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!-- Configure fat jar -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.4.1</version>
|
||||
<configuration>
|
||||
<outputFile>${project.basedir}/out/fat-jar.jar</outputFile>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
BIN
vscode-extensions/vscode-application-properties/spring.gif
Normal file
BIN
vscode-extensions/vscode-application-properties/spring.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
@@ -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 '<EOF>' 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#There are 2 syntax errors in this file
|
||||
abc
|
||||
key: value
|
||||
sdcdsc
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"outDir": "out",
|
||||
"sourceMap": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
12
vscode-extensions/vscode-application-properties/tsd.json
Normal file
12
vscode-extensions/vscode-application-properties/tsd.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
7
vscode-extensions/vscode-application-properties/typings/portfinder.d.ts
vendored
Normal file
7
vscode-extensions/vscode-application-properties/typings/portfinder.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
declare module 'portfinder' {
|
||||
var basePort: number;
|
||||
|
||||
function getPort(callback: (err: any, port: number) => void);
|
||||
}
|
||||
2
vscode-extensions/vscode-application-properties/typings/tsd.d.ts
vendored
Normal file
2
vscode-extensions/vscode-application-properties/typings/tsd.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference path="portfinder.d.ts" />
|
||||
/// <reference path="../node_modules/vscode/typings/index.d.ts" />
|
||||
Reference in New Issue
Block a user