diff --git a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java index ecec52c13..f25b587e9 100644 --- a/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java +++ b/eclipse-language-servers/org.springframework.tooling.ls.eclipse.commons/src/org/springframework/tooling/ls/eclipse/commons/STS4LanguageClientImpl.java @@ -17,10 +17,14 @@ import java.net.URI; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; @@ -58,6 +62,10 @@ import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorPart; +import org.eclipse.ui.IEditorReference; +import org.eclipse.ui.IFileEditorInput; +import org.eclipse.ui.IWorkbenchPage; +import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.progress.UIJob; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor; @@ -358,6 +366,31 @@ public class STS4LanguageClientImpl extends LanguageClientImpl implements STS4La } } + public STS4LanguageClientImpl() { + classpathService.addNotificationsSentCallback(projectNames -> { + List projects = projectNames.stream().map(projectName -> ResourcesPlugin.getWorkspace().getRoot().getProject(projectName)).filter(Objects::nonNull).collect(Collectors.toList()); + for (IWorkbenchWindow ww : PlatformUI.getWorkbench().getWorkbenchWindows()) { + for (IWorkbenchPage page : ww.getPages()) { + for (IEditorReference editorRef : page.getEditorReferences()) { + IEditorPart editor = editorRef.getEditor(false); + if (editor != null) { + if (editor.getEditorInput() instanceof IFileEditorInput) { + IFile file = ((IFileEditorInput)editor.getEditorInput()).getFile(); + if (file != null && projects.contains(file.getProject())) { + ITextViewer viewer = editor.getAdapter(ITextViewer.class); + if (viewer instanceof ISourceViewerExtension5) { + ((ISourceViewerExtension5)viewer).updateCodeMinings(); + } + } + } + } + } + } + } + + }); + } + @Override public CompletableFuture addClasspathListener(ClasspathListenerParams params) { return CompletableFuture.completedFuture(classpathService.addClasspathListener(params.getCallbackCommandId(), params.isBatched())); diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java index d67a1d366..3ef95a2ba 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/ReusableClasspathListenerHandler.java @@ -15,14 +15,22 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.function.Supplier; -import javax.management.Notification; - import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.IJobChangeEvent; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.JavaCore; import org.springframework.tooling.jdt.ls.commons.Logger; @@ -32,10 +40,15 @@ import org.springframework.tooling.jdt.ls.commons.classpath.ClasspathListenerMan * {@link ReusableClasspathListenerHandler} is an 'abstracted' version of the jdtls ClasspathListenerHandler. */ public class ReusableClasspathListenerHandler { + + public interface NotificationSentCallback { + void sent(Collection projectNames); + } private final Logger logger; private final ClientCommandExecutor conn; private final Supplier> projectSorterFactory; + private final ListenerList notificationsSentCallbacks; public ReusableClasspathListenerHandler(Logger logger, ClientCommandExecutor conn) { this(logger, conn, null); @@ -45,13 +58,40 @@ public class ReusableClasspathListenerHandler { this.logger = logger; this.projectSorterFactory = projectSorterFactory; this.conn = conn; + this.notificationsSentCallbacks = new ListenerList<>(); logger.log("Instantiating ReusableClasspathListenerHandler"); } + private class CallbackJob extends Job { + + public CallbackJob() { + super("Classpath Notiifcation Post Processing"); + setSystem(true); + } + + private Set projectNames = Collections.synchronizedSet(new HashSet<>()); + + @Override + protected IStatus run(IProgressMonitor monitor) { + if (!projectNames.isEmpty()) { + notificationsSentCallbacks.forEach(callback -> callback.sent(projectNames)); + } + return Status.OK_STATUS; + } + + void queueProjects(Collection projectNames) { + this.projectNames.addAll(projectNames); + schedule(); + } + + } + class Subscriptions { private Map subscribers = null; private ClasspathListenerManager classpathListener = null; + private CallbackJob callbackJob = new CallbackJob(); + public synchronized void subscribe(String callbackCommandId, boolean isBatched) { if (subscribers==null) { @@ -72,7 +112,19 @@ public class ReusableClasspathListenerHandler { } }); - subscribers.put(callbackCommandId, new SendClasspathNotificationsJob(logger, conn, callbackCommandId, isBatched)); + final SendClasspathNotificationsJob job = new SendClasspathNotificationsJob(logger, conn, callbackCommandId, isBatched); + subscribers.put(callbackCommandId, job); + job.addJobChangeListener(new JobChangeAdapter() { + + @Override + public void done(IJobChangeEvent event) { + List projectNames = job.notificationsSentForProjects; + if (projectNames != null) { + callbackJob.queueProjects(projectNames); + } + } + + }); logger.log("subsribers = " + subscribers); sendInitialEvents(callbackCommandId); } @@ -167,4 +219,11 @@ public class ReusableClasspathListenerHandler { return subscribptions.isEmpty(); } + public void addNotificationsSentCallback(NotificationSentCallback callback) { + notificationsSentCallbacks.add(callback); + } + + public void removeNotificationsSentCallback(NotificationSentCallback callback) { + notificationsSentCallbacks.remove(callback); + } } diff --git a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/SendClasspathNotificationsJob.java b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/SendClasspathNotificationsJob.java index 61a86f6b5..e1277356b 100644 --- a/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/SendClasspathNotificationsJob.java +++ b/headless-services/jdt-ls-extension/org.springframework.tooling.jdt.ls.commons/src/org/springframework/tooling/jdt/ls/commons/classpath/SendClasspathNotificationsJob.java @@ -20,6 +20,7 @@ import java.util.Map; import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.stream.Collectors; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; @@ -39,6 +40,8 @@ public class SendClasspathNotificationsJob extends Job { private final Logger logger; private String callbackCommandId; + List notificationsSentForProjects; + /** * Used only if caller has requested 'batched' events. This buffer, accumulates messsages to be sent out all at once, * rather than one by one. @@ -103,6 +106,7 @@ public class SendClasspathNotificationsJob extends Job { @Override protected IStatus run(IProgressMonitor monitor) { + notificationsSentForProjects = null; synchronized (projectLocations) { //Could use some Eclipse job rule. But its really a bit of a PITA to create the right one. try { // Try to see if classpath needs to be sent for the projects that have been @@ -177,6 +181,7 @@ public class SendClasspathNotificationsJob extends Job { try { logger.log("executing callback "+callbackCommandId+" "+projectName+" "+deleted+" "+ classpath.getEntries().size()); Object r = conn.executeClientCommand(callbackCommandId, projectLoc.toString(), projectName, deleted, classpath); + notificationsSentForProjects = ImmutableList.of(projectName); logger.log("executing callback "+callbackCommandId+" SUCCESS ["+r+"]"); } catch (Exception e) { logger.log("executing callback "+callbackCommandId+" FAILED"); @@ -190,6 +195,8 @@ public class SendClasspathNotificationsJob extends Job { try { logger.log("executing callback "+callbackCommandId+" "+buffer.size()+" batched events"); Object r = conn.executeClientCommand(callbackCommandId, buffer.toArray(new Object[buffer.size()])); + notificationsSentForProjects = ImmutableList.copyOf(buffer.stream().filter(l -> l instanceof List) + .map(l -> (List) l).map(l -> (String) l.get(1)).collect(Collectors.toList())); logger.log("executing callback "+callbackCommandId+" SUCCESS ["+r+"]"); } catch (Exception e) { logger.log("executing callback "+callbackCommandId+" FAILED");