diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java index 8aad7df17..5b152f95c 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java @@ -17,7 +17,6 @@ import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; import org.eclipse.lsp4j.services.LanguageClient; import org.springframework.ide.vscode.commons.languageserver.jdt.ls.ClasspathListenerParams; -import org.springframework.ide.vscode.commons.languageserver.jdt.ls.ClasspathListenerResponse; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit.CursorMovement; /** @@ -43,5 +42,8 @@ public interface STS4LanguageClient extends LanguageClient { CompletableFuture project(String uri); @JsonRequest("sts/addClasspathListener") - CompletableFuture addClasspathListener(ClasspathListenerParams params); + CompletableFuture addClasspathListener(ClasspathListenerParams params); + + @JsonRequest("sts/removeClasspathListener") + CompletableFuture removeClasspathListener(ClasspathListenerParams classpathListenerParams); } 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 b360816b6..aa84b7b02 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 @@ -14,11 +14,14 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.RandomStringUtils; 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.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.commons.languageserver.util.AsyncRunner; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; @@ -28,10 +31,13 @@ import com.google.gson.JsonElement; import reactor.core.Disposable; +import static org.springframework.ide.vscode.commons.languageserver.util.AsyncRunner.*; + public class ClasspathListenerManager { + private static Logger log = LoggerFactory.getLogger(ClasspathListenerManager.class); + private static final String WORKSPACE_EXECUTE_COMMAND = "workspace/executeCommand"; - private int commandIdCounter = 0; private SimpleLanguageServer server; private AsyncRunner async; @@ -41,7 +47,8 @@ public class ClasspathListenerManager { } public Disposable addClasspathListener(ClasspathListener classpathListener) { - String callbackCommandId = "sts4.classpath." + (commandIdCounter++); + String callbackCommandId = "sts4.classpath." + RandomStringUtils.randomAlphabetic(8); + // 1. register callback command handler in SimpleLanguageServer Disposable unregisterCommand = server.onCommand(callbackCommandId, (ExecuteCommandParams callbackParams) -> async.invoke(() -> { List args = callbackParams.getArguments(); @@ -54,7 +61,9 @@ public class ClasspathListenerManager { })); // 2. call the client to ask it to call that callback - CompletableFuture future1 = server.getClient().addClasspathListener(new ClasspathListenerParams(callbackCommandId)); + CompletableFuture future1 = server.getClient().addClasspathListener( + new ClasspathListenerParams(callbackCommandId) + ); // 2. register the callback command with the client String registrationId = UUID.randomUUID().toString(); @@ -73,6 +82,7 @@ public class ClasspathListenerManager { // Cleanups: return () -> { unregisterCommand.dispose(); + thenLog(log, this.server.getClient().removeClasspathListener(new ClasspathListenerParams(callbackCommandId))); 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/jdt/ls/ClasspathListenerResponse.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerResponse.java deleted file mode 100644 index aea750923..000000000 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/jdt/ls/ClasspathListenerResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2018 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.jdt.ls; - -public class ClasspathListenerResponse { - -} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/AsyncRunner.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/AsyncRunner.java index 830f084fb..859456cf3 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/AsyncRunner.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/AsyncRunner.java @@ -37,15 +37,19 @@ public class AsyncRunner { return x; } - public synchronized void withLog(Logger logger, RunnableWithException runnable) { - execute(runnable).handle((v, e) -> { + public static void thenLog(Logger log, CompletableFuture work) { + work.handle((v, e) -> { if (e!=null) { - logger.error("", e); + log.error("", e); } return null; }); } + public synchronized void withLog(Logger logger, RunnableWithException runnable) { + thenLog(logger, execute(runnable)); + } + public synchronized CompletableFuture execute(RunnableWithException runnable) { CompletableFuture x = Mono.fromCallable(() -> { runnable.run(); diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java index 20bae53c8..98b69820c 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java @@ -98,7 +98,6 @@ import org.springframework.ide.vscode.commons.languageserver.ProjectResponse; import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; import org.springframework.ide.vscode.commons.languageserver.jdt.ls.ClasspathListenerParams; -import org.springframework.ide.vscode.commons.languageserver.jdt.ls.ClasspathListenerResponse; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit.CursorMovement; import org.springframework.ide.vscode.commons.languageserver.util.LanguageServerTestListener; import org.springframework.ide.vscode.commons.languageserver.util.Settings; @@ -308,9 +307,15 @@ public class LanguageServerHarness { } @Override - public CompletableFuture addClasspathListener( + public CompletableFuture addClasspathListener( ClasspathListenerParams params) { - return CompletableFuture.completedFuture(null); + return CompletableFuture.completedFuture("ok"); + } + + @Override + public CompletableFuture removeClasspathListener( + ClasspathListenerParams classpathListenerParams) { + return CompletableFuture.completedFuture("ok"); } }); 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 index eab5414d6..3d5a9abcc 100644 --- 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 @@ -21,10 +21,12 @@ public class ClasspathListenerHandler implements IDelegateCommandHandler { private List subscribers = new ArrayList<>(1); public synchronized void subscribe(String callbackCommandId) { + Logger.log("subscribing to classpath changes: "+callbackCommandId); if (manager==null) { this.manager = new ClasspathListenerManager(this); } subscribers.add(callbackCommandId); + Logger.log("subsribers = "+subscribers); } @Override @@ -37,6 +39,21 @@ public class ClasspathListenerHandler implements IDelegateCommandHandler { conn.executeCommand(callbackCommandId, project, deleted); } } + + public synchronized void unsubscribe(String callbackCommandId) { + Logger.log("unsubscribing from classpath changes: "+callbackCommandId); + if (subscribers!=null) { + subscribers.remove(callbackCommandId); + if (subscribers.isEmpty()) { + subscribers = null; + if (manager!=null) { + manager.dispose(); + manager = null; + } + } + } + Logger.log("subsribers = "+subscribers); + } } private static MyClasspathListener classpathListener = new MyClasspathListener(); @@ -44,10 +61,21 @@ public class ClasspathListenerHandler implements IDelegateCommandHandler { @Override public Object executeCommand(String commandId, List arguments, IProgressMonitor monitor) throws Exception { log("ClasspathListenerHandler executeCommand "+commandId+ ", "+arguments); - if (commandId.equals("sts.java.addClasspathListener")) { + switch (commandId) { + case "sts.java.addClasspathListener": return addClasspathListener((String)arguments.get(0)); + case "sts.java.removeClasspathListener": + return removeClasspathListener((String)arguments.get(0)); + default: + throw new IllegalArgumentException("Unknown command id: "+commandId); } - throw new IllegalArgumentException("Unknown command id: "+commandId); + } + + private Object removeClasspathListener(String callbackCommandId) { + log("ClasspathListenerHandler addClasspathListener "+callbackCommandId); + classpathListener.unsubscribe(callbackCommandId); + log("ClasspathListenerHandler addClasspathListener "+callbackCommandId+ " => OK"); + return "ok"; } private Object addClasspathListener(String callbackCommandId) { 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 34168521e..007e46c98 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 @@ -60,7 +60,9 @@ public class JdtLsProjectCache implements JavaProjectFinder, ProjectObserver { } })) ); - this.server.onShutdown(() -> disposable.thenAccept(Disposable::dispose)); + this.server.onShutdown(() -> + disposable.thenAccept(Disposable::dispose) + ); } @Override