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/lsp4ij/LSPIJUtils.java b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPIJUtils.java index 9410adc21..26fa6faa0 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPIJUtils.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4ij/LSPIJUtils.java @@ -144,7 +144,7 @@ public static Document getDocument(VirtualFile docFile) { public static @Nullable Module getProject(VirtualFile file) { for (Project project : ProjectManager.getInstance().getOpenProjects()) { - Module module = ReadAction.compute(() -> ProjectFileIndex.getInstance(project).getModuleForFile(file)); + Module module = ReadAction.compute(() -> ProjectFileIndex.getInstance(project).getModuleForFile(file, false)); if (module != null) { return module; } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedListener.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedListener.java new file mode 100644 index 000000000..fec147637 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedListener.java @@ -0,0 +1,255 @@ +/******************************************************************************* + * 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.lsp4mp4ij.classpath; + +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.project.ModuleListener; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.impl.libraries.LibraryEx; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +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.redhat.devtools.intellij.lsp4ij.LSPIJUtils; +import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project.PsiMicroProfileProjectManager; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +/** + * Classpath resource changed listener used to track update of: + * + * + */ +class ClasspathResourceChangedListener extends PsiTreeChangeAdapter implements BulkFileListener, LibraryTable.Listener, ModuleListener { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClasspathResourceChangedListener.class); + + private final ClasspathResourceChangedManager manager; + + private final Set modulesBeingEnsured = new HashSet<>(); + + ClasspathResourceChangedListener(ClasspathResourceChangedManager manager) { + this.manager = manager; + } + + // Track modules changed + + public CompletableFuture processModules() { + var overriders = manager.getOverriders(); + if (overriders.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + // Loop for each modules and process the load of libraries by using Classpath overrider. + return CompletableFuture.runAsync(() -> { + for (var module : ModuleManager.getInstance(manager.getProject()).getModules()) { + LOGGER.info("Calling ensure from processModules"); + checkOverridedLibrary(module, true); + } + }, manager.getExecutor()); + } + + private CompletableFuture processModule(Module module) { + return checkOverridedLibrary(module, false); + } + + private CompletableFuture checkOverridedLibrary(Module module, boolean sync) { + var overriders = manager.getOverriders(); + if (modulesBeingEnsured.add(module)) { + if (sync) { + for (var overrider : overriders) { + overrider.overrideClasspath(module); + } + modulesBeingEnsured.remove(module); + return CompletableFuture.completedFuture(null); + } else { + return CompletableFuture.runAsync(() -> { + for (var overrider : overriders) { + overrider.overrideClasspath(module); + } + modulesBeingEnsured.remove(module); + }, manager.getExecutor()); + } + } + return CompletableFuture.completedFuture(null); + } + + @Override + public void moduleAdded(@NotNull Project project, @NotNull Module module) { + moduleChanged(module); + } + + @Override + public void moduleRemoved(@NotNull Project project, @NotNull Module module) { + moduleChanged(module); + } + + private void moduleChanged(Module module) { + LOGGER.info("Calling ensure from moduleChanged for module " + module.getName()); + var overriders = manager.getOverriders(); + if (!overriders.isEmpty()) { + // A module has changed, process the load of libraries by using Classpath overrider. + checkOverridedLibrary(module, false); + } + } + + // Track library changes + + @Override + public void afterLibraryAdded(@NotNull Library newLibrary) { + handleLibraryUpdate(newLibrary); + } + + @Override + public void afterLibraryRemoved(@NotNull Library library) { + handleLibraryUpdate(library); + } + + private void handleLibraryUpdate(Library library) { + LOGGER.info("handleLibraryUpdate called " + library.getName()); + var project = manager.getProject(); + + // Notify that a library has changed. + final var notifier = manager.getResourceChangedNotifier(); + notifier.addLibrary(library); + + // Process classpath overriders. + var overriders = manager.getOverriders(); + if (overriders.isEmpty()) { + if (library instanceof LibraryEx && ((LibraryEx) library).getModule() != null) { + var module = ((LibraryEx) library).getModule(); + processModule(module).thenRun(() -> { + project.getMessageBus().syncPublisher(ClasspathResourceChangedManager.TOPIC).moduleUpdated(module); + }); + } else { + processModules().thenRun(() -> { + notifier.addLibrary(library); + project.getMessageBus().syncPublisher(ClasspathResourceChangedManager.TOPIC).modulesUpdated(); + }); + } + } + } + + // Track Psi file changes + + @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) { + // A Psi file has been changed in the editor + PsiFile psiFile = event.getFile(); + if (psiFile == null) { + return; + } + tryToAddSourceFile(psiFile.getVirtualFile(), true); + } + + // Track file system changes + + @Override + public void before(@NotNull List 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 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); + } + + private void tryToAddSourceFile(VirtualFile file, boolean checkExistingFile) { + if (checkExistingFile && (file == null || !file.exists())) { + // The file doesn't exist + return; + } + var project = manager.getProject(); + if (!isJavaFile(file, project) && !isConfigSource(file, project)) { + return; + } + // The file is a Java file or microprofile-config.properties + Module module = LSPIJUtils.getProject(file); + if (module == null || module.isDisposed()) { + return; + } + // Notify that the file has changed + var notifier = manager.getResourceChangedNotifier(); + notifier.addSourceFile(Pair.pair(file, module)); + } + +} diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedManager.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedManager.java new file mode 100644 index 000000000..df73b720e --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedManager.java @@ -0,0 +1,149 @@ +/******************************************************************************* + * 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.lsp4mp4ij.classpath; + +import com.intellij.ProjectTopics; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.components.ServiceManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; +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.ConcurrencyUtil; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.messages.Topic; +import com.intellij.workspaceModel.ide.WorkspaceModelTopics; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +/** + * 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 ExecutorService executor; + private final ClasspathResourceChangedNotifier resourceChangedNotifier; + private final MessageBusConnection projectConnection; + private final MessageBusConnection appConnection; + private final ClasspathResourceChangedListener listener; + private final List overriders; + + public static ClasspathResourceChangedManager getInstance(Project project) { + return ServiceManager.getService(project, ClasspathResourceChangedManager.class); + } + + public interface Listener { + + void librariesChanged(); + + void sourceFilesChanged(Set> sources); + + void moduleUpdated(Module module); + + void modulesUpdated(); + } + + public interface ClasspathOverrider { + + void overrideClasspath(Module module); + + } + + private final Project project; + + public ClasspathResourceChangedManager(Project project) { + this.project = project; + this.overriders = new ArrayList<>(); + if (ApplicationManager.getApplication().isUnitTestMode()) { + this.executor = ConcurrencyUtil.newSameThreadExecutorService(); + } else { + this.executor = new ThreadPoolExecutor(0, 1, + 1L, TimeUnit.MINUTES, new LinkedBlockingQueue(), + r -> new Thread(r, "Quarkus lib pool " + project.getName())); + } + // Send source files changed in debounce mode + this.resourceChangedNotifier = new ClasspathResourceChangedNotifier(project); + listener = new ClasspathResourceChangedListener(this); + projectConnection = project.getMessageBus().connect(); + // Track end of Java libraries update + LibraryTablesRegistrar.getInstance().getLibraryTable(project).addListener(listener); + // Track update of Psi Java, properties files + PsiManager.getInstance(project).addPsiTreeChangeListener(listener, project); + // Track modules changes + projectConnection.subscribe(ProjectTopics.MODULES, listener); + // Track delete, create, update of file + appConnection = ApplicationManager.getApplication().getMessageBus().connect(project); + appConnection.subscribe(VirtualFileManager.VFS_CHANGES, listener); + processModules(); + } + + public void processModules() { + listener.processModules(); + } + + public void addClasspathOverrider(ClasspathOverrider overrider) { + overriders.add(overrider); + } + + @Override + public void dispose() { + this.resourceChangedNotifier.dispose(); + this.projectConnection.disconnect(); + this.appConnection.disconnect(); + LibraryTablesRegistrar.getInstance().getLibraryTable(project).removeListener(listener); + PsiManager.getInstance(project).removePsiTreeChangeListener(listener); + executor.shutdown(); + } + + Project getProject() { + return project; + } + + ClasspathResourceChangedNotifier getResourceChangedNotifier() { + return resourceChangedNotifier; + } + + ExecutorService getExecutor() { + return executor; + } + + List getOverriders() { + return overriders; + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedNotifier.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedNotifier.java new file mode 100644 index 000000000..79852e884 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedNotifier.java @@ -0,0 +1,104 @@ +/******************************************************************************* + * 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.lsp4mp4ij.classpath; + +import com.intellij.openapi.Disposable; +import com.intellij.openapi.application.ApplicationManager; +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 java.util.*; + +/** + * Source file change notifier with a debounce mode. + */ +public class ClasspathResourceChangedNotifier implements Disposable { + + private static final long DEBOUNCE_DELAY = 1000; + + private final Project project; + + private Timer debounceTimer; + private TimerTask debounceTask; + + private final Set> sourceFiles; + private boolean librariesChanged; + + public ClasspathResourceChangedNotifier(Project project) { + this.project = project; + sourceFiles = new HashSet<>(); + } + + public synchronized void addLibrary(Library library) { + if (debounceTask != null) { + debounceTask.cancel(); + } + librariesChanged = true; + asyncNotifyChanges(); + } + + public synchronized void addSourceFile(Pair pair) { + if (debounceTask != null) { + debounceTask.cancel(); + } + synchronized (sourceFiles) { + sourceFiles.add(pair); + } + + asyncNotifyChanges(); + } + + private void asyncNotifyChanges() { + if (ApplicationManager.getApplication().isUnitTestMode()) { + notifyChanges(); + } else { + debounceTask = new TimerTask() { + @Override + public void run() { + notifyChanges(); + } + }; + + if (debounceTimer == null) { + debounceTimer = new Timer(); + } + + debounceTimer.schedule(debounceTask, DEBOUNCE_DELAY); + } + } + + private void notifyChanges() { + synchronized (sourceFiles) { + project.getMessageBus().syncPublisher(ClasspathResourceChangedManager.TOPIC).sourceFilesChanged(sourceFiles); + sourceFiles.clear(); + } + if (librariesChanged) { + project.getMessageBus().syncPublisher(ClasspathResourceChangedManager.TOPIC).librariesChanged(); + librariesChanged = false; + } + } + + @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/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProjectManager.java index 5d6107144..50bf393b2 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,165 @@ /******************************************************************************* -* 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.lsp4mp4ij.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 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 modulesUpdated() { + // Do nothing + } + + @Override + public void moduleUpdated(Module module) { + // Do nothing + } + + @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(); + } + microprofileProjectListener = null; + } } diff --git a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/core/ls/PsiUtilsLSImpl.java b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/core/ls/PsiUtilsLSImpl.java index e47ba98cc..8b047716b 100644 --- a/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/core/ls/PsiUtilsLSImpl.java +++ b/src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/internal/core/ls/PsiUtilsLSImpl.java @@ -79,10 +79,7 @@ public IPsiUtils refine(Module module) { @Override public Module getModule(VirtualFile file) { - if (file != null && !project.isDisposed()) { - return ProjectFileIndex.getInstance(project).getModuleForFile(file, false); - } - return null; + return LSPIJUtils.getProject(file); } @Override 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..475d28b68 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,23 @@ -/******************************************************************************* - * 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 org.jetbrains.annotations.NotNull; + +public class QuarkusPostStartupActivity implements StartupActivity, DumbAware { + @Override + public void runActivity(@NotNull Project 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..5c1f30bfd 100644 --- a/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusProjectService.java +++ b/src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusProjectService.java @@ -10,215 +10,64 @@ ******************************************************************************/ package com.redhat.devtools.intellij.quarkus; -import com.intellij.ProjectTopics; import com.intellij.json.JsonFileType; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleManager; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.project.DumbService; -import com.intellij.openapi.project.ModuleListener; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.roots.impl.libraries.LibraryEx; -import com.intellij.openapi.roots.libraries.Library; -import com.intellij.openapi.roots.libraries.LibraryTable; -import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; import com.intellij.openapi.vfs.VfsUtil; 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.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; +import com.redhat.devtools.intellij.lsp4mp4ij.classpath.ClasspathResourceChangedManager; import org.apache.commons.lang3.tuple.MutablePair; -import org.apache.commons.lang3.tuple.Pair; import org.eclipse.lsp4mp.commons.ClasspathKind; import org.eclipse.lsp4mp.commons.DocumentFormat; import org.eclipse.lsp4mp.commons.MicroProfileProjectInfo; import org.eclipse.lsp4mp.commons.MicroProfilePropertiesScope; import org.eclipse.lsp4mp.utils.JSONSchemaUtils; -import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; 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.stream.Collectors; +import java.util.*; +import java.util.concurrent.*; -public class QuarkusProjectService implements LibraryTable.Listener, BulkFileListener, ModuleListener, Disposable { +public class QuarkusProjectService implements ClasspathResourceChangedManager.Listener, Disposable { private static final Logger LOGGER = LoggerFactory.getLogger(QuarkusProjectService.class); - private final Project project; - private final Map> schemas = new ConcurrentHashMap<>(); - private final ExecutorService executor; - - 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 Project project; private final MessageBusConnection connection; public QuarkusProjectService(Project project) { this.project = project; - if (ApplicationManager.getApplication().isUnitTestMode()) { - this.executor = ConcurrencyUtil.newSameThreadExecutorService(); - } else { - this.executor = new ThreadPoolExecutor(0, 1, - 1L, TimeUnit.MINUTES, new LinkedBlockingQueue(), - r -> new Thread(r, "Quarkus lib pool " + project.getName())); - } - LibraryTablesRegistrar.getInstance().getLibraryTable(project).addListener(this, project); - connection = ApplicationManager.getApplication().getMessageBus().connect(project); - connection.subscribe(VirtualFileManager.VFS_CHANGES, this); - project.getMessageBus().connect().subscribe(ProjectTopics.MODULES, this); - processModules(); - } - - private CompletableFuture checkQuarkusLibrary(Module module, boolean sync) { - if (modulesBeingEnsured.add(module)) { - if (sync) { - QuarkusModuleUtil.ensureQuarkusLibrary(module); - modulesBeingEnsured.remove(module); - return CompletableFuture.completedFuture(null); - } else { - return CompletableFuture.runAsync(() -> { - QuarkusModuleUtil.ensureQuarkusLibrary(module); - modulesBeingEnsured.remove(module); - }, executor); - } - } - return CompletableFuture.completedFuture(null); - } - - public CompletableFuture processModules() { - return CompletableFuture.runAsync(() -> { - for (var module : ModuleManager.getInstance(project).getModules()) { - LOGGER.info("Calling ensure from processModules"); - checkQuarkusLibrary(module, true); - } - }, executor); - } - - private CompletableFuture processModule(Module module) { - return checkQuarkusLibrary(module, false); - } - - private void handleLibraryUpdate(Library library) { - LOGGER.info("handleLibraryUpdate called " + library.getName()); - if (library instanceof LibraryEx && ((LibraryEx) library).getModule() != null) { - var module = ((LibraryEx) library).getModule(); - processModule(module).thenRun(() -> { - var pair = schemas.get(module); - if (pair != null) { - pair.setRight(Boolean.FALSE); - } - }); - } else { - processModules().thenRun(() -> { - project.getMessageBus().syncPublisher(TOPIC).libraryUpdated(library); - schemas.forEach((module, pair) -> { - pair.setRight(Boolean.FALSE); + ClasspathResourceChangedManager.getInstance(project) + .addClasspathOverrider(new ClasspathResourceChangedManager.ClasspathOverrider() { + @Override + public void overrideClasspath(Module module) { + QuarkusModuleUtil.ensureQuarkusLibrary(module); + } }); - }); - } - } - - @Override - public void afterLibraryAdded(@NotNull Library newLibrary) { - handleLibraryUpdate(newLibrary); - } - - @Override - public void afterLibraryRemoved(@NotNull Library library) { - handleLibraryUpdate(library); - } - - @Override - public void after(@NotNull List events) { - List> pairs = events.stream() - .map(event -> toPair(event)) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - if (!pairs.isEmpty()) { - pairs.forEach(pair -> schemas.computeIfPresent(pair.getLeft(), (m, p) -> { - p.setRight(Boolean.FALSE); - return p; - })); - project.getMessageBus().syncPublisher(TOPIC).sourceUpdated(pairs); - } - } - - private Pair toPair(VFileEvent event) { - VirtualFile file = event.getFile(); - if (file == null || !file.exists()) { - return null; - } - boolean expectedEvent = (event instanceof VFileCreateEvent || event instanceof VFileContentChangeEvent || event instanceof VFileDeleteEvent); - if (!expectedEvent) { - return null; - } - // Here a file has been created, saved ot deleted - Module module = ProjectFileIndex.getInstance(project).getModuleForFile(file); - if (module == null || module.isDisposed()) { - return null; - } - if (!isJavaFile(file) && !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 isConfigSource(VirtualFile file, Project project) { - return PsiMicroProfileProjectManager.getInstance(project).isConfigSource(file); + connection = project.getMessageBus().connect(); + connection.subscribe(ClasspathResourceChangedManager.TOPIC, this); } public VirtualFile getSchema(Module module) { - MutablePair schemaEntry = schemas.get(module); + var 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,14 +77,13 @@ 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 { return new LightVirtualFile(name + "-schema.json", JsonFileType.INSTANCE, ""); } - private VirtualFile computeSchema(Module module, VirtualFile schemaFile) { try { if (schemaFile == null) { @@ -256,24 +104,46 @@ private VirtualFile computeSchema(Module module, VirtualFile schemaFile) { }); }); return schemaFile; - } catch (IOException| ProcessCanceledException e) { + } catch (IOException | ProcessCanceledException e) { LOGGER.warn(e.getLocalizedMessage(), e); } return null; } - private void moduleChanged(Module module) { - LOGGER.info("Calling ensure from moduleChanged for module " + module.getName()); - checkQuarkusLibrary(module, false); + @Override + public void dispose() { + connection.disconnect(); } @Override - public void moduleAdded(@NotNull Project project, @NotNull Module module) { - moduleChanged(module); + public void librariesChanged() { + // Do nothing } @Override - public void moduleRemoved(@NotNull Project project, @NotNull Module module) { - moduleChanged(module); + public void sourceFilesChanged(Set> sources) { + sources.forEach(pair -> schemas.computeIfPresent(pair.getSecond(), (m, p) -> { + p.setRight(Boolean.FALSE); + return p; + })); + } + + @Override + public void moduleUpdated(Module module) { + var pair = schemas.get(module); + if (pair != null) { + pair.setRight(Boolean.FALSE); + } + } + + @Override + public void modulesUpdated() { + schemas.forEach((module, pair) -> { + pair.setRight(Boolean.FALSE); + }); + } + + public void processModules() { + ClasspathResourceChangedManager.getInstance(project).processModules(); } } 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..dc3d8d675 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.lsp4mp4ij.classpath.ClasspathResourceChangedManager; import org.eclipse.lsp4j.*; import org.eclipse.lsp4mp.commons.*; import org.eclipse.lsp4mp.commons.codeaction.CodeActionResolveData; @@ -41,21 +40,20 @@ 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); } @Override public void dispose() { + super.dispose(); connection.disconnect(); } @@ -70,31 +68,50 @@ 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())); } } + @Override + public void modulesUpdated() { + // Do nothing + } + + @Override + public void moduleUpdated(Module module) { + // Do nothing + } + private MicroProfilePropertiesScope getScope(VirtualFile file) { return isJavaFile(file)?MicroProfilePropertiesScope.sources:MicroProfilePropertiesScope.configfiles; } 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..ba64fbcaa 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,14 @@ 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.core.project.PsiMicroProfileProjectManager; 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.lsp4mp4ij.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 +45,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 +59,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 +67,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 +83,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,31 +93,43 @@ 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)) + // qute/dataModelChanged must be sent only if there are some Java files which are changed + .filter(pair -> PsiMicroProfileProjectManager.getInstance(getProject()).isJavaFile(pair.getFirst())) + .map(pair -> pair.getSecond()) + .map(module -> PsiUtilsLSImpl.getProjectURI(module)) .collect(Collectors.toSet()); if (!uris.isEmpty()) { - notifyDataModelChanged(uris); + notifyQuteDataModelChanged(uris); } } + @Override + public void modulesUpdated() { + // Do nothing + } + + @Override + public void moduleUpdated(Module module) { + // Do nothing + } + @Override public CompletableFuture getProjectInfo(QuteProjectParams params) { return runAsBackground("getProjectInfo", monitor -> QuteSupportForTemplate.getInstance().getProjectInfo(params, PsiUtilsLSImpl.getInstance(getProject()), monitor)); diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index a01b9591a..61feb764d 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 @@ +