diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerManager.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerManager.java index 8bf460e16..91134a989 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerManager.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerManager.java @@ -10,12 +10,16 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.jdt.ls; +import java.util.List; import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.Registration; import org.eclipse.lsp4j.RegistrationParams; import org.eclipse.lsp4j.Unregistration; import org.eclipse.lsp4j.UnregistrationParams; +import org.springframework.ide.vscode.commons.languageserver.util.AsyncRunner; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import com.google.common.collect.ImmutableList; @@ -28,30 +32,49 @@ public class ClasspathListenerManager { private static final String WORKSPACE_EXECUTE_COMMAND = "workspace/executeCommand"; private int commandIdCounter = 0; private SimpleLanguageServer server; + private AsyncRunner async; public ClasspathListenerManager(SimpleLanguageServer server) { this.server = server; + this.async = server.getAsync(); } public Disposable addClasspathListener(ClasspathListener classpathListener) { - // TODO: - // 1. register callback command handler in SimpleLanguageServer - // 2. call the client to ask it to call that callback - // 3. register the callback command with the client String callbackCommandId = "sts4.classpath." + (commandIdCounter++); - String registrationId = UUID.randomUUID().toString(); + // 1. register callback command handler in SimpleLanguageServer + Disposable unregisterCommand = server.onCommand(callbackCommandId, (ExecuteCommandParams callbackParams) -> async.invoke(() -> { + List args = callbackParams.getArguments(); + //Note: not sure... but args might be deserialized as com.google.gson.JsonElement's. + //If so the code below is not correct (casts will fail). + String projectUri = (String) args.get(0); + boolean deleted = args.size()>=2 && (Boolean)args.get(1); + classpathListener.changed(projectUri, deleted); + return "done"; + })); + // 2. call the client to ask it to call that callback + CompletableFuture future1 = server.getClient().addClasspathListener(new ClasspathListenerParams(callbackCommandId)); + + // 2. register the callback command with the client + String registrationId = UUID.randomUUID().toString(); RegistrationParams params = new RegistrationParams(ImmutableList.of( new Registration(registrationId, WORKSPACE_EXECUTE_COMMAND, ImmutableMap.of("commands", ImmutableList.of(callbackCommandId)) ) - )); - server.getClient().registerCapability(params); + )); + CompletableFuture future2 = server.getClient().registerCapability(params); + + // Wait for async work + //future1.join(); + //future2.join(); + + // Cleanups: return () -> { + unregisterCommand.dispose(); this.server.getClient().unregisterCapability(new UnregistrationParams(ImmutableList.of( new Unregistration(registrationId, WORKSPACE_EXECUTE_COMMAND) - ))); + ))); }; } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index 910b0e2d9..1463f7e0c 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -624,14 +624,28 @@ public class SimpleLanguageServer implements Sts4LanguageServer, LanguageClientA this.initializeHandler = handler; } - public void onInitialized(Runnable handler) { - Assert.isNull("Multiple initialized handlers not supported yet", this.initializedHandler); - this.initializedHandler = handler; + public synchronized void onInitialized(Runnable handler) { + if (this.initializedHandler==null) { + this.initializedHandler = handler; + } else { + Runnable oldHandler = this.initializedHandler; + this.initializedHandler = () -> { + oldHandler.run(); + handler.run(); + }; + } } - public void onShutdown(Runnable handler) { - Assert.isNull("Multiple shutdown handlers not supported yet", this.shutdownHandler); - this.shutdownHandler = handler; + public synchronized void onShutdown(Runnable handler) { + if (shutdownHandler==null) { + this.shutdownHandler = handler; + } else { + Runnable oldHandler = this.shutdownHandler; + this.shutdownHandler = () -> { + oldHandler.run(); + handler.run(); + }; + } } public AsyncRunner getAsync() { diff --git a/headless-services/org.springframework.tooling.jdt.ls.extension/plugin.xml b/headless-services/org.springframework.tooling.jdt.ls.extension/plugin.xml index c2bf5eb23..782d44ade 100644 --- a/headless-services/org.springframework.tooling.jdt.ls.extension/plugin.xml +++ b/headless-services/org.springframework.tooling.jdt.ls.extension/plugin.xml @@ -11,5 +11,11 @@ + + + + + + \ No newline at end of file diff --git a/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java b/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java new file mode 100644 index 000000000..273ed0cbd --- /dev/null +++ b/headless-services/org.springframework.tooling.jdt.ls.extension/src/org/springframework/tooling/jdt/ls/extension/ClasspathListenerHandler.java @@ -0,0 +1,56 @@ +package org.springframework.tooling.jdt.ls.extension; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.ls.core.internal.IDelegateCommandHandler; +import org.eclipse.jdt.ls.core.internal.JavaClientConnection; +import org.eclipse.jdt.ls.core.internal.JavaLanguageServerPlugin; +import org.eclipse.jdt.ls.core.internal.handlers.JDTLanguageServer; +import org.springframework.tooling.jdt.ls.extension.ClasspathListenerManager.ClasspathListener; + +@SuppressWarnings("restriction") +public class ClasspathListenerHandler implements IDelegateCommandHandler { + + static class MyClasspathListener implements ClasspathListener { + + private ClasspathListenerManager manager = null; + private List subscribers = new ArrayList<>(1); + + public synchronized void subscribe(String callbackCommandId) { + if (manager==null) { + this.manager = new ClasspathListenerManager(this); + } + subscribers.add(callbackCommandId); + } + + @Override + public void classpathChanged(IJavaProject jp) { + String project = jp.getProject().getLocationURI().toString(); + boolean deleted = !jp.exists(); + JavaClientConnection conn = JavaLanguageServerPlugin.getInstance().getClientConnection(); + for (String callbackCommandId : subscribers) { + conn.executeCommand(callbackCommandId, Arrays.asList(project, deleted)); + } + } + } + + private static MyClasspathListener classpathListener = new MyClasspathListener(); + + @Override + public Object executeCommand(String commandId, List arguments, IProgressMonitor monitor) throws Exception { + if (commandId.equals("sts.java.addClasspathListener")) { + return addClasspathListener((String)arguments.get(0)); + } + return null; + } + + private Object addClasspathListener(String callbackCommandId) { + classpathListener.subscribe(callbackCommandId); + return "ok"; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java index d04578b8b..0db4e5573 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/jdt/ls/JdtLsProjectCache.java @@ -17,6 +17,7 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.TextDocumentIdentifier; import org.slf4j.Logger; @@ -40,6 +41,8 @@ import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableList; +import reactor.core.Disposable; + public class JdtLsProjectCache implements JavaProjectFinder, ProjectObserver { private SimpleLanguageServer server; @@ -48,14 +51,15 @@ public class JdtLsProjectCache implements JavaProjectFinder, ProjectObserver { public JdtLsProjectCache(SimpleLanguageServer server) { this.server = server; - this.server.addClasspathListener(new ClasspathListener() { + CompletableFuture disposable = new CompletableFuture(); + this.server.onInitialized(() -> disposable.complete(server.addClasspathListener(new ClasspathListener() { @Override public void changed(String projectUri, boolean deleted) { - // TODO Auto-generated method stub - + log.info("Classpath changed: "+projectUri); } - }); + }))); + this.server.onShutdown(() -> disposable.thenAccept(Disposable::dispose)); } @Override diff --git a/vscode-extensions/commons-vscode/src/classpath.ts b/vscode-extensions/commons-vscode/src/classpath.ts index 8a06b6c66..437338c90 100644 --- a/vscode-extensions/commons-vscode/src/classpath.ts +++ b/vscode-extensions/commons-vscode/src/classpath.ts @@ -10,12 +10,25 @@ export function registerClasspathService(client : LanguageClient) : void { client.onRequest(classpathRequest, async (params: ClasspathParams) => { return await executeClasspathCommand(params.resourceUri); }); + + let classpathListenerRequest = new RequestType("sts/addClasspathListener"); + client.onRequest(classpathListenerRequest, async (params: ClasspathListenerParams) => { + return await VSCode.commands.executeCommand("java.execute.workspaceCommand", "sts.java.addClasspathListener", params.callbackCommandId); + }); } async function executeClasspathCommand(resourceUri : string) : Promise { return (await VSCode.commands.executeCommand("java.execute.workspaceCommand", "sts.java.resolveClasspath", resourceUri)); } + +interface ClasspathListenerParams { + callbackCommandId: string +} + +interface ClasspathListenerResponse { +} + interface ClasspathResponse { entries: ClasspathEntry[], defaultOutputFolder : string