Copied the vscode-application-yaml code from Kris's personal repo
This commit is contained in:
31
vscode-extensions/vscode-application-yaml/.classpath
Normal file
31
vscode-extensions/vscode-application-yaml/.classpath
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
11
vscode-extensions/vscode-application-yaml/.gitignore
vendored
Normal file
11
vscode-extensions/vscode-application-yaml/.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
out
|
||||
node_modules
|
||||
target
|
||||
*.log
|
||||
*.log.*
|
||||
.idea
|
||||
*.iml
|
||||
dependency-reduced-pom.xml
|
||||
classpath.txt
|
||||
*.vsix
|
||||
repo
|
||||
23
vscode-extensions/vscode-application-yaml/.project
Normal file
23
vscode-extensions/vscode-application-yaml/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>vscode-application-yaml</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
@@ -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-yaml/.vscode/launch.json
vendored
Normal file
28
vscode-extensions/vscode-application-yaml/.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-yaml/.vscode/settings.json
vendored
Normal file
12
vscode-extensions/vscode-application-yaml/.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-yaml/.vscode/tasks.json
vendored
Normal file
30
vscode-extensions/vscode-application-yaml/.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"
|
||||
}
|
||||
30
vscode-extensions/vscode-application-yaml/.vscodeignore
Normal file
30
vscode-extensions/vscode-application-yaml/.vscodeignore
Normal file
@@ -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
|
||||
53
vscode-extensions/vscode-application-yaml/README.md
Normal file
53
vscode-extensions/vscode-application-yaml/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# VS Code Language Server Example in Java
|
||||
|
||||
The repo contains the end-result of an 'exercise' to implement
|
||||
a simple VS Code Language Server in Java.
|
||||
|
||||
The starting point is a clone of this repo:
|
||||
|
||||
https://github.com/georgewfraser/vscode-javac
|
||||
|
||||
It provides Java support using the javac API.
|
||||
|
||||
As such that repo contains all the pieces necessary
|
||||
to create a working Language Server. In this
|
||||
exercise we stripped it down to the bare minimum
|
||||
to make a very basic language server similar to
|
||||
the one built in the
|
||||
[VScode tutorial on building a language server](https://code.visualstudio.com/docs/extensions/example-language-server).
|
||||
|
||||
# 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.
|
||||
BIN
vscode-extensions/vscode-application-yaml/icon.png
Normal file
BIN
vscode-extensions/vscode-application-yaml/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
1
vscode-extensions/vscode-application-yaml/lib/.gitignore
vendored
Normal file
1
vscode-extensions/vscode-application-yaml/lib/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.js
|
||||
170
vscode-extensions/vscode-application-yaml/lib/Main.ts
Normal file
170
vscode-extensions/vscode-application-yaml/lib/Main.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
'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';
|
||||
|
||||
PortFinder.basePort = 55282;
|
||||
|
||||
var DEBUG = false;
|
||||
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 = {
|
||||
// Register the server for java documents
|
||||
documentSelector: ['yaml'],
|
||||
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')
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
'com.github.kdvolder.lsapi.example.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() {
|
||||
}
|
||||
59
vscode-extensions/vscode-application-yaml/package.json
Normal file
59
vscode-extensions/vscode-application-yaml/package.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "vscode-application-yml",
|
||||
"displayName": "Spring Boot Application YML Support",
|
||||
"description": "Provides validation and content assist for spring-boot application.yml file",
|
||||
"icon": "icon.png",
|
||||
"version": "0.0.1",
|
||||
"publisher": "kdvolder",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/kdvolder/vscode-lsapi-example.git"
|
||||
},
|
||||
"license": "EPL",
|
||||
"engines": {
|
||||
"vscode": "^0.10.10"
|
||||
},
|
||||
"categories": [
|
||||
"Languages",
|
||||
"Linters"
|
||||
],
|
||||
"keywords": [
|
||||
"yaml", "springboot"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage:yaml"
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
13
vscode-extensions/vscode-application-yaml/package.sh
Executable file
13
vscode-extensions/vscode-application-yaml/package.sh
Executable file
@@ -0,0 +1,13 @@
|
||||
#!/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
|
||||
|
||||
mvn clean package ## build language server fat jar
|
||||
npm install
|
||||
vsce package
|
||||
|
||||
|
||||
166
vscode-extensions/vscode-application-yaml/pom.xml
Normal file
166
vscode-extensions/vscode-application-yaml/pom.xml
Normal file
@@ -0,0 +1,166 @@
|
||||
<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>
|
||||
<groupId>com.github.kdvolder</groupId>
|
||||
<artifactId>vscode-application-yaml</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<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>
|
||||
<properties>
|
||||
<jackson-2-version>2.5.0</jackson-2-version>
|
||||
<jersey-2-version>2.10</jersey-2-version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!-- Java implementation of VS Code language server protocol -->
|
||||
<dependency>
|
||||
<groupId>io.typefox.lsapi</groupId>
|
||||
<artifactId>io.typefox.lsapi</artifactId>
|
||||
<version>0.2.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.typefox.lsapi</groupId>
|
||||
<artifactId>io.typefox.lsapi.services</artifactId>
|
||||
<version>0.2.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.typefox.lsapi</groupId>
|
||||
<artifactId>io.typefox.lsapi.annotations</artifactId>
|
||||
<version>0.2.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- JSON -->
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-core</artifactId>
|
||||
<version>${jackson-2-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
<version>${jackson-2-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-databind</artifactId>
|
||||
<version>${jackson-2-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>${jackson-2-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
<version>${jackson-2-version}</version>
|
||||
</dependency>
|
||||
<!-- Yaml -->
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.17</version>
|
||||
</dependency>
|
||||
<!-- Guava collections -->
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>18.0</version>
|
||||
</dependency>
|
||||
<!-- Testing framework -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.11</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.5.2</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>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.github.kdvolder.lsapi.example;
|
||||
|
||||
public class ErrorCodes {
|
||||
public static final String YAML_SYNTAX_ERROR = "YAML_SYNTAX_ERROR";
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.github.kdvolder.lsapi.example;
|
||||
|
||||
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 com.github.kdvolder.lsapi.util.LoggingFormat;
|
||||
|
||||
import io.typefox.lsapi.services.json.LoggingJsonAdapter;
|
||||
|
||||
public class Main {
|
||||
private static final Logger LOG = Logger.getLogger("main");
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
LOG.info("Starting LS");
|
||||
try {
|
||||
LoggingFormat.startLogging();
|
||||
|
||||
Connection connection = connectToNode();
|
||||
|
||||
run(connection);
|
||||
} catch (Throwable t) {
|
||||
LOG.log(Level.SEVERE, t.getMessage(), t);
|
||||
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
else {
|
||||
InputStream in = System.in;
|
||||
PrintStream out = System.out;
|
||||
|
||||
LOG.info("Connected to parent using stdio");
|
||||
|
||||
return new Connection(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
private static class Connection {
|
||||
final InputStream in;
|
||||
final OutputStream out;
|
||||
|
||||
private Connection(InputStream in, OutputStream out) {
|
||||
this.in = in;
|
||||
this.out = out;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
YamlLanguageServer server = new YamlLanguageServer();
|
||||
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,168 @@
|
||||
package com.github.kdvolder.lsapi.example;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.error.MarkedYAMLException;
|
||||
import org.yaml.snakeyaml.error.YAMLException;
|
||||
import org.yaml.snakeyaml.nodes.Node;
|
||||
|
||||
import com.github.kdvolder.lsapi.util.Futures;
|
||||
import com.github.kdvolder.lsapi.util.SimpleLanguageServer;
|
||||
import com.github.kdvolder.lsapi.util.SimpleTextDocumentService;
|
||||
import com.github.kdvolder.lsapi.util.TextDocument;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import io.typefox.lsapi.CompletionItem;
|
||||
import io.typefox.lsapi.CompletionItemImpl;
|
||||
import io.typefox.lsapi.CompletionList;
|
||||
import io.typefox.lsapi.CompletionListImpl;
|
||||
import io.typefox.lsapi.CompletionOptionsImpl;
|
||||
import io.typefox.lsapi.Diagnostic;
|
||||
import io.typefox.lsapi.DiagnosticImpl;
|
||||
import io.typefox.lsapi.PositionImpl;
|
||||
import io.typefox.lsapi.RangeImpl;
|
||||
import io.typefox.lsapi.ServerCapabilities;
|
||||
import io.typefox.lsapi.ServerCapabilitiesImpl;
|
||||
|
||||
public class YamlLanguageServer extends SimpleLanguageServer {
|
||||
|
||||
private Yaml yaml = new Yaml();
|
||||
|
||||
public YamlLanguageServer() {
|
||||
SimpleTextDocumentService documents = getTextDocumentService();
|
||||
// SimpleWorkspaceService workspace = getWorkspaceService();
|
||||
documents.onDidChangeContent(params -> {
|
||||
System.out.println("Document changed: "+params);
|
||||
TextDocument doc = params.getDocument();
|
||||
validateDocument(documents, doc);
|
||||
});
|
||||
|
||||
// workspace.onDidChangeConfiguraton(settings -> {
|
||||
// System.out.println("Config changed: "+params);
|
||||
// Integer val = settings.getInt("languageServerExample", "maxNumberOfProblems");
|
||||
// if (val!=null) {
|
||||
// maxProblems = ((Number) val).intValue();
|
||||
// for (TextDocument doc : documents.getAll()) {
|
||||
// validateDocument(documents, doc);
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
documents.onCompletion(params -> {
|
||||
CompletableFuture<CompletionList> promise = new CompletableFuture<>();
|
||||
CompletionListImpl completions = new CompletionListImpl();
|
||||
completions.setIncomplete(false);
|
||||
List<CompletionItemImpl> items = new ArrayList<>();
|
||||
{
|
||||
// {
|
||||
// label: 'TypeScript',
|
||||
// kind: CompletionItemKind.Text,
|
||||
// data: 1
|
||||
// },
|
||||
CompletionItemImpl item = new CompletionItemImpl();
|
||||
item.setLabel("TypeScript");
|
||||
item.setKind(CompletionItem.KIND_TEXT);
|
||||
item.setData(1);
|
||||
items.add(item);
|
||||
}
|
||||
|
||||
{
|
||||
// {
|
||||
// label: 'JavaScript',
|
||||
// kind: CompletionItemKind.Text,
|
||||
// data: 2
|
||||
// }
|
||||
CompletionItemImpl item = new CompletionItemImpl();
|
||||
item.setLabel("JavaScript");
|
||||
item.setKind(CompletionItem.KIND_TEXT);
|
||||
item.setData(2);
|
||||
items.add(item);
|
||||
}
|
||||
completions.setItems(items);
|
||||
|
||||
promise.complete(completions);
|
||||
return promise;
|
||||
});
|
||||
|
||||
documents.onCompletionResolve((_item) -> {
|
||||
CompletionItemImpl item = (CompletionItemImpl) _item;
|
||||
Object data = item.getData();
|
||||
if (Integer.valueOf(1).equals(data)) {
|
||||
item.setDetail("TypeScript details");
|
||||
item.setDocumentation("TypeScript docs");
|
||||
} else {
|
||||
item.setDetail("JavaScript details");
|
||||
item.setDocumentation("JavaScript docs");
|
||||
}
|
||||
return Futures.of((CompletionItem)item);
|
||||
});
|
||||
}
|
||||
|
||||
private void validateDocument(SimpleTextDocumentService documents, TextDocument doc) {
|
||||
List<DiagnosticImpl> diagnostics = reconcile(documents, doc);
|
||||
documents.publishDiagnostics(doc, diagnostics);
|
||||
}
|
||||
|
||||
protected List<DiagnosticImpl> reconcile(SimpleTextDocumentService documents, TextDocument doc) {
|
||||
try {
|
||||
Iterator<Node> asts = yaml.composeAll(new StringReader(doc.getText())).iterator();
|
||||
while (asts.hasNext()) {
|
||||
asts.next();
|
||||
}
|
||||
return ImmutableList.of();
|
||||
} catch (YAMLException e) {
|
||||
return ImmutableList.of(parseError(e));
|
||||
}
|
||||
}
|
||||
|
||||
private DiagnosticImpl parseError(YAMLException e) {
|
||||
DiagnosticImpl d = new DiagnosticImpl();
|
||||
d.setMessage(getMessage(e));
|
||||
d.setRange(getRange(e));
|
||||
d.setSeverity(Diagnostic.SEVERITY_ERROR);
|
||||
d.setCode(ErrorCodes.YAML_SYNTAX_ERROR);
|
||||
d.setSource("yaml");
|
||||
return d;
|
||||
}
|
||||
|
||||
private String getMessage(YAMLException e) {
|
||||
if (e instanceof MarkedYAMLException) {
|
||||
return ((MarkedYAMLException) e).getProblem();
|
||||
}
|
||||
return e.getMessage();
|
||||
}
|
||||
|
||||
private RangeImpl getRange(YAMLException _e) {
|
||||
if (_e instanceof MarkedYAMLException) {
|
||||
MarkedYAMLException e = (MarkedYAMLException) _e;
|
||||
|
||||
PositionImpl start = new PositionImpl();
|
||||
start.setLine(e.getProblemMark().getLine());
|
||||
start.setCharacter(e.getProblemMark().getColumn());
|
||||
|
||||
RangeImpl rng = new RangeImpl();
|
||||
rng.setStart(start);
|
||||
rng.setEnd(start);
|
||||
return rng;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ServerCapabilitiesImpl getServerCapabilities() {
|
||||
ServerCapabilitiesImpl c = new ServerCapabilitiesImpl();
|
||||
|
||||
c.setTextDocumentSync(ServerCapabilities.SYNC_FULL);
|
||||
|
||||
CompletionOptionsImpl completionProvider = new CompletionOptionsImpl();
|
||||
completionProvider.setResolveProvider(true);
|
||||
c.setCompletionProvider(completionProvider);
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
public class Assert {
|
||||
|
||||
public static void isNull(String msg, Object obj) {
|
||||
if (obj!=null) {
|
||||
throw new IllegalStateException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.typefox.lsapi.CompletionList;
|
||||
import io.typefox.lsapi.TextDocumentPositionParams;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CompletionHandler {
|
||||
CompletableFuture<CompletionList> handle(TextDocumentPositionParams params);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import io.typefox.lsapi.CompletionItem;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CompletionResolveHandler {
|
||||
CompletableFuture<CompletionItem> handle(CompletionItem unresolved);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class Futures {
|
||||
|
||||
public static <T> CompletableFuture<T> of(T value) {
|
||||
CompletableFuture<T> f = new CompletableFuture<T>();
|
||||
f.complete(value);
|
||||
return f;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.JsonDeserializer;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JSR310Module;
|
||||
|
||||
public class JSON {
|
||||
|
||||
public static final ObjectMapper JSON = new ObjectMapper().registerModule(new Jdk8Module())
|
||||
.registerModule(new JSR310Module())
|
||||
.registerModule(pathAsJson())
|
||||
.configure(JsonGenerator.Feature.AUTO_CLOSE_TARGET, false);
|
||||
|
||||
private static SimpleModule pathAsJson() {
|
||||
SimpleModule m = new SimpleModule();
|
||||
|
||||
m.addSerializer(Path.class, new JsonSerializer<Path>() {
|
||||
@Override
|
||||
public void serialize(Path path,
|
||||
JsonGenerator gen,
|
||||
SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
|
||||
gen.writeString(path.toString());
|
||||
}
|
||||
});
|
||||
|
||||
m.addDeserializer(Path.class, new JsonDeserializer<Path>() {
|
||||
@Override
|
||||
public Path deserialize(JsonParser parse,
|
||||
DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
|
||||
return Paths.get(parse.getText());
|
||||
}
|
||||
});
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ListenerList<T> {
|
||||
|
||||
private List<Consumer<T>> listeners = new ArrayList<>();
|
||||
|
||||
public synchronized void fire(T evt) {
|
||||
for (Consumer<T> l : listeners) {
|
||||
l.accept(evt);
|
||||
}
|
||||
}
|
||||
|
||||
public void add(Consumer<T> l) {
|
||||
listeners.add(l);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.time.Instant;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.logging.*;
|
||||
|
||||
public class LoggingFormat extends Formatter {
|
||||
public static ThreadLocal<Integer> request = new ThreadLocal<>();
|
||||
|
||||
@Override
|
||||
public String format(LogRecord record) {
|
||||
StringJoiner joiner = new StringJoiner("\t");
|
||||
|
||||
joiner.add(record.getLevel().getName());
|
||||
joiner.add(requestAsString());
|
||||
joiner.add(Thread.currentThread().getName());
|
||||
joiner.add(Instant.ofEpochMilli(record.getMillis()).toString());
|
||||
// joiner.add(record.getLoggerName());
|
||||
joiner.add(record.getSourceClassName() + "#" + record.getSourceMethodName());
|
||||
joiner.add(record.getMessage());
|
||||
|
||||
String result = joiner.toString() + "\n";
|
||||
|
||||
Throwable thrown = record.getThrown();
|
||||
|
||||
if (thrown != null) {
|
||||
StringWriter stackTrace = new StringWriter();
|
||||
PrintWriter print = new PrintWriter(stackTrace);
|
||||
|
||||
thrown.printStackTrace(print);
|
||||
print.flush();
|
||||
|
||||
result = result + stackTrace + "\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private CharSequence requestAsString() {
|
||||
Integer i = request.get();
|
||||
|
||||
if (i == null)
|
||||
return "?";
|
||||
else
|
||||
return Integer.toString(i);
|
||||
}
|
||||
|
||||
private static boolean started = false;
|
||||
|
||||
public static void startLogging() throws IOException {
|
||||
if (!started) {
|
||||
started = true;
|
||||
|
||||
Logger root = Logger.getLogger("");
|
||||
|
||||
FileHandler file = new FileHandler("javac-services.%g.log", 100_000, 1, false);
|
||||
|
||||
root.addHandler(file);
|
||||
|
||||
for (Handler h : root.getHandlers())
|
||||
h.setFormatter(new LoggingFormat());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Convenience wrapper around a 'settings' object. Provides some useful accessor methods to
|
||||
* retrieve properties from the settings object.
|
||||
*/
|
||||
public class Settings {
|
||||
|
||||
private Object settings;
|
||||
|
||||
public Settings(Object settings) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public Integer getInt(String... names) {
|
||||
Object val = getProperty(names);
|
||||
if (val instanceof Number) {
|
||||
return ((Number) val).intValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object getProperty(String... names) {
|
||||
return getProperty(settings, names, 0);
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static Object getProperty(Object settings, String[] names, int i) {
|
||||
if (i >= names.length) {
|
||||
return settings;
|
||||
} else if (settings instanceof Map) {
|
||||
Object sub = ((Map)settings).get(names[i]);
|
||||
return getProperty(sub, names, i+1);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import io.typefox.lsapi.MessageParams;
|
||||
import io.typefox.lsapi.MessageParamsImpl;
|
||||
|
||||
public class ShowMessageException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
public final MessageParams message;
|
||||
|
||||
public ShowMessageException(MessageParams message, Exception cause) {
|
||||
super(message.getMessage(), cause);
|
||||
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static ShowMessageException error(String message, Exception cause) {
|
||||
return create(MessageParams.TYPE_ERROR, message, cause);
|
||||
}
|
||||
|
||||
public static ShowMessageException warning(String message, Exception cause) {
|
||||
return create(MessageParams.TYPE_WARNING, message, cause);
|
||||
}
|
||||
|
||||
private static ShowMessageException create(int type, String message, Exception cause) {
|
||||
MessageParamsImpl m = new MessageParamsImpl();
|
||||
|
||||
m.setMessage(message);
|
||||
m.setType(type);
|
||||
|
||||
return new ShowMessageException(m, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.typefox.lsapi.InitializeParams;
|
||||
import io.typefox.lsapi.InitializeResult;
|
||||
import io.typefox.lsapi.InitializeResultImpl;
|
||||
import io.typefox.lsapi.MessageParams;
|
||||
import io.typefox.lsapi.MessageParamsImpl;
|
||||
import io.typefox.lsapi.ServerCapabilitiesImpl;
|
||||
import io.typefox.lsapi.ShowMessageRequestParams;
|
||||
import io.typefox.lsapi.services.LanguageServer;
|
||||
import io.typefox.lsapi.services.WindowService;
|
||||
|
||||
/**
|
||||
* Abstract base class to implement LanguageServer. Bits and pieces copied from
|
||||
* the 'JavaLanguageServer' example which seem generally useful / reusable end up in
|
||||
* here so we can try to keep the subclass itself more 'clutter free' and focus on
|
||||
* what its really doing and not the 'wiring and plumbing'.
|
||||
*/
|
||||
public abstract class SimpleLanguageServer implements LanguageServer {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(SimpleLanguageServer.class.getName());
|
||||
|
||||
private Consumer<MessageParams> showMessage = m -> {};
|
||||
|
||||
private Path workspaceRoot;
|
||||
|
||||
private SimpleTextDocumentService tds;
|
||||
|
||||
private SimpleWorkspaceService workspace;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
|
||||
LOG.info("Initializing");
|
||||
String rootPath = params.getRootPath();
|
||||
if (rootPath==null) {
|
||||
LOG.warning("workspaceRoot NOT SET");
|
||||
} else {
|
||||
this.workspaceRoot= Paths.get(rootPath).toAbsolutePath().normalize();
|
||||
LOG.info("workspaceRoot = "+workspaceRoot);
|
||||
}
|
||||
|
||||
InitializeResultImpl result = new InitializeResultImpl();
|
||||
|
||||
ServerCapabilitiesImpl cap = getServerCapabilities();
|
||||
result.setCapabilities(cap);
|
||||
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WindowService getWindowService() {
|
||||
return new WindowService() {
|
||||
@Override
|
||||
public void onShowMessage(Consumer<MessageParams> callback) {
|
||||
showMessage = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShowMessageRequest(Consumer<ShowMessageRequestParams> callback) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLogMessage(Consumer<MessageParams> callback) {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable error) {
|
||||
if (error instanceof ShowMessageException)
|
||||
showMessage.accept(((ShowMessageException) error).message);
|
||||
else {
|
||||
LOG.log(Level.SEVERE, message, error);
|
||||
|
||||
MessageParamsImpl m = new MessageParamsImpl();
|
||||
|
||||
m.setMessage(message);
|
||||
m.setType(MessageParams.TYPE_ERROR);
|
||||
|
||||
showMessage.accept(m);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract ServerCapabilitiesImpl getServerCapabilities();
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
}
|
||||
|
||||
|
||||
public Path getWorkspaceRoot() {
|
||||
return workspaceRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized SimpleTextDocumentService getTextDocumentService() {
|
||||
if (tds==null) {
|
||||
tds = createTextDocumentService();
|
||||
}
|
||||
return tds;
|
||||
}
|
||||
|
||||
protected SimpleTextDocumentService createTextDocumentService() {
|
||||
return new SimpleTextDocumentService();
|
||||
}
|
||||
|
||||
public SimpleWorkspaceService createWorkspaceService() {
|
||||
return new SimpleWorkspaceService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized SimpleWorkspaceService getWorkspaceService() {
|
||||
if (workspace==null) {
|
||||
workspace = createWorkspaceService();
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import io.typefox.lsapi.CodeActionParams;
|
||||
import io.typefox.lsapi.CodeLens;
|
||||
import io.typefox.lsapi.CodeLensParams;
|
||||
import io.typefox.lsapi.Command;
|
||||
import io.typefox.lsapi.CompletionItem;
|
||||
import io.typefox.lsapi.CompletionList;
|
||||
import io.typefox.lsapi.DiagnosticImpl;
|
||||
import io.typefox.lsapi.DidChangeTextDocumentParams;
|
||||
import io.typefox.lsapi.DidCloseTextDocumentParams;
|
||||
import io.typefox.lsapi.DidOpenTextDocumentParams;
|
||||
import io.typefox.lsapi.DidSaveTextDocumentParams;
|
||||
import io.typefox.lsapi.DocumentFormattingParams;
|
||||
import io.typefox.lsapi.DocumentHighlight;
|
||||
import io.typefox.lsapi.DocumentOnTypeFormattingParams;
|
||||
import io.typefox.lsapi.DocumentRangeFormattingParams;
|
||||
import io.typefox.lsapi.DocumentSymbolParams;
|
||||
import io.typefox.lsapi.Hover;
|
||||
import io.typefox.lsapi.Location;
|
||||
import io.typefox.lsapi.PublishDiagnosticsParams;
|
||||
import io.typefox.lsapi.PublishDiagnosticsParamsImpl;
|
||||
import io.typefox.lsapi.Range;
|
||||
import io.typefox.lsapi.ReferenceParams;
|
||||
import io.typefox.lsapi.RenameParams;
|
||||
import io.typefox.lsapi.SignatureHelp;
|
||||
import io.typefox.lsapi.SymbolInformation;
|
||||
import io.typefox.lsapi.TextDocumentContentChangeEvent;
|
||||
import io.typefox.lsapi.TextDocumentPositionParams;
|
||||
import io.typefox.lsapi.TextEdit;
|
||||
import io.typefox.lsapi.VersionedTextDocumentIdentifier;
|
||||
import io.typefox.lsapi.WorkspaceEdit;
|
||||
import io.typefox.lsapi.services.TextDocumentService;
|
||||
|
||||
public class SimpleTextDocumentService implements TextDocumentService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(SimpleTextDocumentService.class.getName());
|
||||
|
||||
private Consumer<PublishDiagnosticsParams> publishDiagnostics = (p) -> {};
|
||||
|
||||
private Map<String, TextDocument> documents = new HashMap<>();
|
||||
private ListenerList<TextDocumentContentChange> documentChangeListeners = new ListenerList<>();
|
||||
private CompletionHandler completionHandler = null;
|
||||
private CompletionResolveHandler completionResolveHandler = null;
|
||||
|
||||
public synchronized void onCompletion(CompletionHandler h) {
|
||||
Assert.isNull("A completion handler is already set, multiple handlers not supported yet", completionHandler);
|
||||
this.completionHandler = h;
|
||||
}
|
||||
|
||||
public synchronized void onCompletionResolve(CompletionResolveHandler h) {
|
||||
Assert.isNull("A completionResolveHandler handler is already set, multiple handlers not supported yet", completionResolveHandler);
|
||||
this.completionResolveHandler = h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all documents this service is tracking, generally these are the documents that have been opened / changed,
|
||||
* and not yet closed.
|
||||
*/
|
||||
public synchronized Collection<TextDocument> getAll() {
|
||||
return new ArrayList<>(documents.values());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public final void didChange(DidChangeTextDocumentParams params) {
|
||||
VersionedTextDocumentIdentifier docId = params.getTextDocument();
|
||||
String url = docId.getUri();
|
||||
if (url!=null) {
|
||||
LOG.info("Document changed: "+url);
|
||||
TextDocument doc = getOrCreateDocument(url);
|
||||
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
|
||||
doc.apply(change);
|
||||
didChangeContent(doc, change);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didOpen(DidOpenTextDocumentParams params) {
|
||||
//LOG.info("didOpen: "+params);
|
||||
//Example message:
|
||||
//{
|
||||
// "jsonrpc":"2.0",
|
||||
// "method":"textDocument/didOpen",
|
||||
// "params":{
|
||||
// "textDocument":{
|
||||
// "uri":"file:///home/kdvolder/tmp/hello-java/hello.txt",
|
||||
// "languageId":"plaintext",
|
||||
// "version":1,
|
||||
// "text":"This is some text ya-all o\nsss typescript\n"
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
String url = params.getTextDocument().getUri();
|
||||
if (url!=null) {
|
||||
String text = params.getTextDocument().getText();
|
||||
TextDocument doc = getOrCreateDocument(url);
|
||||
doc.setText(text);
|
||||
TextDocumentContentChangeEvent change = new TextDocumentContentChangeEvent() {
|
||||
@Override
|
||||
public Range getRange() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getRangeLength() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
TextDocumentContentChange evt = new TextDocumentContentChange(doc, change);
|
||||
documentChangeListeners.fire(evt);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didClose(DidCloseTextDocumentParams params) {
|
||||
String url = params.getTextDocument().getUri();
|
||||
if (url!=null) {
|
||||
documents.remove(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void didChangeContent(TextDocument doc, TextDocumentContentChangeEvent change) {
|
||||
documentChangeListeners.fire(new TextDocumentContentChange(doc, change));
|
||||
}
|
||||
|
||||
public void onDidChangeContent(Consumer<TextDocumentContentChange> l) {
|
||||
documentChangeListeners.add(l);
|
||||
}
|
||||
|
||||
private synchronized TextDocument getOrCreateDocument(String url) {
|
||||
TextDocument doc = documents.get(url);
|
||||
if (doc==null) {
|
||||
documents.put(url, doc = new TextDocument(url));
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CompletionList> completion(TextDocumentPositionParams position) {
|
||||
CompletionHandler h = completionHandler;
|
||||
if (h!=null) {
|
||||
return completionHandler.handle(position);
|
||||
}
|
||||
return null; //TODO: does caller handle nulls? Or do we need to provide something that create a empty completion list?
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CompletionItem> resolveCompletionItem(CompletionItem unresolved) {
|
||||
CompletionResolveHandler h = completionResolveHandler;
|
||||
if (h!=null) {
|
||||
return h.handle(unresolved);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Hover> hover(TextDocumentPositionParams position) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<SignatureHelp> signatureHelp(TextDocumentPositionParams position) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Location>> definition(TextDocumentPositionParams position) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<DocumentHighlight> documentHighlight(TextDocumentPositionParams position) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends SymbolInformation>> documentSymbol(DocumentSymbolParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Command>> codeAction(CodeActionParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CodeLens> resolveCodeLens(CodeLens unresolved) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> onTypeFormatting(DocumentOnTypeFormattingParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didSave(DidSaveTextDocumentParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void onPublishDiagnostics(Consumer<PublishDiagnosticsParams> callback) {
|
||||
publishDiagnostics = publishDiagnostics.andThen(callback);
|
||||
}
|
||||
|
||||
public void publishDiagnostics(TextDocument doc, List<DiagnosticImpl> diagnostics) {
|
||||
if (diagnostics!=null && !diagnostics.isEmpty()) {
|
||||
PublishDiagnosticsParamsImpl params = new PublishDiagnosticsParamsImpl();
|
||||
params.setUri(doc.getUri());
|
||||
params.setDiagnostics(diagnostics);
|
||||
publishDiagnostics.accept(params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import io.typefox.lsapi.DidChangeConfigurationParams;
|
||||
import io.typefox.lsapi.DidChangeWatchedFilesParams;
|
||||
import io.typefox.lsapi.SymbolInformation;
|
||||
import io.typefox.lsapi.WorkspaceSymbolParams;
|
||||
import io.typefox.lsapi.services.WorkspaceService;
|
||||
|
||||
public class SimpleWorkspaceService implements WorkspaceService {
|
||||
|
||||
private ListenerList<Settings> configurationListeners = new ListenerList<>();
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends SymbolInformation>> symbol(WorkspaceSymbolParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChangeConfiguraton(DidChangeConfigurationParams params) {
|
||||
configurationListeners.fire(new Settings(params.getSettings()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didChangeWatchedFiles(DidChangeWatchedFilesParams params) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
public void onDidChangeConfiguraton(Consumer<Settings> l) {
|
||||
configurationListeners.add(l);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import io.typefox.lsapi.Range;
|
||||
import io.typefox.lsapi.TextDocumentContentChangeEvent;
|
||||
|
||||
public class TextDocument {
|
||||
|
||||
private final String uri;
|
||||
private String text = "";
|
||||
|
||||
public TextDocument(String uri) {
|
||||
this.uri = uri;
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
public void setText(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
public void apply(TextDocumentContentChangeEvent change) {
|
||||
Range rng = change.getRange();
|
||||
if (rng==null) {
|
||||
//full sync mode
|
||||
this.text = change.getText();
|
||||
} else {
|
||||
//incremental sync mode
|
||||
throw new IllegalStateException("Incremental sync not yet implemented");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.github.kdvolder.lsapi.util;
|
||||
|
||||
import io.typefox.lsapi.TextDocumentContentChangeEvent;
|
||||
|
||||
public class TextDocumentContentChange {
|
||||
|
||||
private final TextDocument document;
|
||||
private final TextDocumentContentChangeEvent change;
|
||||
|
||||
public TextDocumentContentChange(TextDocument doc, TextDocumentContentChangeEvent change) {
|
||||
this.document = doc;
|
||||
this.change = change;
|
||||
}
|
||||
|
||||
public TextDocument getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
public TextDocumentContentChangeEvent getChange() {
|
||||
return change;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.github.kdvolder.lsapi.example;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.kdvolder.lsapi.testharness.Editor;
|
||||
import com.github.kdvolder.lsapi.testharness.LanguageServerHarness;
|
||||
|
||||
/**
|
||||
* This class is a placeholder where we will attempt to copy and port
|
||||
* as many tests a possible from
|
||||
* org.springframework.ide.eclipse.boot.properties.editor.test.YamlEditorTests
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ApplicationYamlEditorTest {
|
||||
|
||||
@Test public void testReconcileCatchesParseError() throws Exception {
|
||||
LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
|
||||
harness.intialize(null);
|
||||
|
||||
Editor editor = harness.newEditor(
|
||||
"somemap: val\n"+
|
||||
"- sequence"
|
||||
);
|
||||
editor.assertProblems(
|
||||
"-|expected <block end>"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void linterRunsOnDocumentOpenAndChange() throws Exception {
|
||||
LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
|
||||
harness.intialize(null);
|
||||
|
||||
Editor editor = harness.newEditor(
|
||||
"somemap: val\n"+
|
||||
"- sequence"
|
||||
);
|
||||
|
||||
editor.assertProblems(
|
||||
"-|expected <block end>"
|
||||
);
|
||||
|
||||
editor.setText(
|
||||
"- sequence\n" +
|
||||
"zomemap: val"
|
||||
);
|
||||
|
||||
editor.assertProblems(
|
||||
"z|expected <block end>"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.github.kdvolder.lsapi.example;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.github.kdvolder.lsapi.testharness.LanguageServerHarness;
|
||||
import com.github.kdvolder.lsapi.testharness.TextDocumentInfo;
|
||||
|
||||
import io.typefox.lsapi.CompletionItem;
|
||||
import io.typefox.lsapi.CompletionList;
|
||||
import io.typefox.lsapi.InitializeResult;
|
||||
import io.typefox.lsapi.ServerCapabilities;
|
||||
|
||||
public class YamlLanguageServerTest {
|
||||
|
||||
public static File getTestResource(String name) throws URISyntaxException {
|
||||
return Paths.get(YamlLanguageServerTest.class.getResource(name).toURI()).toFile();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndInitializeServerWithWorkspace() throws Exception {
|
||||
LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
|
||||
File workspaceRoot = getTestResource("/workspace/");
|
||||
assertExpectedInitResult(harness.intialize(workspaceRoot));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createAndInitializeServerWithoutWorkspace() throws Exception {
|
||||
File workspaceRoot = null;
|
||||
LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
|
||||
assertExpectedInitResult(harness.intialize(workspaceRoot));
|
||||
}
|
||||
|
||||
|
||||
@Test public void completions() throws Exception {
|
||||
LanguageServerHarness harness = new LanguageServerHarness(YamlLanguageServer::new);
|
||||
|
||||
File workspaceRoot = getTestResource("/workspace/");
|
||||
assertExpectedInitResult(harness.intialize(workspaceRoot));
|
||||
|
||||
TextDocumentInfo doc = harness.openDocument(getTestResource("/workspace/testfile.yml"));
|
||||
|
||||
CompletionList completions = harness.getCompletions(doc, doc.positionOf("foo"));
|
||||
assertThat(completions.isIncomplete()).isFalse();
|
||||
assertThat(completions.getItems())
|
||||
.extracting(CompletionItem::getLabel)
|
||||
.containsExactly("TypeScript", "JavaScript");
|
||||
|
||||
List<CompletionItem> resolved = harness.resolveCompletions(completions);
|
||||
assertThat(resolved)
|
||||
.extracting(CompletionItem::getLabel)
|
||||
.containsExactly("TypeScript", "JavaScript");
|
||||
|
||||
assertThat(resolved)
|
||||
.extracting(CompletionItem::getDetail)
|
||||
.containsExactly("TypeScript details", "JavaScript details");
|
||||
|
||||
assertThat(resolved)
|
||||
.extracting(CompletionItem::getDocumentation)
|
||||
.containsExactly("TypeScript docs", "JavaScript docs");
|
||||
}
|
||||
|
||||
private void assertExpectedInitResult(InitializeResult initResult) {
|
||||
assertThat(initResult.getCapabilities().getCompletionProvider().getResolveProvider()).isTrue();
|
||||
assertThat(initResult.getCapabilities().getTextDocumentSync()).isEqualTo(ServerCapabilities.SYNC_FULL);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package com.github.kdvolder.lsapi.testharness;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import io.typefox.lsapi.Diagnostic;
|
||||
import io.typefox.lsapi.Position;
|
||||
import io.typefox.lsapi.PublishDiagnosticsParams;
|
||||
import io.typefox.lsapi.Range;
|
||||
|
||||
public class Editor {
|
||||
|
||||
private static final Comparator<Diagnostic> PROBLEM_COMPARATOR = new Comparator<Diagnostic>() {
|
||||
@Override
|
||||
public int compare(Diagnostic o1, Diagnostic o2) {
|
||||
int diff = compare(o1.getRange().getStart(), o2.getRange().getStart());
|
||||
if (diff!=0) return diff;
|
||||
return compare(o1.getRange().getEnd(), o2.getRange().getEnd());
|
||||
}
|
||||
|
||||
private int compare(Position p1, Position p2) {
|
||||
int d = p1.getLine() - p2.getLine();
|
||||
if (d!=0) return d;
|
||||
return p1.getCharacter() - p2.getCharacter();
|
||||
}
|
||||
};
|
||||
|
||||
private LanguageServerHarness harness;
|
||||
private TextDocumentInfo document;
|
||||
|
||||
public Editor(LanguageServerHarness harness, String contents) throws Exception {
|
||||
this.harness = harness;
|
||||
this.document = harness.openDocument(harness.createWorkingCopy(contents));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a 'expectedProblems' are found by the reconciler. Expected problems are
|
||||
* specified by string of the form "${badSnippet}|${messageSnippet}". The badSnippet
|
||||
* is the text expected to be covered by the marker's region and the message snippet must
|
||||
* be found in the error marker's message.
|
||||
* <p>
|
||||
* The expected problems are matched one-to-one in the order given (so markers in the
|
||||
* editor must appear in the expected order for the assert to pass).
|
||||
*
|
||||
* @param editor
|
||||
* @param expectedProblems
|
||||
* @throws BadLocationException
|
||||
*/
|
||||
public void assertProblems(String... expectedProblems) throws Exception {
|
||||
Editor editor = this;
|
||||
List<Diagnostic> actualProblems = new ArrayList<>(editor.reconcile());
|
||||
Collections.sort(actualProblems, PROBLEM_COMPARATOR);
|
||||
String bad = null;
|
||||
if (actualProblems.size()!=expectedProblems.length) {
|
||||
bad = "Wrong number of problems (expecting "+expectedProblems.length+" but found "+actualProblems.size()+")";
|
||||
} else {
|
||||
for (int i = 0; i < expectedProblems.length; i++) {
|
||||
if (!matchProblem(editor, actualProblems.get(i), expectedProblems[i])) {
|
||||
bad = "First mismatch at index "+i+": "+expectedProblems[i]+"\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bad!=null) {
|
||||
fail(bad+problemSumary(editor, actualProblems));
|
||||
}
|
||||
}
|
||||
|
||||
private String problemSumary(Editor editor, List<Diagnostic> actualProblems) throws Exception {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (Diagnostic p : actualProblems) {
|
||||
buf.append("\n----------------------\n");
|
||||
|
||||
String snippet = editor.getText(p.getRange());
|
||||
buf.append("("+p.getRange().getStart().getLine()+", "+p.getRange().getStart().getCharacter()+")["+snippet+"]:\n");
|
||||
buf.append(" "+p.getMessage());
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public String getText(Range range) {
|
||||
return document.getText(range);
|
||||
}
|
||||
|
||||
public void setText(String newContent) throws Exception {
|
||||
document = harness.changeDocument(document.getUri(), newContent);
|
||||
}
|
||||
|
||||
private boolean matchProblem(Editor editor, Diagnostic problem, String expect) {
|
||||
String[] parts = expect.split("\\|");
|
||||
assertEquals(2, parts.length);
|
||||
String badSnippet = parts[0];
|
||||
String messageSnippet = parts[1];
|
||||
boolean spaceSensitive = badSnippet.trim().length()<badSnippet.length();
|
||||
boolean emptyRange = problem.getRange().getStart().equals(problem.getRange().getEnd());
|
||||
String actualBadSnippet = emptyRange
|
||||
? editor.getCharAt(problem.getRange().getStart())
|
||||
: editor.getText(problem.getRange());
|
||||
if (!spaceSensitive) {
|
||||
actualBadSnippet = actualBadSnippet.trim();
|
||||
}
|
||||
return actualBadSnippet.equals(badSnippet)
|
||||
&& problem.getMessage().contains(messageSnippet);
|
||||
}
|
||||
|
||||
private String getCharAt(Position start) {
|
||||
int offset = document.toOffset(start);
|
||||
return document.getText().substring(offset, offset+1);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Diagnostic> reconcile() {
|
||||
// We assume the language server works synchronously for now and it does an immediate reconcile
|
||||
// when the document changes. In the future this is probably not going to be the case though and then this
|
||||
// method will need to somehow ensure the linter is done working before retrieving the problems from the
|
||||
// test harness.
|
||||
PublishDiagnosticsParams diagnostics = harness.getDiagnostics(document);
|
||||
if (diagnostics!=null) {
|
||||
return (List<Diagnostic>) diagnostics.getDiagnostics();
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
package com.github.kdvolder.lsapi.testharness;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
|
||||
import io.typefox.lsapi.ClientCapabilitiesImpl;
|
||||
import io.typefox.lsapi.CompletionItem;
|
||||
import io.typefox.lsapi.CompletionList;
|
||||
import io.typefox.lsapi.Diagnostic;
|
||||
import io.typefox.lsapi.DidChangeTextDocumentParamsImpl;
|
||||
import io.typefox.lsapi.DidOpenTextDocumentParamsImpl;
|
||||
import io.typefox.lsapi.InitializeParamsImpl;
|
||||
import io.typefox.lsapi.InitializeResult;
|
||||
import io.typefox.lsapi.Position;
|
||||
import io.typefox.lsapi.PositionImpl;
|
||||
import io.typefox.lsapi.PublishDiagnosticsParams;
|
||||
import io.typefox.lsapi.Range;
|
||||
import io.typefox.lsapi.ServerCapabilities;
|
||||
import io.typefox.lsapi.TextDocumentContentChangeEventImpl;
|
||||
import io.typefox.lsapi.TextDocumentItemImpl;
|
||||
import io.typefox.lsapi.TextDocumentPositionParamsImpl;
|
||||
import io.typefox.lsapi.VersionedTextDocumentIdentifierImpl;
|
||||
import io.typefox.lsapi.services.LanguageServer;
|
||||
|
||||
public class LanguageServerHarness {
|
||||
|
||||
//Warning this 'harness' is not very good yet. It just implements bare minimum to
|
||||
// be able to test the MyLanguageServer example.
|
||||
|
||||
private Random random = new Random();
|
||||
|
||||
private Callable<? extends LanguageServer> factory;
|
||||
|
||||
private LanguageServer server;
|
||||
|
||||
private InitializeResult initResult;
|
||||
|
||||
private Map<String,TextDocumentInfo> documents = new HashMap<>();
|
||||
private Map<String, PublishDiagnosticsParams> diagnostics = new HashMap<>();
|
||||
|
||||
public LanguageServerHarness(Callable<? extends LanguageServer> factory) throws Exception {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
public synchronized TextDocumentInfo getOrReadFile(File file) throws Exception {
|
||||
String uri = file.toURI().toString();
|
||||
TextDocumentInfo d = documents.get(uri);
|
||||
if (d==null) {
|
||||
documents.put(uri, d = readFile(file));
|
||||
}
|
||||
return d;
|
||||
}
|
||||
|
||||
public TextDocumentInfo readFile(File file) throws Exception {
|
||||
byte[] encoded = Files.readAllBytes(file.toPath());
|
||||
String content = new String(encoded, getEncoding());
|
||||
TextDocumentItemImpl document = new TextDocumentItemImpl();
|
||||
document.setText(content);
|
||||
document.setUri(file.toURI().toString());
|
||||
document.setVersion(getFirstVersion());
|
||||
document.setLanguageId(getLanguageId());
|
||||
return new TextDocumentInfo(document);
|
||||
}
|
||||
|
||||
private synchronized TextDocumentItemImpl setDocumentContent(String uri, String newContent) {
|
||||
TextDocumentInfo o = documents.get(uri);
|
||||
TextDocumentItemImpl n = new TextDocumentItemImpl();
|
||||
n.setLanguageId(o.getLanguageId());
|
||||
n.setText(newContent);
|
||||
n.setVersion(o.getVersion()+1);
|
||||
n.setUri(o.getUri());
|
||||
documents.put(uri, new TextDocumentInfo(n));
|
||||
return n;
|
||||
}
|
||||
|
||||
protected Charset getEncoding() {
|
||||
return Charset.forName("utf8");
|
||||
}
|
||||
|
||||
protected String getLanguageId() {
|
||||
return "plaintext";
|
||||
}
|
||||
|
||||
protected String getFileExtension() {
|
||||
return ".txt";
|
||||
}
|
||||
|
||||
private synchronized void receiveDiagnostics(PublishDiagnosticsParams diags) {
|
||||
this.diagnostics.put(diags.getUri(), diags);
|
||||
}
|
||||
|
||||
public InitializeResult intialize(File workspaceRoot) throws Exception {
|
||||
server = factory.call();
|
||||
int parentPid = random.nextInt(40000)+1000;
|
||||
InitializeParamsImpl initParams = new InitializeParamsImpl();
|
||||
initParams.setRootPath(workspaceRoot== null?null:workspaceRoot.toString());
|
||||
initParams.setProcessId(parentPid);
|
||||
ClientCapabilitiesImpl clientCap = new ClientCapabilitiesImpl();
|
||||
initParams.setCapabilities(clientCap);
|
||||
initResult = server.initialize(initParams).get();
|
||||
|
||||
server.getTextDocumentService().onPublishDiagnostics(this::receiveDiagnostics);
|
||||
return initResult;
|
||||
}
|
||||
|
||||
public TextDocumentInfo openDocument(TextDocumentInfo documentInfo) throws Exception {
|
||||
DidOpenTextDocumentParamsImpl didOpen = new DidOpenTextDocumentParamsImpl();
|
||||
didOpen.setTextDocument(documentInfo.getDocument());
|
||||
didOpen.setText(documentInfo.getText());
|
||||
didOpen.setUri(documentInfo.getUri());
|
||||
server.getTextDocumentService().didOpen(didOpen);
|
||||
return documentInfo;
|
||||
}
|
||||
|
||||
public TextDocumentInfo openDocument(File file) throws Exception {
|
||||
return openDocument(getOrReadFile(file));
|
||||
}
|
||||
|
||||
public TextDocumentInfo changeDocument(String uri, String newContent) throws Exception {
|
||||
TextDocumentItemImpl textDocument = setDocumentContent(uri, newContent);
|
||||
DidChangeTextDocumentParamsImpl didChange = new DidChangeTextDocumentParamsImpl();
|
||||
VersionedTextDocumentIdentifierImpl version = new VersionedTextDocumentIdentifierImpl();
|
||||
version.setUri(uri);
|
||||
version.setVersion(textDocument.getVersion());
|
||||
didChange.setTextDocument(version);
|
||||
switch (getDocumentSyncMode()) {
|
||||
case ServerCapabilities.SYNC_NONE:
|
||||
break; //nothing todo
|
||||
case ServerCapabilities.SYNC_INCREMENTAL:
|
||||
throw new IllegalStateException("Incremental sync not yet supported by this test harness");
|
||||
case ServerCapabilities.SYNC_FULL:
|
||||
TextDocumentContentChangeEventImpl change = new TextDocumentContentChangeEventImpl();
|
||||
change.setText(newContent);
|
||||
didChange.setContentChanges(Collections.singletonList(change));
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unkown SYNC mode: "+getDocumentSyncMode());
|
||||
}
|
||||
server.getTextDocumentService().didChange(didChange);
|
||||
return documents.get(uri);
|
||||
}
|
||||
|
||||
private int getDocumentSyncMode() {
|
||||
Integer mode = initResult.getCapabilities().getTextDocumentSync();
|
||||
return mode==null ? ServerCapabilities.SYNC_NONE : mode;
|
||||
}
|
||||
|
||||
public PublishDiagnosticsParams getDiagnostics(TextDocumentInfo doc) {
|
||||
return diagnostics.get(doc.getUri());
|
||||
}
|
||||
|
||||
public static Condition<Diagnostic> isDiagnosticWithSeverity(int severity) {
|
||||
return new Condition<>(
|
||||
(d) -> d.getSeverity()==severity,
|
||||
"Diagnostic with severity '"+severity+"'"
|
||||
);
|
||||
}
|
||||
|
||||
public static Condition<Diagnostic> isDiagnosticCovering(TextDocumentInfo doc, String string) {
|
||||
return new Condition<>(
|
||||
(d) -> isDiagnosticCovering(d, doc, string),
|
||||
"Diagnostic covering '"+string+"'"
|
||||
);
|
||||
}
|
||||
|
||||
public static final Condition<Diagnostic> isWarning = isDiagnosticWithSeverity(Diagnostic.SEVERITY_WARNING);
|
||||
|
||||
public static boolean isDiagnosticCovering(Diagnostic diag, TextDocumentInfo doc, String string) {
|
||||
Range rng = diag.getRange();
|
||||
String actualText = doc.getText(rng);
|
||||
return string.equals(actualText);
|
||||
}
|
||||
|
||||
public static Condition<Diagnostic> isDiagnosticOnLine(int line) {
|
||||
return new Condition<>(
|
||||
(d) -> d.getRange().getStart().getLine()==line,
|
||||
"Diagnostic on line "+line
|
||||
);
|
||||
}
|
||||
|
||||
public CompletionList getCompletions(TextDocumentInfo doc, Position cursor) throws Exception {
|
||||
TextDocumentPositionParamsImpl params = new TextDocumentPositionParamsImpl();
|
||||
params.setPosition(toImpl(cursor));
|
||||
params.setTextDocument(doc.getId());
|
||||
return server.getTextDocumentService().completion(params).get();
|
||||
}
|
||||
|
||||
private PositionImpl toImpl(Position pos) {
|
||||
if (pos instanceof PositionImpl) {
|
||||
return (PositionImpl) pos;
|
||||
} else {
|
||||
PositionImpl imp = new PositionImpl();
|
||||
imp.setCharacter(pos.getCharacter());
|
||||
imp.setLine(pos.getLine());
|
||||
return imp;
|
||||
}
|
||||
}
|
||||
|
||||
private CompletionItem resolveCompletionItem(CompletionItem unresolved) {
|
||||
try {
|
||||
return server.getTextDocumentService().resolveCompletionItem(unresolved).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public List<CompletionItem> resolveCompletions(CompletionList completions) {
|
||||
return completions.getItems().stream()
|
||||
.map(this::resolveCompletionItem)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Editor newEditor(String contents) throws Exception {
|
||||
return new Editor(this, contents);
|
||||
}
|
||||
|
||||
public synchronized TextDocumentInfo createWorkingCopy(String contents) throws Exception {
|
||||
TextDocumentItemImpl doc = new TextDocumentItemImpl();
|
||||
doc.setLanguageId(getLanguageId());
|
||||
doc.setText(contents);
|
||||
doc.setUri(createTempUri());
|
||||
doc.setVersion(getFirstVersion());
|
||||
TextDocumentInfo docinfo = new TextDocumentInfo(doc);
|
||||
documents.put(docinfo.getUri(), docinfo);
|
||||
return docinfo;
|
||||
}
|
||||
|
||||
protected int getFirstVersion() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
protected String createTempUri() throws Exception {
|
||||
return File.createTempFile("workingcopy", getFileExtension()).toURI().toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.github.kdvolder.lsapi.testharness;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import io.typefox.lsapi.Position;
|
||||
import io.typefox.lsapi.PositionImpl;
|
||||
import io.typefox.lsapi.Range;
|
||||
import io.typefox.lsapi.TextDocumentIdentifierImpl;
|
||||
import io.typefox.lsapi.TextDocumentItemImpl;
|
||||
|
||||
public class TextDocumentInfo {
|
||||
|
||||
Pattern NEWLINE = Pattern.compile("\\r|\\n|\\r\\n|\\n\\r");
|
||||
|
||||
private final TextDocumentItemImpl document;
|
||||
|
||||
private int[] _lineStarts;
|
||||
|
||||
public TextDocumentInfo(TextDocumentItemImpl document) {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
public String getLanguageId() {
|
||||
return getDocument().getLanguageId();
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return getDocument().getVersion();
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return getDocument().getText();
|
||||
}
|
||||
|
||||
public String getUri() {
|
||||
return getDocument().getUri();
|
||||
}
|
||||
|
||||
public TextDocumentItemImpl getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
public String getText(Range rng) {
|
||||
int start = toOffset(rng.getStart());
|
||||
int end = toOffset(rng.getEnd());
|
||||
return getText().substring(start, end);
|
||||
}
|
||||
|
||||
public int toOffset(Position p) {
|
||||
int startOfLine = startOfLine(p.getLine());
|
||||
return startOfLine+p.getCharacter();
|
||||
}
|
||||
|
||||
private int startOfLine(int line) {
|
||||
return lineStarts()[line];
|
||||
}
|
||||
|
||||
private int[] lineStarts() {
|
||||
if (_lineStarts==null) {
|
||||
_lineStarts = parseLines();
|
||||
}
|
||||
return _lineStarts;
|
||||
}
|
||||
|
||||
private int[] parseLines() {
|
||||
List<Integer> lineStarts = new ArrayList<>();
|
||||
lineStarts.add(0);
|
||||
Matcher matcher = NEWLINE.matcher(getText());
|
||||
int pos = 0;
|
||||
while (matcher.find(pos)) {
|
||||
lineStarts.add(pos = matcher.end());
|
||||
}
|
||||
int[] array = new int[lineStarts.size()];
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
array[i] = lineStarts.get(i);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find and return the (first) position of a given text snippet in the
|
||||
* document.
|
||||
*
|
||||
* @return The position, or null if the snippet can't be found.
|
||||
*/
|
||||
public Position positionOf(String snippet) {
|
||||
int offset = getText().indexOf(snippet);
|
||||
if (offset>=0) {
|
||||
return toPosition(offset);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Position toPosition(int offset) {
|
||||
int line = lineNumber(offset);
|
||||
int startOfLine = startOfLine(line);
|
||||
int column = offset - startOfLine;
|
||||
PositionImpl pos = new PositionImpl();
|
||||
pos.setCharacter(column);
|
||||
pos.setLine(line);
|
||||
return pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the line-number a given offset (i.e. what line is the offset inside of?)
|
||||
*/
|
||||
private int lineNumber(int offset) {
|
||||
int[] lineStarts = lineStarts();
|
||||
// TODO Could use binary search which is faster
|
||||
int lineNumber = 0;
|
||||
for (int i = 0; i < lineStarts.length; i++) {
|
||||
if (lineStarts[i]<=offset) {
|
||||
lineNumber = i;
|
||||
} else {
|
||||
return lineNumber;
|
||||
}
|
||||
}
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
public TextDocumentIdentifierImpl getId() {
|
||||
TextDocumentIdentifierImpl id = new TextDocumentIdentifierImpl();
|
||||
id.setUri(getUri());
|
||||
return id;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#There's a syntax error in this file
|
||||
abc: foo
|
||||
- foo
|
||||
1
vscode-extensions/vscode-application-yaml/test/.gitignore
vendored
Normal file
1
vscode-extensions/vscode-application-yaml/test/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.js
|
||||
32
vscode-extensions/vscode-application-yaml/test/LintSpec.ts
Normal file
32
vscode-extensions/vscode-application-yaml/test/LintSpec.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import * as assert from 'assert';
|
||||
|
||||
// TODO
|
||||
// describe('lint', () => {
|
||||
// it('should report a syntax error', done => {
|
||||
// EMPTY_JAVAC.then(javac => {
|
||||
// let path = 'test/examples/SyntaxError.java';
|
||||
|
||||
// return javac.lint({ path }).then(result => {
|
||||
// let ms = messages(result.messages);
|
||||
|
||||
// assert(ms.length > 0, `${ms} is empty`);
|
||||
|
||||
// done();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
|
||||
// it('should report a type error', done => {
|
||||
// EMPTY_JAVAC.then(javac => {
|
||||
// let path = 'test/examples/TypeError.java';
|
||||
|
||||
// return javac.lint({ path }).then(result => {
|
||||
// let ms = messages(result.messages);
|
||||
|
||||
// assert(ms.length > 0, `${ms} is empty`);
|
||||
|
||||
// done();
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
// });
|
||||
@@ -0,0 +1,2 @@
|
||||
foo: bar
|
||||
- asss
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Note: This example test is leveraging the Mocha test framework.
|
||||
// Please refer to their documentation on https://mochajs.org/ for help.
|
||||
//
|
||||
|
||||
// The module 'assert' provides assertion methods from node
|
||||
import * as assert from 'assert';
|
||||
|
||||
// You can import and use all API from the 'vscode' module
|
||||
// as well as import your extension to test it
|
||||
import * as vscode from 'vscode';
|
||||
import * as myExtension from '../lib/Main';
|
||||
|
||||
// Useful link:
|
||||
// http://ricostacruz.com/cheatsheets/mocha-tdd.html
|
||||
|
||||
// Defines a Mocha test suite to group tests of similar kind together
|
||||
suite("Extension Tests", () => {
|
||||
|
||||
// Defines a Mocha unit test
|
||||
test("My Extension gets activated", () => {
|
||||
assert.equal(-1, [1, 2, 3].indexOf(5));
|
||||
assert.equal(-1, [1, 2, 3].indexOf(0));
|
||||
});
|
||||
});
|
||||
22
vscode-extensions/vscode-application-yaml/test/index.ts
Normal file
22
vscode-extensions/vscode-application-yaml/test/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||
//
|
||||
// This file is providing the test runner to use when running extension tests.
|
||||
// By default the test runner in use is Mocha based.
|
||||
//
|
||||
// You can provide your own test runner if you want to override it by exporting
|
||||
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
|
||||
// host can call to run the tests. The test runner is expected to use console.log
|
||||
// to report the results back to the caller. When the tests are finished, return
|
||||
// a possible error to the callback or null if none.
|
||||
|
||||
var testRunner = require('vscode/lib/testrunner');
|
||||
|
||||
// You can directly control Mocha options by uncommenting the following lines
|
||||
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
|
||||
testRunner.configure({
|
||||
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
|
||||
useColors: true // colored output from test results
|
||||
});
|
||||
|
||||
module.exports = testRunner;
|
||||
12
vscode-extensions/vscode-application-yaml/tsconfig.json
Normal file
12
vscode-extensions/vscode-application-yaml/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"outDir": "out",
|
||||
"sourceMap": true,
|
||||
"rootDir": "."
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
12
vscode-extensions/vscode-application-yaml/tsd.json
Normal file
12
vscode-extensions/vscode-application-yaml/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-yaml/typings/portfinder.d.ts
vendored
Normal file
7
vscode-extensions/vscode-application-yaml/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-yaml/typings/tsd.d.ts
vendored
Normal file
2
vscode-extensions/vscode-application-yaml/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" />
|
||||
@@ -0,0 +1,33 @@
|
||||
# Welcome to your first 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/vscode/vscode.d.ts`
|
||||
|
||||
## Run tests
|
||||
* open the debug viewlet (`Ctrl+Shift+D` or `Cmd+Shift+D` on Mac) and from the launch configuration dropdown pick `Launch 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 `test/extension.test.ts` or create new test files inside the `test` folder
|
||||
* by convention, the 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
|
||||
Reference in New Issue
Block a user