diff --git a/.github/workflows/IJ.yml b/.github/workflows/IJ.yml
index 759fba1d0..0da0da0d4 100644
--- a/.github/workflows/IJ.yml
+++ b/.github/workflows/IJ.yml
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- IJ: [IC-2020.3.1, IC-2021.1, IC-2021.2, IC-2021.3, IC-2022.1, IC-2022.2, IC-2022.3, IC-2023.1]
+ IJ: [IC-2021.3, IC-2022.1, IC-2022.2, IC-2022.3, IC-2023.1]
steps:
- uses: actions/checkout@v2
diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java
index 5d6107144..9badb96ee 100644
--- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java
+++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java
@@ -1,126 +1,154 @@
/*******************************************************************************
-* Copyright (c) 2020 Red Hat Inc. and others.
-* All rights reserved. This program and the accompanying materials
-* which accompanies this distribution, and is available at
-* https://www.eclipse.org/legal/epl-v20.html
-*
-* Contributors:
-* Red Hat Inc. - initial API and implementation
-*******************************************************************************/
+ * Copyright (c) 2020 Red Hat Inc. and others.
+ * All rights reserved. This program and the accompanying materials
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
package com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project;
import com.intellij.ProjectTopics;
-import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.Service;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.ModuleListener;
import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
-import com.intellij.openapi.vfs.VirtualFileManager;
-import com.intellij.openapi.vfs.newvfs.BulkFileListener;
-import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
-import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
-import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent;
-import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.util.messages.MessageBusConnection;
-import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.core.ls.PsiUtilsLSImpl;
+import com.redhat.devtools.intellij.quarkus.classpath.ClasspathResourceChangedManager;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* {@link PsiMicroProfileProject} manager.
- *
+ *
* @author Angelo ZERR
* @see https://github.com/redhat-developer/quarkus-ls/blob/master/microprofile.jdt/com.redhat.microprofile.jdt.core/src/main/java/com/redhat/microprofile/jdt/core/project/JDTMicroProfileProjectManager.java
- *
*/
@Service
-public final class PsiMicroProfileProjectManager {
-
- public static PsiMicroProfileProjectManager getInstance(Project project) {
- return ServiceManager.getService(project, PsiMicroProfileProjectManager.class);
- }
-
- private Project project;
-
- private final Map projects;
- private MicroProfileProjectListener microprofileProjectListener;
-
- private class MicroProfileProjectListener implements ModuleListener, BulkFileListener {
- @Override
- public void after(@NotNull List extends VFileEvent> events) {
- for(VFileEvent event : events) {
- if ((event instanceof VFileDeleteEvent || event instanceof VFileContentChangeEvent ||
- event instanceof VFileCreateEvent) && isConfigSource(event.getFile())) {
- Module javaProject = PsiUtilsLSImpl.getInstance(project).getModule(event.getFile());
- if (javaProject != null) {
- PsiMicroProfileProject mpProject = getJDTMicroProfileProject(javaProject);
- if (mpProject != null) {
- mpProject.evictConfigSourcesCache();
- }
- }
-
- }
- }
- }
-
- @Override
- public void beforeModuleRemoved(@NotNull Project project, @NotNull Module module) {
- evict(module);
- }
-
- private void evict(Module javaProject) {
- if (javaProject != null) {
- // Remove the JDTMicroProfile project instance from the cache.
- projects.remove(javaProject);
- }
- }
- }
-
- private PsiMicroProfileProjectManager(Project project) {
- this.project = project;
- this.projects = new HashMap<>();
- initialize();
- }
-
- public PsiMicroProfileProject getJDTMicroProfileProject(Module project) {
- return getJDTMicroProfileProject(project, true);
- }
-
- private PsiMicroProfileProject getJDTMicroProfileProject(Module project, boolean create) {
- Module javaProject = project;
- PsiMicroProfileProject info = projects.get(javaProject);
- if (info == null) {
- if (!create) {
- return null;
- }
- info = new PsiMicroProfileProject(javaProject);
- projects.put(javaProject, info);
- }
- return info;
- }
-
- public boolean isConfigSource(VirtualFile file) {
- String fileName = file.getName();
- for (IConfigSourceProvider provider : IConfigSourceProvider.EP_NAME.getExtensions()) {
- if (provider.isConfigSource(fileName)) {
- return true;
- }
- }
- return false;
- }
-
- public void initialize() {
- if (microprofileProjectListener != null) {
- return;
- }
- microprofileProjectListener = new MicroProfileProjectListener();
- MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect(project);
- connection.subscribe(VirtualFileManager.VFS_CHANGES, microprofileProjectListener);
- project.getMessageBus().connect(project).subscribe(ProjectTopics.MODULES, microprofileProjectListener);
- }
+public final class PsiMicroProfileProjectManager implements Disposable {
+
+ private static final String JAVA_FILE_EXTENSION = "java";
+
+ private MessageBusConnection connection;
+
+ public static PsiMicroProfileProjectManager getInstance(Project project) {
+ return ServiceManager.getService(project, PsiMicroProfileProjectManager.class);
+ }
+
+ private Project project;
+
+ private final Map projects;
+ private MicroProfileProjectListener microprofileProjectListener;
+
+ private class MicroProfileProjectListener implements ModuleListener, ClasspathResourceChangedManager.Listener {
+
+ @Override
+ public void librariesChanged() {
+ // Do nothing
+ }
+
+ @Override
+ public void sourceFilesChanged(Set> sources) {
+ for (var pair : sources) {
+ VirtualFile file = pair.getFirst();
+ if (isConfigSource(file)) {
+ // A microprofile config file properties file source has been updated, evict the cache of the properties
+ Module javaProject = pair.getSecond();
+ PsiMicroProfileProject mpProject = getJDTMicroProfileProject(javaProject);
+ if (mpProject != null) {
+ mpProject.evictConfigSourcesCache();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void beforeModuleRemoved(@NotNull Project project, @NotNull Module module) {
+ evict(module);
+ }
+
+ private void evict(Module javaProject) {
+ if (javaProject != null) {
+ // Remove the JDTMicroProfile project instance from the cache.
+ projects.remove(javaProject);
+ }
+ }
+ }
+
+ private PsiMicroProfileProjectManager(Project project) {
+ this.project = project;
+ this.projects = new HashMap<>();
+ initialize();
+ }
+
+ public PsiMicroProfileProject getJDTMicroProfileProject(Module project) {
+ return getJDTMicroProfileProject(project, true);
+ }
+
+ private PsiMicroProfileProject getJDTMicroProfileProject(Module project, boolean create) {
+ Module javaProject = project;
+ PsiMicroProfileProject info = projects.get(javaProject);
+ if (info == null) {
+ if (!create) {
+ return null;
+ }
+ info = new PsiMicroProfileProject(javaProject);
+ projects.put(javaProject, info);
+ }
+ return info;
+ }
+
+ /**
+ * Returns true if the given file is a MicroProfile config properties file (microprofile-config.properties, application.properties, application.yaml, etc) and false otherwise.
+ *
+ * @param file the file to check.
+ * @return true if the given file is a MicroProfile config properties file (microprofile-config.properties, application.properties, application.yaml, etc) and false otherwise.
+ */
+ public boolean isConfigSource(VirtualFile file) {
+ if (file == null) {
+ return false;
+ }
+ String fileName = file.getName();
+ for (IConfigSourceProvider provider : IConfigSourceProvider.EP_NAME.getExtensions()) {
+ if (provider.isConfigSource(fileName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns true if the given file is a Java file and false otherwise.
+ *
+ * @param file the file to check.
+ * @return true if the given file is a Java file and false otherwise.
+ */
+ public boolean isJavaFile(VirtualFile file) {
+ return file != null && JAVA_FILE_EXTENSION.equals(file.getExtension());
+ }
+
+ public void initialize() {
+ if (microprofileProjectListener != null) {
+ return;
+ }
+ connection = project.getMessageBus().connect(project);
+ microprofileProjectListener = new MicroProfileProjectListener();
+ connection.subscribe(ClasspathResourceChangedManager.TOPIC, microprofileProjectListener);
+ connection.subscribe(ProjectTopics.MODULES, microprofileProjectListener);
+ }
+
+ @Override
+ public void dispose() {
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
}
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java
index 38e3c0b82..713786427 100644
--- a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusPostStartupActivity.java
@@ -1,25 +1,25 @@
-/*******************************************************************************
- * Copyright (c) 2019-2020 Red Hat, Inc.
- * Distributed under license by Red Hat, Inc. All rights reserved.
- * This program is made available under the terms of the
- * Eclipse Public License v2.0 which accompanies this distribution,
- * and is available at https://www.eclipse.org/legal/epl-v20.html
- *
- * Contributors:
- * Red Hat, Inc. - initial API and implementation
- ******************************************************************************/
-package com.redhat.devtools.intellij.quarkus;
-
-import com.intellij.openapi.module.Module;
-import com.intellij.openapi.module.ModuleManager;
-import com.intellij.openapi.project.DumbAware;
-import com.intellij.openapi.project.Project;
-import com.intellij.openapi.startup.StartupActivity;
-import org.jetbrains.annotations.NotNull;
-
-public class QuarkusPostStartupActivity implements StartupActivity, DumbAware {
- @Override
- public void runActivity(@NotNull Project project) {
- QuarkusProjectService.getInstance(project);
- }
-}
+/*******************************************************************************
+ * Copyright (c) 2019-2020 Red Hat, Inc.
+ * Distributed under license by Red Hat, Inc. All rights reserved.
+ * This program is made available under the terms of the
+ * Eclipse Public License v2.0 which accompanies this distribution,
+ * and is available at https://www.eclipse.org/legal/epl-v20.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ ******************************************************************************/
+package com.redhat.devtools.intellij.quarkus;
+
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.startup.StartupActivity;
+import com.redhat.devtools.intellij.quarkus.classpath.ClasspathResourceChangedManager;
+import org.jetbrains.annotations.NotNull;
+
+public class QuarkusPostStartupActivity implements StartupActivity, DumbAware {
+ @Override
+ public void runActivity(@NotNull Project project) {
+ ClasspathResourceChangedManager.getInstance(project);
+ QuarkusProjectService.getInstance(project);
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusProjectService.java b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusProjectService.java
index a32ec7cfc..10b980e05 100644
--- a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusProjectService.java
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusProjectService.java
@@ -37,7 +37,6 @@
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.ConcurrencyUtil;
import com.intellij.util.messages.MessageBusConnection;
-import com.intellij.util.messages.Topic;
import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.PropertiesManager;
import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project.PsiMicroProfileProjectManager;
import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.core.ls.PsiUtilsLSImpl;
@@ -53,17 +52,8 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
+import java.util.*;
+import java.util.concurrent.*;
import java.util.stream.Collectors;
public class QuarkusProjectService implements LibraryTable.Listener, BulkFileListener, ModuleListener, Disposable {
@@ -77,23 +67,10 @@ public class QuarkusProjectService implements LibraryTable.Listener, BulkFileLis
private Set modulesBeingEnsured = new HashSet<>();
- @Override
- public void dispose() {
- connection.disconnect();
- executor.shutdown();
- }
-
- public interface Listener {
- void libraryUpdated(Library library);
- void sourceUpdated(List> sources);
- }
-
public static QuarkusProjectService getInstance(Project project) {
return ServiceManager.getService(project, QuarkusProjectService.class);
}
- public static final Topic TOPIC = Topic.create(QuarkusProjectService.class.getName(), Listener.class);
-
private final MessageBusConnection connection;
public QuarkusProjectService(Project project) {
@@ -106,9 +83,9 @@ public QuarkusProjectService(Project project) {
r -> new Thread(r, "Quarkus lib pool " + project.getName()));
}
LibraryTablesRegistrar.getInstance().getLibraryTable(project).addListener(this, project);
- connection = ApplicationManager.getApplication().getMessageBus().connect(project);
+ connection = project.getMessageBus().connect();
connection.subscribe(VirtualFileManager.VFS_CHANGES, this);
- project.getMessageBus().connect().subscribe(ProjectTopics.MODULES, this);
+ connection.subscribe(ProjectTopics.MODULES, this);
processModules();
}
@@ -153,7 +130,6 @@ private void handleLibraryUpdate(Library library) {
});
} else {
processModules().thenRun(() -> {
- project.getMessageBus().syncPublisher(TOPIC).libraryUpdated(library);
schemas.forEach((module, pair) -> {
pair.setRight(Boolean.FALSE);
});
@@ -182,7 +158,6 @@ public void after(@NotNull List extends VFileEvent> events) {
p.setRight(Boolean.FALSE);
return p;
}));
- project.getMessageBus().syncPublisher(TOPIC).sourceUpdated(pairs);
}
}
@@ -200,15 +175,15 @@ private Pair toPair(VFileEvent event) {
if (module == null || module.isDisposed()) {
return null;
}
- if (!isJavaFile(file) && !isConfigSource(file, project)) {
+ if (!isJavaFile(file, project) && !isConfigSource(file, project)) {
return null;
}
// Here a java file or a microprofile-config.properties, application.properties has been changed
return Pair.of(module, file);
}
- private static boolean isJavaFile(VirtualFile file) {
- return "java".equalsIgnoreCase(file.getExtension());
+ private static boolean isJavaFile(VirtualFile file, Project project) {
+ return PsiMicroProfileProjectManager.getInstance(project).isJavaFile(file);
}
private static boolean isConfigSource(VirtualFile file, Project project) {
@@ -218,7 +193,7 @@ private static boolean isConfigSource(VirtualFile file, Project project) {
public VirtualFile getSchema(Module module) {
MutablePair schemaEntry = schemas.get(module);
if (schemaEntry == null || !schemaEntry.getRight()) {
- VirtualFile file = computeSchema(module, schemaEntry!=null?schemaEntry.getLeft():null);
+ VirtualFile file = computeSchema(module, schemaEntry != null ? schemaEntry.getLeft() : null);
if (file != null) {
if (schemaEntry != null) {
schemaEntry.setRight(Boolean.TRUE);
@@ -228,7 +203,7 @@ public VirtualFile getSchema(Module module) {
}
}
}
- return schemaEntry!=null?schemaEntry.getLeft():null;
+ return schemaEntry != null ? schemaEntry.getLeft() : null;
}
private static VirtualFile createJSONSchemaFile(String name) throws IOException {
@@ -256,7 +231,7 @@ private VirtualFile computeSchema(Module module, VirtualFile schemaFile) {
});
});
return schemaFile;
- } catch (IOException| ProcessCanceledException e) {
+ } catch (IOException | ProcessCanceledException e) {
LOGGER.warn(e.getLocalizedMessage(), e);
}
return null;
@@ -276,4 +251,13 @@ public void moduleAdded(@NotNull Project project, @NotNull Module module) {
public void moduleRemoved(@NotNull Project project, @NotNull Module module) {
moduleChanged(module);
}
+
+
+ @Override
+ public void dispose() {
+ LibraryTablesRegistrar.getInstance().getLibraryTable(project).removeListener(this);
+ connection.disconnect();
+ executor.shutdown();
+ }
+
}
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/classpath/ClasspathResourceChangedManager.java b/src/main/java/com/redhat/devtools/intellij/quarkus/classpath/ClasspathResourceChangedManager.java
new file mode 100644
index 000000000..fc2a58c2e
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/classpath/ClasspathResourceChangedManager.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package com.redhat.devtools.intellij.quarkus.classpath;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.psi.PsiManager;
+import com.intellij.util.messages.MessageBusConnection;
+import com.intellij.util.messages.Topic;
+import com.intellij.workspaceModel.ide.WorkspaceModelTopics;
+
+import java.util.Set;
+
+/**
+ * Classpath resource change manager provides the capability to track update of libraries changed and Java, microprofile-config properties files
+ * by any component by registering a listener {@link Listener}.
+ *
+ *
+ * ClasspathResourceChangeManager.Listener myListener = ...
+ * project.getMessageBus().connect(project).subscribe(ClasspathResourceChangeManager.TOPIC, myListener);
+ *
+ *
+ *
+ *
+ * - Track update of libraries is done with {@link WorkspaceModelTopics}.
+ * In other words {@link Listener#librariesChanged()} are fired when all libraries are inserted, deleted, updated.
+ * - Track update of Java, microprofile-config properties files are done when Java Psi file is updated, when Java file is created, deleted, saved.
+ *
+ */
+public class ClasspathResourceChangedManager implements Disposable {
+
+ public static final Topic TOPIC = Topic.create(ClasspathResourceChangedManager.class.getName(), ClasspathResourceChangedManager.Listener.class);
+
+ private final SourceFileChangeNotifier sourcesChanged;
+ private final MessageBusConnection connection;
+ private final ClasspathRessourceChangedListener listener;
+
+ public static ClasspathResourceChangedManager getInstance(Project project) {
+ return ServiceManager.getService(project, ClasspathResourceChangedManager.class);
+ }
+
+ public interface Listener {
+
+ void librariesChanged();
+
+ void sourceFilesChanged(Set> sources);
+ }
+
+ private final Project project;
+
+ public ClasspathResourceChangedManager(Project project) {
+ this.project = project;
+ // Send source files changed in debounce mode
+ this.sourcesChanged = new SourceFileChangeNotifier(files ->
+ project.getMessageBus().syncPublisher(ClasspathResourceChangedManager.TOPIC).sourceFilesChanged(files)
+ );
+ listener = new ClasspathRessourceChangedListener(project, sourcesChanged);
+ connection = project.getMessageBus().connect();
+ // Track end of Java libraries update
+ WorkspaceModelTopics.getInstance(project).subscribeAfterModuleLoading(connection, listener);
+ // Track delete, create, update of file
+ connection.subscribe(VirtualFileManager.VFS_CHANGES, listener);
+ // Track update of Psi Java, properties files
+ PsiManager.getInstance(project).addPsiTreeChangeListener(listener, project);
+ }
+
+ @Override
+ public void dispose() {
+ this.sourcesChanged.dispose();
+ this.connection.disconnect();
+ PsiManager.getInstance(project).removePsiTreeChangeListener(listener);
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/classpath/ClasspathRessourceChangedListener.java b/src/main/java/com/redhat/devtools/intellij/quarkus/classpath/ClasspathRessourceChangedListener.java
new file mode 100644
index 000000000..0da8980d3
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/classpath/ClasspathRessourceChangedListener.java
@@ -0,0 +1,150 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package com.redhat.devtools.intellij.quarkus.classpath;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.newvfs.BulkFileListener;
+import com.intellij.openapi.vfs.newvfs.events.VFileContentChangeEvent;
+import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent;
+import com.intellij.openapi.vfs.newvfs.events.VFileDeleteEvent;
+import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiTreeChangeAdapter;
+import com.intellij.psi.PsiTreeChangeEvent;
+import com.intellij.workspaceModel.ide.WorkspaceModelChangeListener;
+import com.intellij.workspaceModel.storage.VersionedStorageChange;
+import com.intellij.workspaceModel.storage.bridgeEntities.LibraryEntity;
+import com.redhat.devtools.intellij.lsp4ij.LSPIJUtils;
+import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project.PsiMicroProfileProjectManager;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * Classpath resource changed listener used to track update of libraries and Java source, microprofile-config.properties files.
+ */
+class ClasspathRessourceChangedListener extends PsiTreeChangeAdapter implements WorkspaceModelChangeListener, BulkFileListener {
+
+ private final Project project;
+
+ private final SourceFileChangeNotifier sourcesChanged;
+
+ ClasspathRessourceChangedListener(Project project, SourceFileChangeNotifier sourcesChanged) {
+ this.project = project;
+ this.sourcesChanged = sourcesChanged;
+ }
+
+ @Override
+ public void beforeChanged(@NotNull VersionedStorageChange event) {
+ // Do nothing
+ }
+
+ @Override
+ public void changed(@NotNull VersionedStorageChange event) {
+ var libraryChanges = event.getChanges(LibraryEntity.class);
+ if (libraryChanges.isEmpty()) {
+ return;
+ }
+ project.getMessageBus().syncPublisher(ClasspathResourceChangedManager.TOPIC).librariesChanged();
+ }
+
+ @Override
+ public void childAdded(@NotNull PsiTreeChangeEvent event) {
+ handleChangedPsiTree(event);
+ }
+
+ @Override
+ public void childRemoved(@NotNull PsiTreeChangeEvent event) {
+ handleChangedPsiTree(event);
+ }
+
+ @Override
+ public void childReplaced(@NotNull PsiTreeChangeEvent event) {
+ handleChangedPsiTree(event);
+ }
+
+ @Override
+ public void childMoved(@NotNull PsiTreeChangeEvent event) {
+ handleChangedPsiTree(event);
+ }
+
+ @Override
+ public void childrenChanged(@NotNull PsiTreeChangeEvent event) {
+ handleChangedPsiTree(event);
+ }
+
+ @Override
+ public void propertyChanged(@NotNull PsiTreeChangeEvent event) {
+ handleChangedPsiTree(event);
+ }
+
+ private void handleChangedPsiTree(PsiTreeChangeEvent event) {
+ PsiFile psiFile = event.getFile();
+ if (psiFile == null) {
+ return;
+ }
+ tryToAddSourceFile(psiFile.getVirtualFile(), true);
+ }
+
+ private void tryToAddSourceFile(VirtualFile file, boolean checkExistingFile) {
+ if (checkExistingFile && (file == null || !file.exists())) {
+ // The file doesn't exist
+ return;
+ }
+ if (!isJavaFile(file, project) && !isConfigSource(file, project)) {
+ return;
+ }
+ // The file is a Java file or microprofile-config.properties
+ Module fileProject = LSPIJUtils.getProject(file);
+ if (fileProject == null) {
+ return;
+ }
+ // Debounce the notification that the file has changed
+ sourcesChanged.addSourceFile(Pair.pair(file, fileProject));
+ }
+
+ @Override
+ public void before(@NotNull List extends VFileEvent> events) {
+ for (VFileEvent event: events) {
+ boolean expectedEvent = (event instanceof VFileDeleteEvent);
+ if (expectedEvent) {
+ // A file has been deleted
+ // We need to track delete event in 'before' method because we need the project of the file (in after we loose this information).
+ tryToAddSourceFile(event.getFile(), false);
+ }
+ }
+ }
+
+ @Override
+ public void after(@NotNull List extends VFileEvent> events) {
+ for (VFileEvent event: events) {
+ boolean expectedEvent = (event instanceof VFileCreateEvent || event instanceof VFileContentChangeEvent);
+ if (expectedEvent) {
+ // A file has been created, updated
+ tryToAddSourceFile(event.getFile(), false);
+ }
+ }
+ }
+
+ private static boolean isJavaFile(VirtualFile file, Project project) {
+ return PsiMicroProfileProjectManager.getInstance(project).isJavaFile(file);
+ }
+
+ private static boolean isConfigSource(VirtualFile file, Project project) {
+ return PsiMicroProfileProjectManager.getInstance(project).isConfigSource(file);
+ }
+}
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/classpath/SourceFileChangeNotifier.java b/src/main/java/com/redhat/devtools/intellij/quarkus/classpath/SourceFileChangeNotifier.java
new file mode 100644
index 000000000..b8b051d87
--- /dev/null
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/classpath/SourceFileChangeNotifier.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
+ * which is available at https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
+ *
+ * Contributors:
+ * Red Hat Inc. - initial API and implementation
+ *******************************************************************************/
+package com.redhat.devtools.intellij.quarkus.classpath;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import java.util.*;
+import java.util.function.Consumer;
+
+/**
+ * Source file change notifier with a debounce mode.
+ */
+public class SourceFileChangeNotifier implements Disposable {
+
+ private static final long DEBOUNCE_DELAY = 1000;
+ private Timer debounceTimer;
+ private TimerTask debounceTask;
+
+ private final Set> sourceFiles;
+
+ private final Consumer>> notifier;
+
+ public SourceFileChangeNotifier(Consumer>> notifier) {
+ this.notifier = notifier;
+ sourceFiles = new HashSet<>();
+ }
+ public synchronized void addSourceFile(Pair pair) {
+ if (debounceTask != null) {
+ debounceTask.cancel();
+ }
+ synchronized (sourceFiles) {
+ sourceFiles.add(pair);
+ }
+
+ debounceTask = new TimerTask() {
+ @Override
+ public void run() {
+ synchronized (sourceFiles) {
+ notifier.accept(sourceFiles);
+ sourceFiles.clear();
+ }
+ }
+ };
+
+ if (debounceTimer == null) {
+ debounceTimer = new Timer();
+ }
+
+ debounceTimer.schedule(debounceTask, DEBOUNCE_DELAY);
+ }
+
+ @Override
+ public void dispose() {
+ if (debounceTask != null) {
+ debounceTask.cancel();
+ }
+ if(debounceTimer != null) {
+ debounceTimer.cancel();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/redhat/devtools/intellij/quarkus/lsp/QuarkusLanguageClient.java b/src/main/java/com/redhat/devtools/intellij/quarkus/lsp/QuarkusLanguageClient.java
index da5bb6691..0430ed1bf 100644
--- a/src/main/java/com/redhat/devtools/intellij/quarkus/lsp/QuarkusLanguageClient.java
+++ b/src/main/java/com/redhat/devtools/intellij/quarkus/lsp/QuarkusLanguageClient.java
@@ -12,7 +12,7 @@
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.messages.MessageBusConnection;
import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.ProjectLabelManager;
@@ -22,9 +22,8 @@
import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.utils.IPsiUtils;
import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.core.ls.PsiUtilsLSImpl;
import com.redhat.devtools.intellij.quarkus.QuarkusModuleUtil;
-import com.redhat.devtools.intellij.quarkus.QuarkusProjectService;
import com.redhat.devtools.intellij.lsp4ij.IndexAwareLanguageClient;
-import org.apache.commons.lang3.tuple.Pair;
+import com.redhat.devtools.intellij.quarkus.classpath.ClasspathResourceChangedManager;
import org.eclipse.lsp4j.*;
import org.eclipse.lsp4mp.commons.*;
import org.eclipse.lsp4mp.commons.codeaction.CodeActionResolveData;
@@ -41,17 +40,16 @@
import java.util.stream.Collectors;
-public class QuarkusLanguageClient extends IndexAwareLanguageClient implements MicroProfileLanguageClientAPI, QuarkusProjectService.Listener {
+public class QuarkusLanguageClient extends IndexAwareLanguageClient implements MicroProfileLanguageClientAPI, ClasspathResourceChangedManager.Listener {
private static final Logger LOGGER = LoggerFactory.getLogger(QuarkusLanguageClient.class);
- private static final String JAVA_FILE_EXTENSION = "java";
private final MessageBusConnection connection;
public QuarkusLanguageClient(Project project) {
super(project);
connection = project.getMessageBus().connect(project);
- connection.subscribe(QuarkusProjectService.TOPIC, this);
- QuarkusProjectService.getInstance(project);
+ connection.subscribe(ClasspathResourceChangedManager.TOPIC, this);
+ ClasspathResourceChangedManager.getInstance(project);
}
@Override
@@ -70,18 +68,27 @@ private void sendPropertiesChangeEvent(List scope,
}
@Override
- public void libraryUpdated(Library library) {
+ public void librariesChanged() {
+ if (isDisposed()) {
+ // The language client has been disposed, ignore changes in libraries
+ return;
+ }
sendPropertiesChangeEvent(Collections.singletonList(MicroProfilePropertiesScope.dependencies), QuarkusModuleUtil.getModulesURIs(getProject()));
}
@Override
- public void sourceUpdated(List> sources) {
- List> info = sources.stream().
- filter(pair -> isJavaFile(pair.getRight()) || isConfigSource(pair.getRight(), pair.getLeft())).
- map(pair -> Pair.of(PsiUtilsLSImpl.getProjectURI(pair.getLeft()), getScope(pair.getRight()))).
+ public void sourceFilesChanged(Set> sources) {
+ if (isDisposed()) {
+ // The language client has been disposed, ignore changes in Java source / microprofile-config.properties files
+ return;
+ }
+ List> info = sources.stream()
+ .filter(pair -> isJavaFile(pair.getFirst()) || isConfigSource(pair.getFirst()))
+ .map(pair -> Pair.pair(PsiUtilsLSImpl.getProjectURI(pair.getSecond()), getScope(pair.getFirst()))).
collect(Collectors.toList());
if (!info.isEmpty()) {
- sendPropertiesChangeEvent(info.stream().map(Pair::getRight).collect(Collectors.toList()), info.stream().map(Pair::getLeft).collect(Collectors.toSet()));
+ sendPropertiesChangeEvent(info.stream().map(p -> p.getSecond()).collect(Collectors.toList()),
+ info.stream().map(p -> p.getFirst()).collect(Collectors.toSet()));
}
}
@@ -90,11 +97,11 @@ private MicroProfilePropertiesScope getScope(VirtualFile file) {
}
private boolean isJavaFile(VirtualFile file) {
- return JAVA_FILE_EXTENSION.equals(file.getExtension());
+ return PsiMicroProfileProjectManager.getInstance(getProject()).isJavaFile(file);
}
- private boolean isConfigSource(VirtualFile file, Module project) {
- return PsiMicroProfileProjectManager.getInstance(project.getProject()).isConfigSource(file);
+ private boolean isConfigSource(VirtualFile file) {
+ return PsiMicroProfileProjectManager.getInstance(getProject()).isConfigSource(file);
}
@Override
diff --git a/src/main/java/com/redhat/devtools/intellij/qute/lsp/QuteLanguageClient.java b/src/main/java/com/redhat/devtools/intellij/qute/lsp/QuteLanguageClient.java
index 2c6c21452..f308b3ccf 100644
--- a/src/main/java/com/redhat/devtools/intellij/qute/lsp/QuteLanguageClient.java
+++ b/src/main/java/com/redhat/devtools/intellij/qute/lsp/QuteLanguageClient.java
@@ -13,12 +13,13 @@
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
-import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.messages.MessageBusConnection;
import com.redhat.devtools.intellij.lsp4mp4ij.psi.internal.core.ls.PsiUtilsLSImpl;
import com.redhat.devtools.intellij.quarkus.QuarkusProjectService;
import com.redhat.devtools.intellij.lsp4ij.IndexAwareLanguageClient;
+import com.redhat.devtools.intellij.quarkus.classpath.ClasspathResourceChangedManager;
import com.redhat.devtools.intellij.qute.psi.QuteSupportForJava;
import com.redhat.devtools.intellij.qute.psi.QuteSupportForTemplate;
import com.redhat.devtools.intellij.qute.psi.utils.PsiQuteProjectUtils;
@@ -43,7 +44,6 @@
import com.redhat.qute.commons.usertags.UserTagInfo;
import com.redhat.qute.ls.api.QuteLanguageClientAPI;
import com.redhat.qute.ls.api.QuteLanguageServerAPI;
-import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.lsp4j.CodeLens;
import org.eclipse.lsp4j.DocumentLink;
import org.eclipse.lsp4j.Location;
@@ -58,7 +58,7 @@
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
-public class QuteLanguageClient extends IndexAwareLanguageClient implements QuteLanguageClientAPI, QuarkusProjectService.Listener {
+public class QuteLanguageClient extends IndexAwareLanguageClient implements QuteLanguageClientAPI, ClasspathResourceChangedManager.Listener {
private static final Logger LOGGER = LoggerFactory.getLogger(QuteLanguageClient.class);
private final MessageBusConnection connection;
@@ -66,7 +66,7 @@ public class QuteLanguageClient extends IndexAwareLanguageClient implements Qute
public QuteLanguageClient(Project project) {
super(project);
connection = project.getMessageBus().connect(project);
- connection.subscribe(QuarkusProjectService.TOPIC, this);
+ connection.subscribe(ClasspathResourceChangedManager.TOPIC, this);
QuarkusProjectService.getInstance(project);
}
@@ -82,7 +82,7 @@ public void dispose() {
*
* @param uris the project uris where the data model must be refreshed.
*/
- private void notifyDataModelChanged(Set uris) {
+ private void notifyQuteDataModelChanged(Set uris) {
QuteLanguageServerAPI server = (QuteLanguageServerAPI) getLanguageServer();
if (server != null) {
JavaDataModelChangeEvent event = new JavaDataModelChangeEvent();
@@ -92,28 +92,27 @@ private void notifyDataModelChanged(Set uris) {
}
@Override
- public void libraryUpdated(Library library) {
+ public void librariesChanged() {
if (isDisposed()) {
- // The language client has been disposed, ignore the changed of library
+ // The language client has been disposed, ignore changes in libraries
return;
}
Set uris = new HashSet<>();
uris.add(PsiQuteProjectUtils.getProjectURI(getProject()));
- notifyDataModelChanged(uris);
+ notifyQuteDataModelChanged(uris);
}
@Override
- public void sourceUpdated(List> sources) {
+ public void sourceFilesChanged(Set> sources) {
if (isDisposed()) {
- // The language client has been disposed, ignore the changed of Java source files
+ // The language client has been disposed, ignore changes in Java source files
return;
}
- Set uris = sources.stream()
- .map(pair -> pair.getLeft())
- .map(module -> PsiQuteProjectUtils.getProjectURI(module))
+ Set uris = sources.stream().map(pair -> pair.getSecond())
+ .map(module -> PsiUtilsLSImpl.getProjectURI(module))
.collect(Collectors.toSet());
if (!uris.isEmpty()) {
- notifyDataModelChanged(uris);
+ notifyQuteDataModelChanged(uris);
}
}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index a01b9591a..062f9b62c 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -235,7 +235,7 @@
-
+
@@ -264,6 +264,7 @@
+