Support add/remove classpath listener at runtime

This commit is contained in:
BoykoAlex
2021-06-03 11:00:27 -04:00
parent 9df407543c
commit 94dd78b2fc
4 changed files with 146 additions and 69 deletions

View File

@@ -84,7 +84,6 @@ import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -418,12 +417,12 @@ public final class SimpleLanguageServer implements Sts4LanguageServer, LanguageC
hasQuickFixes() ||
!commands.isEmpty()
)) {
ImmutableList.Builder<String> supportedCommands = ImmutableList.builder();
List<String> supportedCommands = new ArrayList<>();
if (hasQuickFixes()) {
supportedCommands.add(CODE_ACTION_COMMAND_ID);
}
supportedCommands.addAll(commands.keySet());
c.setExecuteCommandProvider(new ExecuteCommandOptions(supportedCommands.build()));
c.setExecuteCommandProvider(new ExecuteCommandOptions(supportedCommands));
}
if (hasWorkspaceSymbolHandler()) {
c.setWorkspaceSymbolProvider(true);

View File

@@ -24,7 +24,10 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,16 +38,23 @@ import org.springframework.ide.vscode.commons.java.JavaProject;
import org.springframework.ide.vscode.commons.java.JdtLsJavaProject;
import org.springframework.ide.vscode.commons.javadoc.JdtLsJavadocProvider;
import org.springframework.ide.vscode.commons.languageserver.java.ls.ClasspathListener;
import org.springframework.ide.vscode.commons.languageserver.util.ServerCapabilityInitializer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.protocol.java.Classpath.CPE;
import org.springframework.ide.vscode.commons.util.FileObserver;
import org.springframework.ide.vscode.commons.util.UriUtil;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import reactor.core.Disposable;
import reactor.core.Disposables;
import reactor.core.publisher.Mono;
public class JdtLsProjectCache implements InitializableJavaProjectsService {
public class JdtLsProjectCache implements InitializableJavaProjectsService, ServerCapabilityInitializer {
private static final String CMD_SPRING_BOOT_ENABLE_CLASSPATH_LISTENING = "sts.vscode-spring-boot.enableClasspathListening";
private static final Duration INITIALIZE_TIMEOUT = Duration.ofSeconds(10);
private static final Object JDT_SCHEME = "jdt";
@@ -55,11 +65,20 @@ public class JdtLsProjectCache implements InitializableJavaProjectsService {
private Logger log = LoggerFactory.getLogger(JdtLsProjectCache.class);
private List<Listener> listeners = new ArrayList<>();
private boolean projectWatchingSupported = true;
final private ClasspathListener CLASSPATH_LISTENER = new JstLsClasspathListener();
final private Disposable.Swap DISPOSABLE = Disposables.swap();
private Mono<Disposable> classpathListenerRequest;
private boolean classpathListenerEnabled;
private boolean initialClasspathLisetnerEnable;
public JdtLsProjectCache(SimpleLanguageServer server, boolean isJandexIndex) {
this.server = server;
this.IS_JANDEX_INDEX = isJandexIndex;
this.initialClasspathLisetnerEnable = true;
this.server
.onInitialized(initialize())
.doOnSuccess((disposable) -> {
@@ -69,7 +88,7 @@ public class JdtLsProjectCache implements InitializableJavaProjectsService {
})
.doOnError(error -> {
log.error("JDT-based JavaProject service not available!", error);
switchOffProjectObserver();
enableClasspathListener(false);
})
.toFuture();
}
@@ -133,7 +152,7 @@ public class JdtLsProjectCache implements InitializableJavaProjectsService {
}
private void notifyProjectObserverSupported() {
log.info("Project Observer is " + (projectWatchingSupported ? "" : "not ") + "supported");
log.info("Project Observer is " + (classpathListenerEnabled ? "" : "not ") + "supported");
synchronized (listeners) {
for (Listener listener : listeners) {
listener.supported();
@@ -230,61 +249,44 @@ public class JdtLsProjectCache implements InitializableJavaProjectsService {
@Override
public Mono<Disposable> initialize() {
return Mono.defer(() -> server.addClasspathListener(new ClasspathListener() {
@Override
public void changed(Event event) {
log.debug("claspath event received {}", event);
server.doOnInitialized(() -> {
//log.info("initialized.thenRun block entered");
try {
synchronized (table) {
String uri = UriUtil.normalize(event.projectUri);
log.debug("uri = {}", uri);
if (event.deleted) {
log.debug("event.deleted = true");
IJavaProject deleted = table.remove(uri);
if (deleted!=null) {
log.debug("removed from table = true");
notifyDelete(deleted);
} else {
log.warn("Deleted project not removed because uri {} not found in {}", uri, table.keySet());
}
} else {
log.debug("deleted = false");
URI projectUri = new URI(uri);
ClasspathData classpath = new ClasspathData(event.name, event.classpath.getEntries());
IJavaProject newProject = IS_JANDEX_INDEX
? new JavaProject(getFileObserver(), projectUri, classpath,
JdtLsProjectCache.this)
: new JdtLsJavaProject(server.getClient(), projectUri, classpath, JdtLsProjectCache.this);
IJavaProject oldProject = table.put(uri, newProject);
if (oldProject != null) {
notifyChanged(newProject);
} else {
notifyCreated(newProject);
}
}
}
} catch (Exception e) {
log.error("", e);
}
});
}
})
.timeout(INITIALIZE_TIMEOUT)
.doOnSubscribe(x -> log.info("addClasspathListener ..."))
.doOnSuccess(x -> log.info("addClasspathListener DONE"))
.doOnError(t -> {
log.error("Unexpected error registering classpath listener with JDT.", t);
switchOffProjectObserver();
}));
return Mono.defer(() -> {
log.debug("ADD CLASSPATH LISTENER enableClasspath=" + initialClasspathLisetnerEnable);
enableClasspathListener(initialClasspathLisetnerEnable);
return Mono.just(DISPOSABLE);
});
}
private void switchOffProjectObserver() {
projectWatchingSupported = false;
notifyProjectObserverSupported();
private synchronized void enableClasspathListener(boolean enabled) {
if (classpathListenerEnabled != enabled) {
if (enabled) {
log.debug("Adding classpath listener enabled=" + enabled);
classpathListenerEnabled = true;
notifyProjectObserverSupported();
classpathListenerRequest = server.addClasspathListener(CLASSPATH_LISTENER).timeout(INITIALIZE_TIMEOUT)
.doOnSubscribe(x -> log.info("addClasspathListener ..."))
.doOnSuccess(x -> log.info("addClasspathListener DONE"))
.doOnError(t -> {
log.error("Unexpected error registering classpath listener with JDT.", t);
enableClasspathListener(false);
});
final Mono<Disposable> oldClasspathSubscription = classpathListenerRequest;
classpathListenerRequest.subscribe(d -> {
if (oldClasspathSubscription != classpathListenerRequest) {
d.dispose();
} else {
DISPOSABLE.update(d);
}
});
} else {
log.debug("Removing classpath listener enabled=" + enabled);
DISPOSABLE.update(Disposables.single());
classpathListenerRequest = null;
classpathListenerEnabled = false;
notifyProjectObserverSupported();
}
}
}
@Override
public Collection<? extends IJavaProject> all() {
return table.values();
@@ -292,7 +294,75 @@ public class JdtLsProjectCache implements InitializableJavaProjectsService {
@Override
public boolean isSupported() {
return projectWatchingSupported;
return classpathListenerEnabled;
}
@Override
public void initialize(InitializeParams p, ServerCapabilities cap) {
server.onCommand(CMD_SPRING_BOOT_ENABLE_CLASSPATH_LISTENING, params -> {
log.debug("CLASSPATH ENABLED CMD EXEC");
if (params.getArguments().get(0) instanceof JsonPrimitive) {
boolean classpathListeningEnabled = ((JsonPrimitive)params.getArguments().get(0)).getAsBoolean();
log.debug("Enable classpath listening: " + classpathListeningEnabled);
enableClasspathListener(classpathListeningEnabled);
}
return CompletableFuture.completedFuture(null);
});
log.debug("REGISTER ENABLE CLASSPATH CMD");
JsonObject o = (JsonObject) p.getInitializationOptions();
if (o != null) {
JsonPrimitive enable = o.getAsJsonPrimitive("enableJdtClasspath");
if (enable != null) {
log.debug("READING INIT VALUE for classpathEnabled=" + enable.getAsBoolean());
initialClasspathLisetnerEnable = enable.getAsBoolean();
}
}
log.debug("INIT VALUE for classpathEnabled=" + initialClasspathLisetnerEnable);
cap.getExecuteCommandProvider().getCommands().add(CMD_SPRING_BOOT_ENABLE_CLASSPATH_LISTENING);
}
private class JstLsClasspathListener implements ClasspathListener {
@Override
public void changed(Event event) {
log.debug("claspath event received {}", event);
server.doOnInitialized(() -> {
//log.info("initialized.thenRun block entered");
try {
synchronized (table) {
String uri = UriUtil.normalize(event.projectUri);
log.debug("uri = {}", uri);
if (event.deleted) {
log.debug("event.deleted = true");
IJavaProject deleted = table.remove(uri);
if (deleted!=null) {
log.debug("removed from table = true");
notifyDelete(deleted);
} else {
log.warn("Deleted project not removed because uri {} not found in {}", uri, table.keySet());
}
} else {
log.debug("deleted = false");
URI projectUri = new URI(uri);
ClasspathData classpath = new ClasspathData(event.name, event.classpath.getEntries());
IJavaProject newProject = IS_JANDEX_INDEX
? new JavaProject(getFileObserver(), projectUri, classpath,
JdtLsProjectCache.this)
: new JdtLsJavaProject(server.getClient(), projectUri, classpath, JdtLsProjectCache.this);
IJavaProject oldProject = table.put(uri, newProject);
if (oldProject != null) {
notifyChanged(newProject);
} else {
notifyCreated(newProject);
}
}
}
} catch (Exception e) {
log.error("", e);
}
});
}
}
}

View File

@@ -1,18 +1,25 @@
'use strict';
import * as VSCode from 'vscode';
import { RequestType } from 'vscode-languageclient';
import { LanguageClient } from 'vscode-languageclient/node';
import { workspace } from 'vscode';
const JDT_SERVER_STANDARD_MODE = 'Standard'
const CMD_STS_ENABLE_CLASSPATH_LISTENER = 'sts.vscode-spring-boot.enableClasspathListening';
export function registerClasspathService(client : LanguageClient) : void {
const javaExt = VSCode.extensions.getExtension('redhat.java');
const javaApi = javaExt?.exports;
if (javaApi) {
VSCode.commands.executeCommand(CMD_STS_ENABLE_CLASSPATH_LISTENER, javaApi.serverMode === JDT_SERVER_STANDARD_MODE);
javaApi.onDidServerModeChange(e => VSCode.commands.executeCommand(CMD_STS_ENABLE_CLASSPATH_LISTENER, javaApi.serverMode === JDT_SERVER_STANDARD_MODE));
}
let addRequest = new RequestType<ClasspathListenerParams, ClasspathListenerResponse, void>("sts/addClasspathListener");
client.onRequest(addRequest, async (params: ClasspathListenerParams) => {
const jdtConfig = workspace.getConfiguration('java')
const launchMode = jdtConfig?.get('server.launchMode');
if (launchMode === 'LightWeight') {
if (javaApi?.serverMode === 'LightWeight') {
throw new Error('Classpath listener not supported while Java Language Server is in LightWeight mode');
}
return <ClasspathListenerResponse> await VSCode.commands.executeCommand("java.execute.workspaceCommand", "sts.java.addClasspathListener", params.callbackCommandId);

View File

@@ -70,9 +70,10 @@ export function activate(context: VSCode.ExtensionContext): Thenable<LanguageCli
synchronize: {
configurationSection: 'boot-java'
},
initializationOptions: {
workspaceFolders: workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.toString()) : null
}
initializationOptions: () => ({
workspaceFolders: workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.toString()) : null,
enableJdtClasspath: VSCode.extensions.getExtension('redhat.java')?.exports?.serverMode === 'Standard'
})
},
highlightCodeLensSettingKey: 'boot-java.highlight-codelens.on'
};