Moved commons and concourse editor to 'headless-services'
This commit is contained in:
26
headless-services/commons/commons-language-server/.classpath
Normal file
26
headless-services/commons/commons-language-server/.classpath
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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 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>
|
||||
23
headless-services/commons/commons-language-server/.project
Normal file
23
headless-services/commons/commons-language-server/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>commons-language-server</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,3 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/main/java=UTF-8
|
||||
encoding/<project>=UTF-8
|
||||
@@ -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,59 @@
|
||||
eclipse.preferences.version=1
|
||||
editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
|
||||
sp_cleanup.add_default_serial_version_id=true
|
||||
sp_cleanup.add_generated_serial_version_id=false
|
||||
sp_cleanup.add_missing_annotations=true
|
||||
sp_cleanup.add_missing_deprecated_annotations=true
|
||||
sp_cleanup.add_missing_methods=false
|
||||
sp_cleanup.add_missing_nls_tags=false
|
||||
sp_cleanup.add_missing_override_annotations=true
|
||||
sp_cleanup.add_missing_override_annotations_interface_methods=true
|
||||
sp_cleanup.add_serial_version_id=false
|
||||
sp_cleanup.always_use_blocks=true
|
||||
sp_cleanup.always_use_parentheses_in_expressions=false
|
||||
sp_cleanup.always_use_this_for_non_static_field_access=false
|
||||
sp_cleanup.always_use_this_for_non_static_method_access=false
|
||||
sp_cleanup.convert_functional_interfaces=false
|
||||
sp_cleanup.convert_to_enhanced_for_loop=false
|
||||
sp_cleanup.correct_indentation=false
|
||||
sp_cleanup.format_source_code=false
|
||||
sp_cleanup.format_source_code_changes_only=false
|
||||
sp_cleanup.insert_inferred_type_arguments=false
|
||||
sp_cleanup.make_local_variable_final=true
|
||||
sp_cleanup.make_parameters_final=false
|
||||
sp_cleanup.make_private_fields_final=true
|
||||
sp_cleanup.make_type_abstract_if_missing_method=false
|
||||
sp_cleanup.make_variable_declarations_final=false
|
||||
sp_cleanup.never_use_blocks=false
|
||||
sp_cleanup.never_use_parentheses_in_expressions=true
|
||||
sp_cleanup.on_save_use_additional_actions=true
|
||||
sp_cleanup.organize_imports=true
|
||||
sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
|
||||
sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
|
||||
sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
|
||||
sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
|
||||
sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
|
||||
sp_cleanup.remove_private_constructors=true
|
||||
sp_cleanup.remove_redundant_type_arguments=false
|
||||
sp_cleanup.remove_trailing_whitespaces=true
|
||||
sp_cleanup.remove_trailing_whitespaces_all=true
|
||||
sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
|
||||
sp_cleanup.remove_unnecessary_casts=true
|
||||
sp_cleanup.remove_unnecessary_nls_tags=false
|
||||
sp_cleanup.remove_unused_imports=false
|
||||
sp_cleanup.remove_unused_local_variables=false
|
||||
sp_cleanup.remove_unused_private_fields=true
|
||||
sp_cleanup.remove_unused_private_members=false
|
||||
sp_cleanup.remove_unused_private_methods=true
|
||||
sp_cleanup.remove_unused_private_types=true
|
||||
sp_cleanup.sort_members=false
|
||||
sp_cleanup.sort_members_all=false
|
||||
sp_cleanup.use_anonymous_class_creation=false
|
||||
sp_cleanup.use_blocks=false
|
||||
sp_cleanup.use_blocks_only_for_return_and_throw=false
|
||||
sp_cleanup.use_lambda=true
|
||||
sp_cleanup.use_parentheses_in_expressions=false
|
||||
sp_cleanup.use_this_for_non_static_field_access=false
|
||||
sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
|
||||
sp_cleanup.use_this_for_non_static_method_access=false
|
||||
sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
|
||||
@@ -0,0 +1,4 @@
|
||||
activeProfiles=
|
||||
eclipse.preferences.version=1
|
||||
resolveWorkspaceProjects=true
|
||||
version=1
|
||||
55
headless-services/commons/commons-language-server/pom.xml
Normal file
55
headless-services/commons/commons-language-server/pom.xml
Normal file
@@ -0,0 +1,55 @@
|
||||
<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/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>commons-language-server</artifactId>
|
||||
<name>commons-language-server</name>
|
||||
<description>Shared utilities for building vscode language servers in Java</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>commons-parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.ide.vscode</groupId>
|
||||
<artifactId>commons-util</artifactId>
|
||||
<version>${project.version}</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>
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
<version>${reactor-version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,25 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.commons.languageserver;
|
||||
|
||||
/**
|
||||
* Central place to define constants for the known language-ids that we care
|
||||
* about.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class LanguageIds {
|
||||
|
||||
public static final String PLAINTEXT = "plaintext";
|
||||
public static final String CONCOURSE_TASK = "concourse-task-yaml";
|
||||
public static final String CONCOURSE_PIPELINE = "concourse-pipeline-yaml";
|
||||
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.channels.AsynchronousServerSocketChannel;
|
||||
import java.nio.channels.AsynchronousSocketChannel;
|
||||
import java.nio.channels.Channels;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Provider;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.Launcher;
|
||||
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
|
||||
import org.eclipse.lsp4j.services.LanguageClient;
|
||||
import org.eclipse.lsp4j.services.LanguageClientAware;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LoggingFormat;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
|
||||
|
||||
/**
|
||||
* Abstract class meant to minimize the amount of code needed to
|
||||
* write to create suitable 'main' method to launch a language server.
|
||||
* <p>
|
||||
* The easiest way to use this is to create your own static main method.
|
||||
* Then call this class's start method with a Provider<SimpleLanguageServer> as
|
||||
* a argument.
|
||||
* <p>
|
||||
* Alternatively, you can also subclass it and implement the abstract
|
||||
* `createServer` method.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public abstract class LaunguageServerApp {
|
||||
|
||||
private static final String STANDALONE_STARTUP = "standalone-startup";
|
||||
private static final int SERVER_STANDALONE_PORT = 5007;
|
||||
|
||||
public static void start(Provider<SimpleLanguageServer> languageServerFactory) throws IOException, InterruptedException {
|
||||
LaunguageServerApp app = new LaunguageServerApp() {
|
||||
@Override
|
||||
protected SimpleLanguageServer createServer() {
|
||||
return languageServerFactory.get();
|
||||
}
|
||||
};
|
||||
|
||||
if (System.getProperty(STANDALONE_STARTUP, "false").equals("true")) {
|
||||
app.startAsServer();
|
||||
}
|
||||
else {
|
||||
app.start();
|
||||
}
|
||||
}
|
||||
|
||||
public static void startAsServer(Provider<SimpleLanguageServer> languageServerFactory) throws IOException, InterruptedException {
|
||||
LaunguageServerApp app = new LaunguageServerApp() {
|
||||
@Override
|
||||
protected SimpleLanguageServer createServer() {
|
||||
return languageServerFactory.get();
|
||||
}
|
||||
};
|
||||
app.startAsServer();
|
||||
}
|
||||
|
||||
protected 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void start() 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* starts up the language server and let it listen for connections from the outside
|
||||
* instead of connecting itself to an existing port or channel.
|
||||
*
|
||||
* This is meant for development only, to reduce turnaround times while working
|
||||
* on the language server from within an IDE, so that you can start the language
|
||||
* server right away in debug mode and let the vscode extension connect to that
|
||||
* instance instead of vice versa.
|
||||
*
|
||||
* Source of inspiration:
|
||||
* https://github.com/itemis/xtext-languageserver-example/blob/master/org.xtext.example.mydsl.ide/src/org/xtext/example/mydsl/ide/RunServer.java
|
||||
*/
|
||||
public void startAsServer() throws IOException, InterruptedException {
|
||||
LOG.info("Starting LS as standlone server");
|
||||
|
||||
Function<MessageConsumer, MessageConsumer> wrapper = consumer -> {
|
||||
MessageConsumer result = consumer;
|
||||
return result;
|
||||
};
|
||||
|
||||
SimpleLanguageServer languageServer = createServer();
|
||||
Launcher<STS4LanguageClient> launcher = createSocketLauncher(languageServer, STS4LanguageClient.class,
|
||||
new InetSocketAddress("localhost", SERVER_STANDALONE_PORT), Executors.newCachedThreadPool(), wrapper);
|
||||
|
||||
languageServer.connect(launcher.getRemoteProxy());
|
||||
Future<?> future = launcher.startListening();
|
||||
while (!future.isDone()) {
|
||||
Thread.sleep(10_000l);
|
||||
}
|
||||
}
|
||||
|
||||
private <T> Launcher<T> createSocketLauncher(Object localService, Class<T> remoteInterface, SocketAddress socketAddress, ExecutorService executorService, Function<MessageConsumer, MessageConsumer> wrapper) throws IOException {
|
||||
AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open().bind(socketAddress);
|
||||
AsynchronousSocketChannel socketChannel;
|
||||
try {
|
||||
socketChannel = serverSocket.accept().get();
|
||||
return Launcher.createIoLauncher(localService, remoteInterface, Channels.newInputStream(socketChannel), Channels.newOutputStream(socketChannel), executorService, wrapper);
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
LOG.info("Connected to parent using socket on port " + port);
|
||||
return new Connection(in, out, socket);
|
||||
}
|
||||
else {
|
||||
InputStream in = System.in;
|
||||
PrintStream out = System.out;
|
||||
|
||||
LOG.info("Connected to parent using stdio");
|
||||
|
||||
return new Connection(in, out, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static final Logger LOG = Logger.getLogger("main");
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @throws ExecutionException
|
||||
* @throws InterruptedException
|
||||
*/
|
||||
protected void run(Connection connection) throws InterruptedException, ExecutionException {
|
||||
LanguageServer server = createServer();
|
||||
ExecutorService executor = Executors.newCachedThreadPool();
|
||||
Function<MessageConsumer, MessageConsumer> wrapper = (MessageConsumer consumer) -> {
|
||||
return (msg) -> {
|
||||
try {
|
||||
consumer.consume(msg);
|
||||
} catch (UnsupportedOperationException e) {
|
||||
//log a warning and ignore. We are getting some messages from vsCode the server doesn't know about
|
||||
LOG.log(Level.WARNING, "Unsupported message was ignored!", e);
|
||||
}
|
||||
};
|
||||
};
|
||||
Launcher<STS4LanguageClient> launcher = Launcher.createLauncher(server,
|
||||
STS4LanguageClient.class,
|
||||
connection.in,
|
||||
connection.out,
|
||||
executor,
|
||||
wrapper
|
||||
);
|
||||
|
||||
if (server instanceof LanguageClientAware) {
|
||||
LanguageClient client = launcher.getRemoteProxy();
|
||||
((LanguageClientAware) server).connect(client);
|
||||
}
|
||||
|
||||
launcher.startListening().get();
|
||||
}
|
||||
|
||||
protected abstract SimpleLanguageServer createServer();
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver;
|
||||
|
||||
public class ProgressParams {
|
||||
|
||||
/**
|
||||
* An id representing the enitity for which progress messages are to be shown.
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* Updates the current statusMsg associated with a given the id. If null, then the message
|
||||
* is cleared.
|
||||
*/
|
||||
private String statusMsg;
|
||||
|
||||
|
||||
public ProgressParams() {
|
||||
}
|
||||
|
||||
public ProgressParams(String id, String statusMsg) {
|
||||
super();
|
||||
this.id = id;
|
||||
this.statusMsg = statusMsg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProgressParams [id=" + id + ", statusMsg=" + statusMsg + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((id == null) ? 0 : id.hashCode());
|
||||
result = prime * result + ((statusMsg == null) ? 0 : statusMsg.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
ProgressParams other = (ProgressParams) obj;
|
||||
if (id == null) {
|
||||
if (other.id != null)
|
||||
return false;
|
||||
} else if (!id.equals(other.id))
|
||||
return false;
|
||||
if (statusMsg == null) {
|
||||
if (other.statusMsg != null)
|
||||
return false;
|
||||
} else if (!statusMsg.equals(other.statusMsg))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getStatusMsg() {
|
||||
return statusMsg;
|
||||
}
|
||||
|
||||
public void setStatusMsg(String statusMsg) {
|
||||
this.statusMsg = statusMsg;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ProgressService {
|
||||
|
||||
/**
|
||||
* Sends a progress event to the LSP client. A taskId is an arbirary id
|
||||
* that can be chosen by the caller. The purpose of the id is to be a 'unique'
|
||||
* id for some kind of 'long running job'. Only a single 'statusMsg' is associated
|
||||
* with a given taskId at any one time. Each event updates the message shown
|
||||
* to the user replacing the old one.
|
||||
* <p>
|
||||
* Updating the message to 'null' erases the previous message without showing
|
||||
* a new one.
|
||||
* <p>
|
||||
* More than one message may be shown simultaneously to the user, if they
|
||||
* have different taskId.
|
||||
*
|
||||
* @param taskId
|
||||
* @param statusMsg
|
||||
*/
|
||||
void progressEvent(String taskId, String statusMsg);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
|
||||
import org.eclipse.lsp4j.services.LanguageClient;
|
||||
|
||||
/**
|
||||
* Some 'custom' extensions to standard LSP {@link LanguageClient}.
|
||||
*
|
||||
* @author
|
||||
*/
|
||||
public interface STS4LanguageClient extends LanguageClient {
|
||||
|
||||
@JsonNotification("sts/progress")
|
||||
void progress(ProgressParams progressEvent);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 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.commons.languageserver.completion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.Region;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* Helper to make it easier to create composite modifications to IDocument.
|
||||
* <p>
|
||||
* It allows building up a sequence of edits which are all expressed in terms of
|
||||
* offsets in the unmodified document. (So, when computing edits based on a
|
||||
* some kind of AST its is not necessary to recompute the AST or update its
|
||||
* position information after each small modification).
|
||||
* <p>
|
||||
* Similar functionality to Eclipse's {@link TextEdit} but unlike {@link TextEdit}
|
||||
* it is not as finicky with respect to overlapping edits. We consider the
|
||||
* order in which edits are created meaningful and give a logical semantics
|
||||
* to edits that 'overlap'.
|
||||
* <p>
|
||||
* Also, each edit affects the cursor position placing it at the end of
|
||||
* that edit. This will mostly do what you would want it to, provided that
|
||||
* you save the edit where you want the cursor to end-up at for last.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class DocumentEdits implements ProposalApplier {
|
||||
|
||||
// Note: for small number of edits this implementation is okay.
|
||||
// for large number of edits it is potentially slow because of the
|
||||
// way it transforms edit coordinates (a growing chain of
|
||||
// OffsetTransformer is created so every extra edit added
|
||||
// will take O(n) to preform the transform on its coordinates.
|
||||
// So applying 'n' edits is O(n^2).
|
||||
//
|
||||
// A smarter way of doing this is possible. Here's a possible idea:
|
||||
//
|
||||
// For simplicity sake assume that all edits are 'independent' (i.e.
|
||||
// changing their executing order doesn't matter.
|
||||
//
|
||||
// It is advantageous to sort the edits by position and execute them
|
||||
// high to low because... we can then guarantee that the transform
|
||||
// function that will apply to each edit does nothing on the coordinates
|
||||
// that it cares about (since all prior edits only affect higher offets)
|
||||
//
|
||||
// Unfortunately the simplifying assumption does not allways hold.
|
||||
// There are two problems:
|
||||
//
|
||||
// 1) updating the selection is order dependent.
|
||||
// => this can be solved by observing that only the last
|
||||
// edit operation need update the selection since it cancels
|
||||
// all prior selections.
|
||||
// => Mark the last operation with a 'flag' 'setSelection=true'
|
||||
// and do not update the selection in any other operations.
|
||||
//
|
||||
// 2) some edits may not be independent
|
||||
// => When the edits are sorted in descending order based on their 'end'
|
||||
// coordinate them 'conflicting' edits should be adjacent and we can
|
||||
// 'group them' together into 'cluster' where we can preserve their
|
||||
// relative execution order.
|
||||
// => While executing a 'cluster' we shall keep track of the offset
|
||||
// transform function just like the current implementation does.
|
||||
// => When the cluster of 'conflicting' operations has been dealt with
|
||||
// the offset transform function no longer matters for the
|
||||
// remaining edits who's offsets are all strictly 'smaller'.
|
||||
// Thus the trasnform function can be discarded.
|
||||
//
|
||||
// Assuming most edits are independent and only a few of them conflict, then
|
||||
// this algorithm can provide equivalent functinonality to the current one
|
||||
// but for an 'average' performance which is O(n*log(n))
|
||||
// Of course worst-case is still O(n^2) but we wouldn't expect to hit that case
|
||||
// assuming we mostly have lots of small edits to disjoint sections of the document.
|
||||
//
|
||||
// So... edit operations could be sorted based on their position
|
||||
// and executed in decreasing order of their 'start'.
|
||||
//
|
||||
// The tricky part would be to preserve the order-dependent semantics.
|
||||
|
||||
public static class TextReplace {
|
||||
public final int start;
|
||||
public final int end;
|
||||
public final String newText;
|
||||
|
||||
public TextReplace(int start, int end, String newText) {
|
||||
super();
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.newText = newText;
|
||||
}
|
||||
|
||||
public IRegion getRegion() {
|
||||
return new Region(start, end-start);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When an insert occurs, a single position in the file transforms ambiguously into two different
|
||||
* positions after the insertion, depending on whether we 'float' marker for the position after the
|
||||
* inserted block or leave it at the beginning.
|
||||
*/
|
||||
public enum Direction {
|
||||
/**
|
||||
* Transform positions around inserts to stick to the front of the inserted block.
|
||||
*/
|
||||
BEFORE,
|
||||
/**
|
||||
*
|
||||
* Transform positions around inserts to stick to the end of the inserted block.
|
||||
*/
|
||||
AFTER
|
||||
}
|
||||
|
||||
private class Insertion extends Edit {
|
||||
private int offset;
|
||||
private String text;
|
||||
|
||||
public Insertion(int offset, String insert) {
|
||||
this.offset = offset;
|
||||
this.text = insert;
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(DocumentState doc) throws BadLocationException {
|
||||
doc.insert(offset, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ins("+text+"@"+offset+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStart() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEnd() {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class Edit {
|
||||
public abstract int getStart();
|
||||
public abstract int getEnd();
|
||||
abstract void apply(DocumentState doc) throws BadLocationException;
|
||||
@Override
|
||||
public abstract String toString();
|
||||
}
|
||||
|
||||
private class Deletion extends Edit {
|
||||
|
||||
private int start;
|
||||
private int end;
|
||||
|
||||
public Deletion(int start, int end) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
}
|
||||
|
||||
@Override
|
||||
void apply(DocumentState doc) throws BadLocationException {
|
||||
doc.delete(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "del("+start+"->"+end+")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private interface OffsetTransformer {
|
||||
int transform(int offset, Direction dir);
|
||||
}
|
||||
|
||||
private static final OffsetTransformer NULL_TRANSFORM = new OffsetTransformer() {
|
||||
@Override
|
||||
public int transform(int offset, Direction dir) {
|
||||
return offset;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* DocumentState provides methods to modify a document, its methods accept
|
||||
* offsets expressed relative to the original document contents and keeps track
|
||||
* of a OffsetTransformer that maps them to offsets in the current document.
|
||||
*/
|
||||
private static class DocumentState {
|
||||
private IDocument doc; //may be null, in which case no actual modifications are performed
|
||||
private OffsetTransformer org2new = NULL_TRANSFORM;
|
||||
private int selection = -1; //-1 Means no edits where applied that change selection so
|
||||
// the current selection is unknown
|
||||
|
||||
public DocumentState(IDocument doc) {
|
||||
this.doc = doc;
|
||||
}
|
||||
|
||||
public void insert(int start, final String text) throws BadLocationException {
|
||||
final int tStart = org2new.transform(start, Direction.AFTER);
|
||||
if (!text.isEmpty()) {
|
||||
if (doc!=null) {
|
||||
doc.replace(tStart, 0, text);
|
||||
}
|
||||
final OffsetTransformer parent = org2new;
|
||||
org2new = new OffsetTransformer() {
|
||||
@Override
|
||||
public int transform(int org, Direction dir) {
|
||||
int tOffset = parent.transform(org, dir);
|
||||
if (tOffset<tStart) {
|
||||
return tOffset;
|
||||
} else if (tOffset>tStart) {
|
||||
return tOffset + text.length();
|
||||
} else /* tOffset==tStart*/ {
|
||||
if (dir==Direction.BEFORE) {
|
||||
return tOffset;
|
||||
} else {
|
||||
return tOffset + text.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
selection = tStart+text.length();
|
||||
}
|
||||
|
||||
public void delete(final int start, final int end) throws BadLocationException {
|
||||
final int tStart = org2new.transform(start, Direction.AFTER);
|
||||
if (end>start) { // skip work for 'delete nothing' op
|
||||
final int tEnd = org2new.transform(end, Direction.AFTER);
|
||||
if (tEnd>tStart) { // skip work for 'delete nothing' op
|
||||
if (doc!=null) {
|
||||
doc.replace(tStart, tEnd-tStart, "");
|
||||
}
|
||||
|
||||
final OffsetTransformer parent = org2new;
|
||||
org2new = new OffsetTransformer() {
|
||||
@Override
|
||||
public int transform(int org, Direction dir) {
|
||||
int tOffset = parent.transform(org, dir);
|
||||
if (tOffset<=tStart) {
|
||||
return tOffset;
|
||||
} else if (tOffset>=tEnd) {
|
||||
return tOffset - tEnd + tStart;
|
||||
} else {
|
||||
return start;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
selection = tStart;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (doc==null) {
|
||||
return super.toString();
|
||||
}
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("DocumentState(\n");
|
||||
buf.append(doc.get()+"\n");
|
||||
buf.append(")\n");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<Edit> edits = new ArrayList<Edit>();
|
||||
private IDocument doc;
|
||||
|
||||
public DocumentEdits(IDocument doc) {
|
||||
this.doc = doc;
|
||||
}
|
||||
|
||||
public void delete(int start, int end) {
|
||||
Assert.isLegal(start<=end);
|
||||
edits.add(new Deletion(start, end));
|
||||
}
|
||||
|
||||
public void delete(int offset, String text) {
|
||||
delete(offset, offset+text.length());
|
||||
}
|
||||
|
||||
public void insert(int offset, String insert) {
|
||||
edits.add(new Insertion(offset, insert));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IRegion getSelection() throws Exception {
|
||||
DocumentState selectionState = new DocumentState(null);
|
||||
for (Edit edit : edits) {
|
||||
edit.apply(selectionState);
|
||||
}
|
||||
if (selectionState.selection>=0) {
|
||||
return new Region(selectionState.selection, 0);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public TextReplace asReplacement(TextDocument doc) throws BadLocationException {
|
||||
if (!edits.isEmpty()) {
|
||||
int start = edits.stream().mapToInt(Edit::getStart).min().getAsInt();
|
||||
int end = edits.stream().mapToInt(Edit::getEnd).max().getAsInt();
|
||||
|
||||
DocumentState state = new DocumentState(doc.copy());
|
||||
for (Edit edit : edits) {
|
||||
edit.apply(state);
|
||||
}
|
||||
int newStart = state.org2new.transform(start, Direction.BEFORE);
|
||||
int newEnd = state.org2new.transform(end, Direction.AFTER);
|
||||
return new TextReplace(start, end, state.doc.textBetween(newStart, newEnd));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(IDocument _doc) throws Exception {
|
||||
DocumentState doc = new DocumentState(_doc);
|
||||
for (Edit edit : edits) {
|
||||
edit.apply(doc);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("DocumentModifier(\n");
|
||||
for (Edit edit : edits) {
|
||||
buf.append(" "+edit);
|
||||
}
|
||||
buf.append(")\n");
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
public void moveCursorTo(int newCursor) {
|
||||
insert(newCursor, "");
|
||||
}
|
||||
|
||||
public void deleteLineBackwardAtOffset(int offset) throws Exception {
|
||||
int line = doc.getLineOfOffset(offset);
|
||||
deleteLineBackward(line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the line of text with given line number, including either the following or
|
||||
* preceding newline. If there is a choice between the preceding or following newline,
|
||||
* the preceding newline is deleted. This will leave the cursor at the end of
|
||||
* the preceding line.
|
||||
* <p>
|
||||
* Note: a similar operation 'deleteLineForward' could be implemented prefering to
|
||||
* delete the following newline. This would be equivalent except that it will leave the
|
||||
* cursor at the start of the following line.
|
||||
*/
|
||||
public void deleteLineBackward(int lineNumber) throws BadLocationException {
|
||||
IRegion line = doc.getLineInformation(lineNumber);
|
||||
int startOfDeletion;
|
||||
int endOfDeletion;
|
||||
if (lineNumber>0) {
|
||||
IRegion previousLine = doc.getLineInformation(lineNumber-1);
|
||||
startOfDeletion = endOf(previousLine);
|
||||
endOfDeletion = endOf(line);
|
||||
} else if (lineNumber<doc.getNumberOfLines()-1) {
|
||||
IRegion nextLine = doc.getLineInformation(lineNumber+1);
|
||||
startOfDeletion = line.getOffset();
|
||||
endOfDeletion = nextLine.getOffset();
|
||||
} else {
|
||||
startOfDeletion = line.getOffset();
|
||||
endOfDeletion = endOf(line);
|
||||
}
|
||||
delete(startOfDeletion, endOfDeletion);
|
||||
}
|
||||
|
||||
private int endOf(IRegion line) {
|
||||
return line.getOffset()+line.getLength();
|
||||
}
|
||||
|
||||
public void replace(int start, int end, String newText) {
|
||||
delete(start, end);
|
||||
insert(start, newText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds extra indentation at the position of the first edit in this {@link DocumentEdits}
|
||||
*/
|
||||
public void indentFirstEdit(String indentString) {
|
||||
if (edits.size()>0) {
|
||||
Edit firstEdit = edits.get(0);
|
||||
int offset = firstEdit.getStart();
|
||||
edits.add(0, new Insertion(offset, indentString));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 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.commons.languageserver.completion;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface ICompletionEngine {
|
||||
|
||||
Collection<ICompletionProposal> getCompletions(IDocument document, int offset) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.completion;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
|
||||
/**
|
||||
* Replaces STS/Eclipse's ICompletionProposal
|
||||
*/
|
||||
public interface ICompletionProposal {
|
||||
|
||||
/**
|
||||
* Transforms a proposal to make it standout less somehow.
|
||||
*/
|
||||
ICompletionProposal deemphasize();
|
||||
|
||||
String getLabel();
|
||||
CompletionItemKind getKind();
|
||||
DocumentEdits getTextEdit();
|
||||
|
||||
String getDetail();
|
||||
Renderable getDocumentation();
|
||||
default String getFilterText() { return getLabel(); }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.completion;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/**
|
||||
* Temprary placeholder which sort of replaces the LazyProposalApplier from old STS.
|
||||
* It really does nothing right now. Somehow this should be tied into LS protocol so
|
||||
* that the edits are not computed until a completion is being resolved.
|
||||
* <p>
|
||||
* Right now this is not lazy at all and the completion edits are just computed immediatly.
|
||||
*/
|
||||
public class LazyProposalApplier {
|
||||
|
||||
public static DocumentEdits from(Callable<DocumentEdits> createEdits) throws Exception {
|
||||
return createEdits.call();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 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.commons.languageserver.completion;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
|
||||
/**
|
||||
* Interface that represents the methods that one needs to implement in order
|
||||
* to define how content assist proposal is applied to a IDocument
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface ProposalApplier {
|
||||
|
||||
/**
|
||||
* {@link ProposalApplier} that does nothing whatsoever.
|
||||
*/
|
||||
static ProposalApplier NULL = new ProposalApplier() {
|
||||
@Override public IRegion getSelection() { return null; }
|
||||
@Override public void apply(IDocument doc) {}
|
||||
@Override public String toString() { return "NULL";};
|
||||
};
|
||||
|
||||
IRegion getSelection() throws Exception;
|
||||
void apply(IDocument doc) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.completion;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
public abstract class ScoreableProposal implements ICompletionProposal {
|
||||
private static final double DEEMP_VALUE = 100000; // should be large enough to move deemphasized stuff to bottom of list.
|
||||
|
||||
private double deemphasizedBy = 0.0;
|
||||
|
||||
/**
|
||||
* A sorter suitable for sorting ScoreableProposals based on their score.
|
||||
*/
|
||||
public static final Comparator<ICompletionProposal> COMPARATOR = new Comparator<ICompletionProposal>() {
|
||||
@Override
|
||||
public int compare(ICompletionProposal p1, ICompletionProposal p2) {
|
||||
if (p1 instanceof ScoreableProposal && p2 instanceof ScoreableProposal) {
|
||||
double s1 = ((ScoreableProposal)p1).getScore();
|
||||
double s2 = ((ScoreableProposal)p2).getScore();
|
||||
if (s1==s2) {
|
||||
String name1 = ((ScoreableProposal)p1).getLabel();
|
||||
String name2 = ((ScoreableProposal)p2).getLabel();
|
||||
return name1.compareTo(name2);
|
||||
} else {
|
||||
return Double.compare(s2, s1);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
public abstract double getBaseScore();
|
||||
public final double getScore() {
|
||||
return getBaseScore() - deemphasizedBy;
|
||||
}
|
||||
@Override
|
||||
public ScoreableProposal deemphasize() {
|
||||
deemphasizedBy+= DEEMP_VALUE;
|
||||
return this;
|
||||
}
|
||||
public boolean isDeemphasized() {
|
||||
return deemphasizedBy > 0;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public boolean isAutoInsertable() {
|
||||
// return !isDeemphasized();
|
||||
// }
|
||||
|
||||
// public StyledString getStyledDisplayString() {
|
||||
// StyledString result = new StyledString();
|
||||
// highlightPattern(getHighlightPattern(), getBaseDisplayString(), result);
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// private void highlightPattern(String pattern, String data, StyledString result) {
|
||||
// Styler highlightStyle = CompletionFactory.HIGHLIGHT;
|
||||
// Styler plainStyle = isDeemphasized()?CompletionFactory.DEEMPHASIZE:CompletionFactory.NULL_STYLER;
|
||||
// if (isDeprecated()) {
|
||||
// highlightStyle = CompletionFactory.compose(highlightStyle, CompletionFactory.DEPRECATE);
|
||||
// plainStyle = CompletionFactory.compose(plainStyle, CompletionFactory.DEPRECATE);
|
||||
// }
|
||||
// if (StringUtils.hasText(pattern)) {
|
||||
// int dataPos = 0; int dataLen = data.length();
|
||||
// int patternPos = 0; int patternLen = pattern.length();
|
||||
//
|
||||
// while (dataPos<dataLen && patternPos<patternLen) {
|
||||
// int pChar = pattern.charAt(patternPos++);
|
||||
// int highlightPos = data.indexOf(pChar, dataPos);
|
||||
// if (dataPos<highlightPos) {
|
||||
// result.append(data.substring(dataPos, highlightPos), plainStyle);
|
||||
// }
|
||||
// result.append(data.charAt(highlightPos), highlightStyle);
|
||||
// dataPos = highlightPos+1;
|
||||
// }
|
||||
// if (dataPos<dataLen) {
|
||||
// result.append(data.substring(dataPos), plainStyle);
|
||||
// }
|
||||
// } else { //no pattern to highlight
|
||||
// result.append(data, plainStyle);
|
||||
// }
|
||||
// }
|
||||
|
||||
// protected abstract boolean isDeprecated();
|
||||
// protected abstract String getHighlightPattern();
|
||||
// protected abstract String getBaseDisplayString();
|
||||
|
||||
// @Override
|
||||
// public String getAdditionalProposalInfo() {
|
||||
// HoverInfo hoverInfo = getAdditionalProposalInfo(new NullProgressMonitor());
|
||||
// if (hoverInfo!=null) {
|
||||
// return hoverInfo.getHtml();
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
// @Override
|
||||
// public abstract HoverInfo getAdditionalProposalInfo(IProgressMonitor monitor);
|
||||
|
||||
// @Override
|
||||
// public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public int getPrefixCompletionStart(IDocument document, int completionOffset) {
|
||||
// return completionOffset;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getLabel();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.completion;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
|
||||
/**
|
||||
* Interface that needs to be implemented by a 'completion engine' which can be easily
|
||||
* wired-up to provide completions for a Vscode language server.
|
||||
*/
|
||||
public interface VscodeCompletionEngine {
|
||||
CompletableFuture<CompletionList> getCompletions(TextDocumentPositionParams params);
|
||||
CompletableFuture<CompletionItem> resolveCompletion(CompletionItem unresolved);
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.completion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.TextReplace;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SortKeys;
|
||||
import org.springframework.ide.vscode.commons.util.Futures;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
/**
|
||||
* Adapts a {@link ICompletionEngine}, wrapping it, to implement {@link VscodeCompletionEngine}
|
||||
*/
|
||||
public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine {
|
||||
|
||||
private final static int MAX_COMPLETIONS = 20;
|
||||
private int maxCompletions = MAX_COMPLETIONS;
|
||||
final static Logger logger = LoggerFactory.getLogger(VscodeCompletionEngineAdapter.class);
|
||||
public static final String VS_CODE_CURSOR_MARKER = "{{}}";
|
||||
|
||||
private SimpleLanguageServer server;
|
||||
private ICompletionEngine engine;
|
||||
|
||||
public VscodeCompletionEngineAdapter(SimpleLanguageServer server, ICompletionEngine engine) {
|
||||
this.server = server;
|
||||
this.engine = engine;
|
||||
}
|
||||
|
||||
public void setMaxCompletionsNumber(int maxCompletions) {
|
||||
this.maxCompletions = maxCompletions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CompletionList> getCompletions(TextDocumentPositionParams params) {
|
||||
return getCompletionsMono(params).toFuture();
|
||||
}
|
||||
|
||||
private Mono<CompletionList> getCompletionsMono(TextDocumentPositionParams params) {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
TextDocument doc = documents.get(params).copy();
|
||||
if (doc!=null) {
|
||||
return Mono.fromCallable(() -> {
|
||||
//TODO: This callable is a 'big lump of work' so can't be canceled in pieces.
|
||||
// Should we push using of reactive streams down further and compose this all
|
||||
// using reactive style? If not then this is overkill could just as well use
|
||||
// only standard Java API such as Executor and CompletableFuture directly.
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
List<ICompletionProposal> completions = new ArrayList<>(engine.getCompletions(doc, offset));
|
||||
Collections.sort(completions, ScoreableProposal.COMPARATOR);
|
||||
CompletionList list = new CompletionList();
|
||||
list.setIsIncomplete(false);
|
||||
List<CompletionItem> items = new ArrayList<>(completions.size());
|
||||
SortKeys sortkeys = new SortKeys();
|
||||
int count = 0;
|
||||
for (ICompletionProposal c : completions) {
|
||||
count++;
|
||||
if (maxCompletions > 0 && count>maxCompletions) {
|
||||
list.setIsIncomplete(true);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
items.add(adaptItem(doc, c, sortkeys));
|
||||
} catch (Exception e) {
|
||||
logger.error("error computing completion", e);
|
||||
}
|
||||
}
|
||||
list.setItems(items);
|
||||
return list;
|
||||
})
|
||||
.subscribeOn(Schedulers.elastic()); //!!! without this the mono will just be computed on the same thread that calls it.
|
||||
}
|
||||
return Mono.just(SimpleTextDocumentService.NO_COMPLETIONS);
|
||||
}
|
||||
|
||||
private CompletionItem adaptItem(TextDocument doc, ICompletionProposal completion, SortKeys sortkeys) throws Exception {
|
||||
CompletionItem item = new CompletionItem();
|
||||
item.setLabel(completion.getLabel());
|
||||
item.setKind(completion.getKind());
|
||||
item.setSortText(sortkeys.next());
|
||||
item.setFilterText(completion.getFilterText());
|
||||
item.setDetail(completion.getDetail());
|
||||
item.setDocumentation(toMarkdown(completion.getDocumentation()));
|
||||
adaptEdits(item, doc, completion.getTextEdit());
|
||||
return item;
|
||||
}
|
||||
|
||||
private String toMarkdown(Renderable r) {
|
||||
if (r!=null) {
|
||||
return r.toMarkdown();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void adaptEdits(CompletionItem item, TextDocument doc, DocumentEdits edits) throws Exception {
|
||||
TextReplace replaceEdit = edits.asReplacement(doc);
|
||||
if (replaceEdit==null) {
|
||||
//The original edit does nothing.
|
||||
item.setInsertText("");
|
||||
} else {
|
||||
TextDocument newDoc = doc.copy();
|
||||
edits.apply(newDoc);
|
||||
TextEdit vscodeEdit = new TextEdit();
|
||||
vscodeEdit.setRange(doc.toRange(replaceEdit.start, replaceEdit.end-replaceEdit.start));
|
||||
vscodeEdit.setNewText(vscodeIndentFix(doc, vscodeEdit.getRange().getStart(), replaceEdit.newText));
|
||||
//TODO: cursor offset within newText? for now we assume its always at the end.
|
||||
item.setTextEdit(vscodeEdit);
|
||||
}
|
||||
}
|
||||
|
||||
private String vscodeIndentFix(TextDocument doc, Position start, String newText) {
|
||||
//Vscode applies some magic indent to a multi-line edit text. We do everything ourself so we have adjust for the magic
|
||||
// and do some kind of 'inverse magic' here.
|
||||
//See here: https://github.com/Microsoft/language-server-protocol/issues/83
|
||||
int referenceLine = start.getLine();
|
||||
int referenceLineIndent = doc.getLineIndentation(referenceLine);
|
||||
int vscodeMagicIndent = Math.min(start.getCharacter(), referenceLineIndent);
|
||||
return vscodeMagicIndent>0
|
||||
? StringUtil.stripIndentation(vscodeMagicIndent, newText)
|
||||
: newText;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CompletionItem> resolveCompletion(CompletionItem unresolved) {
|
||||
//TODO: item is pre-resoved so we don't do anything, but we really should somehow defer some work, such as
|
||||
// for example computing docs and edits to resolve time.
|
||||
//The tricky part is that we have to probably remember infos about the unresolved elements somehow so we can resolve later.
|
||||
return Futures.of(unresolved);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.commons.languageserver.definition;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DefinitionHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
|
||||
/**
|
||||
* {@link SimpleDefinitionFinder} provides a 'dummy' implementation of
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class SimpleDefinitionFinder<T extends SimpleLanguageServer> implements DefinitionHandler {
|
||||
|
||||
protected final T server;
|
||||
|
||||
public SimpleDefinitionFinder(T server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<Location>> handle(TextDocumentPositionParams position) {
|
||||
return findDefinitions(position)
|
||||
.collect(Collectors.toList())
|
||||
.toFuture();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is meant to be overridden by subclass. This method provides a simple implementation
|
||||
* of 'goto definition' (which is not one you probably want to use in practice, but it
|
||||
* might be usful just to test whether things are wired up correctly to make the
|
||||
* 'goto definition' action in vscode work.
|
||||
* <p>
|
||||
* The implementation provided here simply looks for the first occurrence of the word
|
||||
* currently pointed at in the current document using String.indexOf.
|
||||
*/
|
||||
protected Flux<Location> findDefinitions(TextDocumentPositionParams params) {
|
||||
try {
|
||||
TextDocument doc = server.getTextDocumentService().get(params);
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
int start = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(start))) {
|
||||
start--;
|
||||
}
|
||||
start = start+1;
|
||||
int end = offset;
|
||||
while (Character.isLetter(doc.getSafeChar(end))) {
|
||||
end++;
|
||||
}
|
||||
String word = doc.textBetween(start, end);
|
||||
Log.log("Looking for definition of '"+word+"'");
|
||||
String text = doc.get();
|
||||
int def = text.indexOf(word);
|
||||
if (def>=0) {
|
||||
return Flux.just(
|
||||
new Location(params.getTextDocument().getUri(),
|
||||
doc.toRange(def, word.length())
|
||||
)
|
||||
)
|
||||
.doOnNext((Location loc) -> {
|
||||
Log.log("definition: "+loc);
|
||||
});
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return Flux.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*******************************************************************************
|
||||
* 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.commons.languageserver.hover;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
public interface HoverInfoProvider {
|
||||
|
||||
Tuple2<Renderable, IRegion> getHoverInfo(IDocument document, int offset) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*******************************************************************************
|
||||
* 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.commons.languageserver.hover;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
|
||||
public interface VscodeHoverEngine {
|
||||
|
||||
CompletableFuture<Hover> getHover(TextDocumentPositionParams params);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*******************************************************************************
|
||||
* 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.commons.languageserver.hover;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import reactor.util.function.Tuple2;
|
||||
|
||||
public class VscodeHoverEngineAdapter implements VscodeHoverEngine {
|
||||
|
||||
public enum HoverType {
|
||||
MARKDOWN,
|
||||
HTML
|
||||
}
|
||||
|
||||
private HoverInfoProvider hoverInfoProvider;
|
||||
private SimpleLanguageServer server;
|
||||
private HoverType type;
|
||||
final static Logger logger = LoggerFactory.getLogger(VscodeHoverEngineAdapter.class);
|
||||
|
||||
|
||||
public VscodeHoverEngineAdapter(SimpleLanguageServer server, HoverInfoProvider hoverInfoProvider) {
|
||||
this(server, hoverInfoProvider, HoverType.MARKDOWN);
|
||||
}
|
||||
|
||||
public VscodeHoverEngineAdapter(SimpleLanguageServer server, HoverInfoProvider hoverInfoProvider, HoverType type) {
|
||||
this.hoverInfoProvider = hoverInfoProvider;
|
||||
this.server = server;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public void setHoverType(HoverType type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Hover> getHover(TextDocumentPositionParams params) {
|
||||
//TODO: This returns a CompletableFuture which suggests we should try to do expensive work asyncly.
|
||||
// We are currently just doing all this in a blocking way and wrapping the already computed list into
|
||||
// a trivial pre-resolved future.
|
||||
try {
|
||||
SimpleTextDocumentService documents = server.getTextDocumentService();
|
||||
TextDocument doc = documents.get(params);
|
||||
if (doc!=null) {
|
||||
int offset = doc.toOffset(params.getPosition());
|
||||
|
||||
Tuple2<Renderable, IRegion> hoverTuple = hoverInfoProvider.getHoverInfo(doc, offset);
|
||||
if (hoverTuple != null) {
|
||||
Renderable hoverInfo = hoverTuple.getT1();
|
||||
IRegion region = hoverTuple.getT2();
|
||||
Range range = doc.toRange(region.getOffset(), region.getLength());
|
||||
|
||||
String rendered = render(hoverInfo, type);
|
||||
if (StringUtil.hasText(rendered)) {
|
||||
Hover hover = new Hover(ImmutableList.of(Either.forLeft(rendered)), range);
|
||||
return CompletableFuture.completedFuture(hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("error computing hover", e);
|
||||
}
|
||||
return SimpleTextDocumentService.NO_HOVER;
|
||||
}
|
||||
|
||||
private static String render(Renderable renderable, HoverType type) {
|
||||
switch (type) {
|
||||
case HTML:
|
||||
return renderable.toHtml();
|
||||
case MARKDOWN:
|
||||
return renderable.toMarkdown();
|
||||
default:
|
||||
return renderable.toMarkdown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*******************************************************************************
|
||||
* 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.commons.languageserver.quickfix;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
|
||||
/**
|
||||
* Represents a strategy for computing potential quickfixes for a given problem.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ProblemFixer {
|
||||
|
||||
/**
|
||||
* Implementor can inspect the problem and quickfix context provided as parameters.
|
||||
* <p>
|
||||
* If the problem is deemed fixable, the strategy can contribute one or more fixes by
|
||||
* adding them to the list of proposals (provided as third parameter).
|
||||
*/
|
||||
void contributeFixes(
|
||||
QuickfixContext context, ReconcileProblem problem,
|
||||
List<ICompletionProposal> proposals
|
||||
);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 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.commons.languageserver.quickfix;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* Provides access to additional context info and objects that quickfixes might
|
||||
* need in order to be able to apply themselves.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface QuickfixContext {
|
||||
// IProject getProject();
|
||||
// IPreferenceStore getWorkspacePreferences();
|
||||
// IPreferenceStore getProjectPreferences();
|
||||
// IJavaProject getJavaProject();
|
||||
// UserInteractions getUI();
|
||||
IDocument getDocument();
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
/**
|
||||
* A fake reconcule engine which is not useful except for quickly testing
|
||||
* whether stuff is wired up correctly to the editor.
|
||||
*/
|
||||
public class BadWordReconcileEngine implements IReconcileEngine {
|
||||
|
||||
static enum BWProblemType implements ProblemType {
|
||||
VERY_BAD_WORD,
|
||||
BAD_WORD;
|
||||
|
||||
@Override
|
||||
public ProblemSeverity getDefaultSeverity() {
|
||||
if (this==VERY_BAD_WORD) {
|
||||
return ProblemSeverity.ERROR;
|
||||
} else {
|
||||
return ProblemSeverity.WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return this.name();
|
||||
}
|
||||
}
|
||||
|
||||
private final String[] BADWORDS = {
|
||||
"bar", "foo"
|
||||
};
|
||||
|
||||
public BadWordReconcileEngine() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconcile(IDocument doc, IProblemCollector problemCollector) {
|
||||
String text = doc.get();
|
||||
System.out.println(">>>> reconciling for bad words ==========");
|
||||
System.out.println(text);
|
||||
System.out.println("<<<< reconciling for bad words ==========");
|
||||
|
||||
problemCollector.beginCollecting();
|
||||
try {
|
||||
for (String badword : BADWORDS) {
|
||||
int pos = 0;
|
||||
while (pos>=0 && pos < text.length()) {
|
||||
int badPos = text.indexOf(badword, pos);
|
||||
if (badPos>=0) {
|
||||
if (badword.equals(BADWORDS[0])) {
|
||||
problemCollector.accept(new ReconcileProblemImpl(BWProblemType.VERY_BAD_WORD, "'"+badword+"' is a VERY bad word", badPos, badword.length()));
|
||||
} else {
|
||||
problemCollector.accept(new ReconcileProblemImpl(BWProblemType.BAD_WORD, "'"+badword+"' is a bad word", badPos, badword.length()));
|
||||
}
|
||||
pos = badPos+1;
|
||||
} else {
|
||||
pos = badPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
problemCollector.endCollecting();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2014-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.commons.languageserver.reconcile;
|
||||
|
||||
public interface IProblemCollector {
|
||||
|
||||
void beginCollecting();
|
||||
void endCollecting();
|
||||
void accept(ReconcileProblem problem);
|
||||
|
||||
/**
|
||||
* Problem collector that simply ignores/discards anything passed to it.
|
||||
*/
|
||||
IProblemCollector NULL = new IProblemCollector() {
|
||||
public void beginCollecting() {
|
||||
}
|
||||
public void endCollecting() {
|
||||
}
|
||||
public void accept(ReconcileProblem problem) {
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 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.commons.languageserver.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
public interface IReconcileEngine {
|
||||
IReconcileEngine NULL = (d, p) -> {
|
||||
p.beginCollecting();
|
||||
p.endCollecting();
|
||||
};
|
||||
|
||||
public void reconcile(IDocument doc, IProblemCollector problemCollector);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.reconcile;
|
||||
|
||||
/**
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public enum ProblemSeverity {
|
||||
|
||||
IGNORE,
|
||||
WARNING,
|
||||
ERROR;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.reconcile;
|
||||
|
||||
/**
|
||||
* Besides the methods below, the only hard requirement for a 'problem type' is
|
||||
* that it is a unique object that is not 'equals' to any other object.
|
||||
* <p>
|
||||
* It is probably nice if you implement a good toString however.
|
||||
* <p>
|
||||
* A good way to implement a discrete set of problemType objects is as an enum
|
||||
* that implements this interace.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface ProblemType {
|
||||
ProblemSeverity getDefaultSeverity();
|
||||
String toString();
|
||||
String getCode();
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.commons.languageserver.reconcile;
|
||||
|
||||
public interface ProblemTypeProvider {
|
||||
|
||||
ProblemType getProblemType();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.commons.languageserver.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.ValueParseException;
|
||||
|
||||
/**
|
||||
* Exception if there is a failure when parsing a value. It does not wrap
|
||||
* other exceptions such that when thrown, the parse exception is the "deepest"
|
||||
* error.
|
||||
*
|
||||
*/
|
||||
public class ReconcileException extends ValueParseException implements ProblemTypeProvider {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final ProblemType problemType;
|
||||
|
||||
public ReconcileException(String message, ProblemType problemType) {
|
||||
super(message);
|
||||
this.problemType = problemType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProblemType getProblemType() {
|
||||
return problemType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.reconcile;
|
||||
|
||||
/**
|
||||
* Minamal interface that objects representing a reconciler problem must
|
||||
* implement.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public interface ReconcileProblem {
|
||||
ProblemType getType();
|
||||
String getMessage();
|
||||
int getOffset();
|
||||
int getLength();
|
||||
String getCode();
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.reconcile;
|
||||
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
|
||||
|
||||
/**
|
||||
* An implementation of {@link ReconcileProblem} that is just a simple data object.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ReconcileProblemImpl implements ReconcileProblem {
|
||||
|
||||
final private ProblemType type;
|
||||
final private String msg;
|
||||
final private int offset;
|
||||
final private int len;
|
||||
|
||||
public ReconcileProblemImpl(ProblemType type, String msg, int offset, int len) {
|
||||
super();
|
||||
this.type = type;
|
||||
this.msg = msg;
|
||||
this.offset = offset;
|
||||
this.len = len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProblemType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return len;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return getType().getCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to enlarge a empty document region to include a
|
||||
* character that can be visibly underlined.
|
||||
*/
|
||||
protected static DocumentRegion makeVisible(DocumentRegion region) {
|
||||
DocumentRegion altRegion = region.textAfter(1);
|
||||
if (!altRegion.isEmpty() && canUnderline(altRegion.charAt(0))) {
|
||||
return altRegion;
|
||||
}
|
||||
altRegion = region.textBefore(1);
|
||||
if (!altRegion.isEmpty() && canUnderline(altRegion.charAt(0))) {
|
||||
return altRegion;
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
private static boolean canUnderline(char c) {
|
||||
return c!='\n'&&c!='\r';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CompletionHandler {
|
||||
CompletableFuture<CompletionList> handle(TextDocumentPositionParams params);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface CompletionResolveHandler {
|
||||
CompletableFuture<CompletionItem> handle(CompletionItem unresolved);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.commons.languageserver.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface DefinitionHandler {
|
||||
CompletableFuture<List<Location>> handle(TextDocumentPositionParams position);
|
||||
}
|
||||
@@ -0,0 +1,285 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
import org.springframework.ide.vscode.commons.util.text.IRegion;
|
||||
import org.springframework.ide.vscode.commons.util.text.Region;
|
||||
|
||||
/**
|
||||
* A non-sucky alternative to {@link IRegion}. Represents a region of text in a document.
|
||||
* <p>
|
||||
* Caution: assumes the underlying document is not mutated during the lifetime of the
|
||||
* region object (otherwise start/end positions may no longer be valid).
|
||||
* <p>
|
||||
* Implements {@link CharSequence} for convenience (e.g you can use {@link DocumentRegion} as
|
||||
* input to a {@link Pattern} and other standard JRE functions which expect a {@link CharSequence}.
|
||||
*
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class DocumentRegion implements CharSequence {
|
||||
final IDocument doc;
|
||||
final int start;
|
||||
final int end;
|
||||
public DocumentRegion(IDocument doc, IRegion r) {
|
||||
this(doc,
|
||||
r.getOffset(),
|
||||
r.getOffset()+r.getLength()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link DocumentRegion} on a given document. Tries its
|
||||
* best to behave sensibly when passed 'strange' coordinates by
|
||||
* adjusting them logically rather than throw an Exception.
|
||||
* <p>
|
||||
* A position before the start of the document is moved to be the start
|
||||
* of the document.
|
||||
* <p>
|
||||
* A position after the end of the document is moved to the end
|
||||
* of the document.
|
||||
* <p>
|
||||
* If 'end' position is before the start position it is moved be
|
||||
* exactly at the start position (this avoids region with
|
||||
* negative length).
|
||||
*/
|
||||
public DocumentRegion(IDocument doc, int start, int end) {
|
||||
this.doc = doc;
|
||||
this.start = limitRange(start, 0, doc.getLength());
|
||||
this.end = limitRange(end, start, doc.getLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create {@link DocumentRegion} covering the whole document.
|
||||
*/
|
||||
public DocumentRegion(IDocument doc) {
|
||||
this(doc, 0, doc.getLength());
|
||||
}
|
||||
|
||||
private int limitRange(int offset, int min, int max) {
|
||||
if (offset<min) {
|
||||
return min;
|
||||
}
|
||||
if (offset>max) {
|
||||
return max;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return DocumentUtil.textBetween(doc, start, end);
|
||||
}
|
||||
|
||||
public DocumentRegion trim() {
|
||||
return trimEnd().trimStart();
|
||||
}
|
||||
|
||||
public DocumentRegion trimStart() {
|
||||
int howMany = 0;
|
||||
int len = length();
|
||||
while (howMany<len && Character.isWhitespace(charAt(howMany))) {
|
||||
howMany++;
|
||||
}
|
||||
return subSequence(howMany, len);
|
||||
}
|
||||
|
||||
public DocumentRegion trimEnd() {
|
||||
int howMany = 0; //how many chars to remove from the end
|
||||
int len = length();
|
||||
int lastChar = len-1;
|
||||
while (howMany<len && Character.isWhitespace(charAt(lastChar-howMany))) {
|
||||
howMany++;
|
||||
}
|
||||
if (howMany>0) {
|
||||
return subSequence(0, len-howMany);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets character from the region, offset from the start of the region
|
||||
* @return the character from the document (char)0 if the offset is outside the region.
|
||||
*/
|
||||
@Override
|
||||
public char charAt(int offset) {
|
||||
if (offset<0 || offset>=length()) {
|
||||
throw new IndexOutOfBoundsException(""+offset);
|
||||
}
|
||||
try {
|
||||
return doc.getChar(start+offset);
|
||||
} catch (BadLocationException e) {
|
||||
throw new IndexOutOfBoundsException(""+offset);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int length() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentRegion subSequence(int start, int end) {
|
||||
int len = length();
|
||||
Assert.isLegal(start>=0);
|
||||
Assert.isLegal(end<=len);
|
||||
if (start==0 && end==len) {
|
||||
return this;
|
||||
}
|
||||
return new DocumentRegion(doc, this.start+start, this.start+end);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return length()==0;
|
||||
}
|
||||
|
||||
public DocumentRegion subSequence(int start) {
|
||||
return subSequence(start, length());
|
||||
}
|
||||
|
||||
public IRegion asRegion() {
|
||||
return new Region(start, end-start);
|
||||
}
|
||||
|
||||
public int indexOf(char ch, int fromIndex) {
|
||||
while (fromIndex < length()) {
|
||||
if (charAt(fromIndex)==ch) {
|
||||
return fromIndex;
|
||||
}
|
||||
fromIndex++;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public DocumentRegion[] split(char c) {
|
||||
List<DocumentRegion> pieces = new ArrayList<>();
|
||||
int start = 0;
|
||||
int end;
|
||||
while ((end=indexOf(c, start))>=0) {
|
||||
pieces.add(subSequence(start, end));
|
||||
start = end+1;
|
||||
}
|
||||
// Do not forget the last piece!
|
||||
pieces.add(subSequence(start, length()));
|
||||
return pieces.toArray(new DocumentRegion[pieces.size()]);
|
||||
}
|
||||
|
||||
public DocumentRegion[] split(Pattern delimiter) {
|
||||
List<DocumentRegion> pieces = new ArrayList<>();
|
||||
int start = 0;
|
||||
Matcher matcher = delimiter.matcher(this);
|
||||
while (matcher.find(start)) {
|
||||
int end = matcher.start();
|
||||
pieces.add(subSequence(start, end));
|
||||
start = matcher.end();
|
||||
}
|
||||
// Do not forget the last piece!
|
||||
pieces.add(subSequence(start, length()));
|
||||
return pieces.toArray(new DocumentRegion[pieces.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single occurrence of pat from the start of this region.
|
||||
*/
|
||||
public DocumentRegion trimStart(Pattern pat) {
|
||||
pat = Pattern.compile("^("+pat.pattern()+")");
|
||||
Matcher matcher = pat.matcher(this);
|
||||
if (matcher.find()) {
|
||||
return subSequence(matcher.end());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single occurrence of pat from the end of this region.
|
||||
*/
|
||||
public DocumentRegion trimEnd(Pattern pat) {
|
||||
pat = Pattern.compile("("+pat.pattern()+")$");
|
||||
Matcher matcher = pat.matcher(this);
|
||||
if (matcher.find()) {
|
||||
return subSequence(0, matcher.start());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the region after this one with a given lenght.
|
||||
* <p>
|
||||
* If the document is too short to provide the requested lenght
|
||||
* then the region is truncated to end of the document.
|
||||
*/
|
||||
public DocumentRegion textAfter(int len) {
|
||||
Assert.isLegal(len>=0);
|
||||
return new DocumentRegion(doc, end, end+len);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the region before this one with a given lenght.
|
||||
* <p>
|
||||
* If the requested region extends before the start of the document,
|
||||
* then the region is shortened so its start coincides with document start.
|
||||
*/
|
||||
public DocumentRegion textBefore(int len) {
|
||||
Assert.isLegal(len>=0);
|
||||
return new DocumentRegion(doc, start-len, start);
|
||||
}
|
||||
|
||||
public IDocument getDocument() {
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start of this region in 'absolute' terms (i.e. relative to the document).
|
||||
*/
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end of this region in 'absolute' terms (i.e. relative to the document).
|
||||
*/
|
||||
public int getEnd() {
|
||||
return end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given document offset into an offset relative to this region.
|
||||
*/
|
||||
public int toRelative(int offset) {
|
||||
return offset-start;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length();
|
||||
}
|
||||
|
||||
public boolean endsWith(CharSequence string) {
|
||||
int myLen = length();
|
||||
int strLen = string.length();
|
||||
if (myLen>=strLen) {
|
||||
for (int i = 0; i < strLen; i++) {
|
||||
if (charAt(myLen-strLen+i)!=string.charAt(i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
public class DocumentUtil {
|
||||
|
||||
//TODO: this stuff belongs in IDocument and its implementation, not here. This class should be removed.
|
||||
|
||||
/**
|
||||
* Fetch text between two offsets. Doesn't throw BadLocationException.
|
||||
* If either one or both of the offsets points outside the
|
||||
* document then they will be adjusted to point the appropriate boundary to
|
||||
* retrieve the text just upto the end or beginning of the document instead.
|
||||
*/
|
||||
public static String textBetween(IDocument doc, int start, int end) {
|
||||
Assert.isLegal(start<=end);
|
||||
if (start>=doc.getLength()) {
|
||||
return "";
|
||||
}
|
||||
if (start<0) {
|
||||
start = 0;
|
||||
}
|
||||
if (end>doc.getLength()) {
|
||||
end = doc.getLength();
|
||||
}
|
||||
if (end<start) {
|
||||
end = start;
|
||||
}
|
||||
try {
|
||||
return doc.get(start, end-start);
|
||||
} catch (BadLocationException e) {
|
||||
//unless the code above is wrong... this is supposed to be impossible!
|
||||
throw new IllegalStateException("Bug!", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface HoverHandler {
|
||||
CompletableFuture<Hover> handle(TextDocumentPositionParams params);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.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,32 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.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,82 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.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.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.LogRecord;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
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("");
|
||||
// for (Handler h : ImmutableList.copyOf(root.getHandlers())) {
|
||||
// root.removeHandler(h);
|
||||
// }
|
||||
// FileHandler file = new FileHandler("sts4-extension.%g.log", 100_000, 1, false);
|
||||
// root.addHandler(file);
|
||||
|
||||
for (Handler h : root.getHandlers())
|
||||
h.setFormatter(new LoggingFormat());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015 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.commons.languageserver.util;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.IDocument;
|
||||
|
||||
public abstract class PrefixFinder {
|
||||
public String getPrefix(IDocument doc, int offset, int lowerBound) {
|
||||
try {
|
||||
if (doc == null || offset > doc.getLength())
|
||||
return null;
|
||||
int prefixStart = offset;
|
||||
while (prefixStart > lowerBound && isPrefixChar(doc.getChar(prefixStart-1))) {
|
||||
prefixStart--;
|
||||
}
|
||||
return doc.get(prefixStart, offset-prefixStart);
|
||||
} catch (BadLocationException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String getPrefix(IDocument doc, int offset) {
|
||||
return getPrefix(doc, offset, 0);
|
||||
}
|
||||
protected abstract boolean isPrefixChar(char c);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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.commons.languageserver.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.ReferenceParams;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ReferencesHandler {
|
||||
|
||||
CompletableFuture<List<? extends Location>> handle(ReferenceParams params);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import org.eclipse.lsp4j.jsonrpc.services.JsonNotification;
|
||||
import org.eclipse.lsp4j.jsonrpc.services.JsonSegment;
|
||||
|
||||
/**
|
||||
* Handler for $ messages. They should/could be ignored since they depend on the
|
||||
* implementation capabilities on the server.
|
||||
*
|
||||
* @author Alex Boyko
|
||||
*
|
||||
*/
|
||||
@JsonSegment("$")
|
||||
public interface ServiceNotificationsClient {
|
||||
|
||||
@JsonNotification
|
||||
default void setTraceNotification(Object param) {
|
||||
// Ignore Message
|
||||
}
|
||||
|
||||
@JsonNotification
|
||||
default void logTraceNotification(Object param) {
|
||||
// Ignore Message
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.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,44 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import org.eclipse.lsp4j.MessageParams;
|
||||
import org.eclipse.lsp4j.MessageType;
|
||||
|
||||
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(MessageType.Error, message, cause);
|
||||
}
|
||||
|
||||
public static ShowMessageException warning(String message, Exception cause) {
|
||||
return create(MessageType.Warning, message, cause);
|
||||
}
|
||||
|
||||
private static ShowMessageException create(MessageType warning, String message, Exception cause) {
|
||||
MessageParams m = new MessageParams();
|
||||
|
||||
m.setMessage(message);
|
||||
m.setType(warning);
|
||||
|
||||
return new ShowMessageException(m, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2017 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.commons.languageserver.util;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.DiagnosticSeverity;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.MessageParams;
|
||||
import org.eclipse.lsp4j.MessageType;
|
||||
import org.eclipse.lsp4j.ServerCapabilities;
|
||||
import org.eclipse.lsp4j.services.LanguageClient;
|
||||
import org.eclipse.lsp4j.services.LanguageClientAware;
|
||||
import org.eclipse.lsp4j.services.LanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.ProgressParams;
|
||||
import org.springframework.ide.vscode.commons.languageserver.ProgressService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemSeverity;
|
||||
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Scheduler;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
/**
|
||||
* 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, LanguageClientAware, ServiceNotificationsClient {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(SimpleLanguageServer.class.getName());
|
||||
|
||||
private static final Scheduler RECONCILER_SCHEDULER = Schedulers.newSingle("Reconciler");
|
||||
|
||||
private Path workspaceRoot;
|
||||
|
||||
private SimpleTextDocumentService tds;
|
||||
|
||||
private SimpleWorkspaceService workspace;
|
||||
|
||||
private STS4LanguageClient client;
|
||||
|
||||
private ProgressService progressService = (String taskId, String statusMsg) -> {
|
||||
STS4LanguageClient client = SimpleLanguageServer.this.client;
|
||||
if (client!=null) {
|
||||
client.progress(new ProgressParams(taskId, statusMsg));
|
||||
}
|
||||
};
|
||||
|
||||
private CompletableFuture<Void> busyReconcile = CompletableFuture.completedFuture(null);
|
||||
|
||||
@Override
|
||||
public void connect(LanguageClient _client) {
|
||||
this.client = (STS4LanguageClient) _client;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
InitializeResult result = new InitializeResult();
|
||||
|
||||
ServerCapabilities cap = getServerCapabilities();
|
||||
result.setCapabilities(cap);
|
||||
|
||||
return CompletableFuture.completedFuture(result);
|
||||
}
|
||||
|
||||
public void onError(String message, Throwable error) {
|
||||
LanguageClient cl = this.client;
|
||||
if (cl != null) {
|
||||
if (error instanceof ShowMessageException)
|
||||
client.showMessage(((ShowMessageException) error).message);
|
||||
else {
|
||||
LOG.log(Level.SEVERE, message, error);
|
||||
|
||||
MessageParams m = new MessageParams();
|
||||
|
||||
m.setMessage(message);
|
||||
m.setType(MessageType.Error);
|
||||
client.showMessage(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract ServerCapabilities getServerCapabilities();
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Object> shutdown() {
|
||||
return CompletableFuture.completedFuture(new Object());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
public Path getWorkspaceRoot() {
|
||||
return workspaceRoot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized SimpleTextDocumentService getTextDocumentService() {
|
||||
if (tds==null) {
|
||||
tds = createTextDocumentService();
|
||||
}
|
||||
return tds;
|
||||
}
|
||||
|
||||
protected SimpleTextDocumentService createTextDocumentService() {
|
||||
return new SimpleTextDocumentService(this);
|
||||
}
|
||||
|
||||
public SimpleWorkspaceService createWorkspaceService() {
|
||||
return new SimpleWorkspaceService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized SimpleWorkspaceService getWorkspaceService() {
|
||||
if (workspace==null) {
|
||||
workspace = createWorkspaceService();
|
||||
}
|
||||
return workspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method. Subclasses can call this to use a {@link IReconcileEngine} ported
|
||||
* from old STS codebase to validate a given {@link TextDocument} and publish Diagnostics.
|
||||
*/
|
||||
protected void validateWith(TextDocument _doc, IReconcileEngine engine) {
|
||||
TextDocument doc = _doc.copy();
|
||||
|
||||
CompletableFuture<Void> reconcileSession = this.busyReconcile = new CompletableFuture<Void>();
|
||||
|
||||
SimpleTextDocumentService documents = getTextDocumentService();
|
||||
IProblemCollector problems = new IProblemCollector() {
|
||||
|
||||
private List<Diagnostic> diagnostics = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public void endCollecting() {
|
||||
documents.publishDiagnostics(doc, diagnostics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beginCollecting() {
|
||||
diagnostics.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(ReconcileProblem problem) {
|
||||
try {
|
||||
DiagnosticSeverity severity = getDiagnosticSeverity(problem);
|
||||
if (severity!=null) {
|
||||
Diagnostic d = new Diagnostic();
|
||||
d.setCode(problem.getCode());
|
||||
d.setMessage(problem.getMessage());
|
||||
d.setRange(doc.toRange(problem.getOffset(), problem.getLength()));
|
||||
d.setSeverity(severity);
|
||||
diagnostics.add(d);
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
LOG.log(Level.WARNING, "Invalid reconcile problem ignored", e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Avoid running in the same thread as lsp4j as it can result
|
||||
// in long "hangs" for slow reconcile providers
|
||||
Mono.fromRunnable(() -> {
|
||||
engine.reconcile(doc, problems);
|
||||
})
|
||||
.doOnTerminate((ignore1, ignore2) -> {
|
||||
reconcileSession.complete(null);
|
||||
})
|
||||
.subscribeOn(RECONCILER_SCHEDULER)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
protected DiagnosticSeverity getDiagnosticSeverity(ReconcileProblem problem) {
|
||||
ProblemSeverity severity = problem.getType().getDefaultSeverity();
|
||||
switch (severity) {
|
||||
case ERROR:
|
||||
return DiagnosticSeverity.Error;
|
||||
case WARNING:
|
||||
return DiagnosticSeverity.Warning;
|
||||
case IGNORE:
|
||||
return null;
|
||||
default:
|
||||
throw new IllegalStateException("Bug! Missing switch case?");
|
||||
}
|
||||
}
|
||||
|
||||
public void waitForReconcile() throws Exception {
|
||||
while (!this.busyReconcile.isDone()) {
|
||||
this.busyReconcile.get();
|
||||
}
|
||||
}
|
||||
|
||||
public LanguageClient getClient() {
|
||||
return client;
|
||||
}
|
||||
|
||||
public ProgressService getProgressService() {
|
||||
return progressService;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,331 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
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.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.eclipse.lsp4j.CodeActionParams;
|
||||
import org.eclipse.lsp4j.CodeLens;
|
||||
import org.eclipse.lsp4j.CodeLensParams;
|
||||
import org.eclipse.lsp4j.Command;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.DidChangeTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidCloseTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidOpenTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DocumentFormattingParams;
|
||||
import org.eclipse.lsp4j.DocumentHighlight;
|
||||
import org.eclipse.lsp4j.DocumentOnTypeFormattingParams;
|
||||
import org.eclipse.lsp4j.DocumentRangeFormattingParams;
|
||||
import org.eclipse.lsp4j.DocumentSymbolParams;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.PublishDiagnosticsParams;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.ReferenceParams;
|
||||
import org.eclipse.lsp4j.RenameParams;
|
||||
import org.eclipse.lsp4j.SignatureHelp;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
|
||||
import org.eclipse.lsp4j.TextDocumentPositionParams;
|
||||
import org.eclipse.lsp4j.TextEdit;
|
||||
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.WorkspaceEdit;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.services.LanguageClient;
|
||||
import org.eclipse.lsp4j.services.TextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.LanguageIds;
|
||||
import org.springframework.ide.vscode.commons.util.Assert;
|
||||
import org.springframework.ide.vscode.commons.util.BadLocationException;
|
||||
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class SimpleTextDocumentService implements TextDocumentService {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(SimpleTextDocumentService.class.getName());
|
||||
|
||||
final private SimpleLanguageServer server;
|
||||
private Map<String, TextDocument> documents = new HashMap<>();
|
||||
private ListenerList<TextDocumentContentChange> documentChangeListeners = new ListenerList<>();
|
||||
private CompletionHandler completionHandler = null;
|
||||
private CompletionResolveHandler completionResolveHandler = null;
|
||||
private HoverHandler hoverHandler = null;
|
||||
|
||||
private DefinitionHandler definitionHandler;
|
||||
private ReferencesHandler referencesHandler;
|
||||
|
||||
public SimpleTextDocumentService(SimpleLanguageServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public synchronized void onHover(HoverHandler h) {
|
||||
Assert.isNull("A hover handler is already set, multiple handlers not supported yet", hoverHandler);
|
||||
this.hoverHandler = h;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public synchronized void onDefinition(DefinitionHandler h) {
|
||||
Assert.isNull("A defintion handler is already set, multiple handlers not supported yet", definitionHandler);
|
||||
this.definitionHandler = h;
|
||||
}
|
||||
|
||||
public synchronized void onRefeences(ReferencesHandler h) {
|
||||
Assert.isNull("A references handler is already set, multiple handlers not supported yet", referencesHandler);
|
||||
this.referencesHandler = 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) {
|
||||
try {
|
||||
VersionedTextDocumentIdentifier docId = params.getTextDocument();
|
||||
String url = docId.getUri();
|
||||
//LOG.info("didChange: "+url);
|
||||
if (url!=null) {
|
||||
TextDocument doc = getDocument(url);
|
||||
for (TextDocumentContentChangeEvent change : params.getContentChanges()) {
|
||||
doc.apply(change);
|
||||
didChangeContent(doc, change);
|
||||
}
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
LOG.log(Level.SEVERE, ExceptionUtil.getMessage(e), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didOpen(DidOpenTextDocumentParams params) {
|
||||
//LOG.info("didOpen: "+params.getUri());
|
||||
//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();
|
||||
String languageId = params.getTextDocument().getLanguageId();
|
||||
if (url!=null) {
|
||||
String text = params.getTextDocument().getText();
|
||||
TextDocument doc = createDocument(url, languageId);
|
||||
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) {
|
||||
//LOG.info("didClose: "+params.getTextDocument().getUri());
|
||||
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 getDocument(String url) {
|
||||
TextDocument doc = documents.get(url);
|
||||
if (doc==null) {
|
||||
LOG.warning("Trying to get document ["+url+"] but it did not exists. Creating it with language-id 'plaintext'");
|
||||
doc = createDocument(url, LanguageIds.PLAINTEXT);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
private synchronized TextDocument createDocument(String url, String languageId) {
|
||||
if (documents.get(url)!=null) {
|
||||
LOG.warning("Creating document ["+url+"] but it already exists. Existing document discarded!");
|
||||
}
|
||||
TextDocument doc = new TextDocument(url, languageId);
|
||||
documents.put(url, doc);
|
||||
return doc;
|
||||
}
|
||||
|
||||
public final static CompletionList NO_COMPLETIONS = new CompletionList(false, Collections.emptyList());
|
||||
public final static CompletableFuture<Hover> NO_HOVER = CompletableFuture.completedFuture(new Hover(ImmutableList.of(), null));
|
||||
public final static CompletableFuture<List<? extends Location>> NO_REFERENCES = CompletableFuture.completedFuture(ImmutableList.of());
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Either<List<CompletionItem>, CompletionList>> completion(TextDocumentPositionParams position) {
|
||||
CompletionHandler h = completionHandler;
|
||||
if (h!=null) {
|
||||
return completionHandler.handle(position)
|
||||
.thenApply(Either::forRight);
|
||||
}
|
||||
return CompletableFuture.completedFuture(Either.forRight(NO_COMPLETIONS));
|
||||
}
|
||||
|
||||
@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) {
|
||||
HoverHandler h = hoverHandler;
|
||||
if (h!=null) {
|
||||
return hoverHandler.handle(position);
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<SignatureHelp> signatureHelp(TextDocumentPositionParams position) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked"})
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Location>> definition(TextDocumentPositionParams position) {
|
||||
DefinitionHandler h = this.definitionHandler;
|
||||
if (h!=null) {
|
||||
Object r = h.handle(position); //YUCK!
|
||||
return (CompletableFuture<List<? extends Location>>) r;
|
||||
}
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Location>> references(ReferenceParams params) {
|
||||
ReferencesHandler h = this.referencesHandler;
|
||||
if (h != null) {
|
||||
return h.handle(params);
|
||||
}
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends SymbolInformation>> documentSymbol(DocumentSymbolParams params) {
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends Command>> codeAction(CodeActionParams params) {
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams params) {
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<CodeLens> resolveCodeLens(CodeLens unresolved) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> formatting(DocumentFormattingParams params) {
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> rangeFormatting(DocumentRangeFormattingParams params) {
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends TextEdit>> onTypeFormatting(DocumentOnTypeFormattingParams params) {
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<WorkspaceEdit> rename(RenameParams params) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void didSave(DidSaveTextDocumentParams params) {
|
||||
}
|
||||
|
||||
public void publishDiagnostics(TextDocument doc, List<Diagnostic> diagnostics) {
|
||||
LanguageClient client = server.getClient();
|
||||
if (client!=null && diagnostics!=null) {
|
||||
PublishDiagnosticsParams params = new PublishDiagnosticsParams();
|
||||
params.setUri(doc.getUri());
|
||||
params.setDiagnostics(diagnostics);
|
||||
client.publishDiagnostics(params);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized TextDocument get(TextDocumentPositionParams params) {
|
||||
return documents.get(params.getTextDocument().getUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<? extends DocumentHighlight>> documentHighlight(TextDocumentPositionParams position) {
|
||||
return CompletableFuture.completedFuture(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.eclipse.lsp4j.DidChangeConfigurationParams;
|
||||
import org.eclipse.lsp4j.DidChangeWatchedFilesParams;
|
||||
import org.eclipse.lsp4j.SymbolInformation;
|
||||
import org.eclipse.lsp4j.WorkspaceSymbolParams;
|
||||
import org.eclipse.lsp4j.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 didChangeConfiguration(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,39 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Utility to generate 'sort keys' in ascending order.
|
||||
* <p>
|
||||
* VSCode uses a String in each completion to determine the order of completions.
|
||||
* We use a 'score' based on how well a key matches what was typed.
|
||||
* <p>
|
||||
* To go from a 'score' to a sort-key we presort our proposals and then assign
|
||||
* a sort key for vscode.
|
||||
*/
|
||||
public class SortKeys implements Iterator<String> {
|
||||
|
||||
private int counter;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return String.format("%05d", counter++);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 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.commons.languageserver.util;
|
||||
|
||||
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user