-
Notifications
You must be signed in to change notification settings - Fork 51
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: debounce classpath/source change events (#916)
Fixes #916 Signed-off-by: azerr <[email protected]>
- Loading branch information
1 parent
b6b4b8d
commit ff3d875
Showing
15 changed files
with
790 additions
and
349 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
255 changes: 255 additions & 0 deletions
255
...va/com/redhat/devtools/intellij/lsp4mp4ij/classpath/ClasspathResourceChangedListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: | ||
* | ||
* <ul> | ||
* <li>library has changed.</li> | ||
* <li>Java source file has changed.</li> | ||
* <li>microprofile-config.properties file has changed.</li> | ||
* </ul> | ||
*/ | ||
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<Module> modulesBeingEnsured = new HashSet<>(); | ||
|
||
ClasspathResourceChangedListener(ClasspathResourceChangedManager manager) { | ||
this.manager = manager; | ||
} | ||
|
||
// Track modules changed | ||
|
||
public CompletableFuture<Void> 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<Void> processModule(Module module) { | ||
return checkOverridedLibrary(module, false); | ||
} | ||
|
||
private CompletableFuture<Void> 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<? 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) { | ||
return PsiMicroProfileProjectManager.isJavaFile(file); | ||
} | ||
|
||
private static boolean isConfigSource(VirtualFile file) { | ||
return PsiMicroProfileProjectManager.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) && !isConfigSource(file)) { | ||
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)); | ||
} | ||
|
||
} |
Oops, something went wrong.