code_samples/project_view_pane: Cleanup (#1093)

* code_samples/project_view_pane: Cleanup:

- fix bug with not refreshing "svg" files and add supporting "jpeg" extension
- fix not working "Folders Always on Top" switch
- fix extracting file extension
- change disposable from Project to ProjectViewPane
- use BulkFileListener instead of VirtualFileListener with Alarm for updating the tree for immediate updates
- use Application.invokeLater instead of SwingUtilities.invokeLater
- code cleanup

* code_samples/project_view_pane: Add project.getDisposed() condition to the Application.invokeLater() call

* code_samples/project_view_pane: Do not count non-project files

* code_samples/project_view_pane: Do not update UI too often
This commit is contained in:
Karol Lewandowski 2023-08-02 14:32:01 +02:00 committed by GitHub
parent d7dd55a7e0
commit 7ffca8f009
2 changed files with 117 additions and 94 deletions

View File

@ -1,72 +1,86 @@
// Copyright 2000-2022 JetBrains s.r.o. and other contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.sdk.view.pane; package org.intellij.sdk.view.pane;
import com.intellij.icons.AllIcons; import com.intellij.icons.AllIcons;
import com.intellij.ide.projectView.PresentationData; import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.projectView.ProjectView; import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.projectView.ProjectViewNode;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.GroupByTypeComparator;
import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.fileEditor.FileEditorManager; import com.intellij.openapi.fileEditor.FileEditorManager;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil; import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.util.Disposer; import com.intellij.openapi.roots.FileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileEvent; import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.VirtualFileListener; import com.intellij.openapi.vfs.newvfs.BulkFileListener;
import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
import com.intellij.psi.search.FilenameIndex; import com.intellij.psi.search.FilenameIndex;
import com.intellij.util.Alarm; import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.*; import java.util.*;
public class ImagesProjectNode extends AbstractTreeNode<VirtualFile> { public class ImagesProjectNode extends ProjectViewNode<VirtualFile> {
private static final Key<Set<VirtualFile>> IMAGES_PROJECT_DIRS = Key.create("images.files.or.directories"); private static final Key<Set<VirtualFile>> IMAGES_PROJECT_DIRS = Key.create("images.files.or.directories");
public ImagesProjectNode(final Project project) { private static final List<String> SUPPORTED_IMAGE_EXTENSIONS = List.of("jpg", "jpeg", "png", "svg");
super(project, ProjectUtil.guessProjectDir(project));
private final MergingUpdateQueue updateQueue;
/**
* Creates root node.
*/
public ImagesProjectNode(@NotNull Project project,
@NotNull ViewSettings settings,
@NotNull VirtualFile rootDir,
@NotNull Disposable parentDisposable) {
super(project, rootDir, settings);
scanImages(project); scanImages(project);
setupImageFilesRefresher(project, parentDisposable); // subscribe to changes only in the root node
subscribeToVFS(project); updateQueue = new MergingUpdateQueue(ImagesProjectNode.class.getName(), 200, true, null, parentDisposable, null);
} }
public ImagesProjectNode(Project project, VirtualFile file) { /**
super(project, file); * Creates child node.
*/
private ImagesProjectNode(@NotNull Project project,
@NotNull ViewSettings settings,
@NotNull VirtualFile file,
@NotNull MergingUpdateQueue updateQueue) {
super(project, file, settings);
this.updateQueue = updateQueue;
} }
private void scanImages(Project project) { private void scanImages(@NotNull Project project) {
addAllByExt(project, "png"); for (String imageExtension : SUPPORTED_IMAGE_EXTENSIONS) {
addAllByExt(project, "jpg"); addAllByExt(project, imageExtension);
addAllByExt(project, "svg"); }
} }
// Creates a collection of image files asynchronously private void addAllByExt(@NotNull Project project, @NotNull String extension) {
private void addAllByExt(Project project, String ext) { Set<VirtualFile> imagesFiles = getImagesFiles(project);
final Set<VirtualFile> imagesFiles = getImagesFiles(project); VirtualFile projectDir = ProjectUtil.guessProjectDir(project);
final VirtualFile projectDir = ProjectUtil.guessProjectDir(project); Collection<VirtualFile> files = ReadAction.compute(() -> FilenameIndex.getAllFilesByExt(project, extension));
for (VirtualFile file : files) {
try { while (file != null && !file.equals(projectDir)) {
final Collection<VirtualFile> files = ReadAction.compute(() -> FilenameIndex.getAllFilesByExt(project, ext)); imagesFiles.add(file);
file = file.getParent();
for (VirtualFile file : files) {
while (file != null && !file.equals(projectDir)) {
imagesFiles.add(file);
file = file.getParent();
}
} }
} catch (Throwable throwable) {
throwable.printStackTrace();
} }
} }
@NotNull @NotNull
private Set<VirtualFile> getImagesFiles(Project project) { private Set<VirtualFile> getImagesFiles(@NotNull Project project) {
Set<VirtualFile> files = project.getUserData(IMAGES_PROJECT_DIRS); Set<VirtualFile> files = project.getUserData(IMAGES_PROJECT_DIRS);
if (files == null) { if (files == null) {
files = new HashSet<>(); files = new HashSet<>();
@ -75,15 +89,57 @@ public class ImagesProjectNode extends AbstractTreeNode<VirtualFile> {
return files; return files;
} }
private void setupImageFilesRefresher(@NotNull Project project, @NotNull Disposable parentDisposable) {
project.getMessageBus().connect(parentDisposable)
.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener() {
@Override
public void after(@NotNull List<? extends @NotNull VFileEvent> events) {
boolean hasAnyImageUpdate = false;
FileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
for (VFileEvent event : events) {
VirtualFile file = event.getFile();
if (file == null || !fileIndex.isInContent(file)) {
continue;
}
String extension = file.getExtension();
if (extension != null && SUPPORTED_IMAGE_EXTENSIONS.contains(extension)) {
hasAnyImageUpdate = true;
break;
}
}
if (hasAnyImageUpdate) {
updateQueue.queue(new Update("UpdateImages") {
public void run() {
getImagesFiles(project).clear();
scanImages(project);
ApplicationManager.getApplication().invokeLater(() ->
ProjectView.getInstance(project)
.getProjectViewPaneById(ImagesProjectViewPane.ID)
.updateFromRoot(true),
project.getDisposed()
);
}
});
}
}
});
}
@Override @Override
protected VirtualFile getVirtualFile() { public boolean contains(@NotNull VirtualFile file) {
return file.equals(getVirtualFile());
}
@Override
@NotNull
public VirtualFile getVirtualFile() {
return getValue(); return getValue();
} }
@NotNull @NotNull
@Override @Override
public Collection<? extends AbstractTreeNode<?>> getChildren() { public Collection<? extends AbstractTreeNode<?>> getChildren() {
final List<VirtualFile> files = new ArrayList<>(0); List<VirtualFile> files = new ArrayList<>();
for (VirtualFile file : getValue().getChildren()) { for (VirtualFile file : getValue().getChildren()) {
if (getImagesFiles(myProject).contains(file)) { if (getImagesFiles(myProject).contains(file)) {
files.add(file); files.add(file);
@ -92,30 +148,14 @@ public class ImagesProjectNode extends AbstractTreeNode<VirtualFile> {
if (files.isEmpty()) { if (files.isEmpty()) {
return Collections.emptyList(); return Collections.emptyList();
} }
final List<AbstractTreeNode<?>> nodes = new ArrayList<>(files.size()); ViewSettings settings = getSettings();
final boolean alwaysOnTop = ProjectView.getInstance(myProject).isFoldersAlwaysOnTop(""); return ContainerUtil.sorted(
files.sort((o1, o2) -> { ContainerUtil.map(files, (file) -> new ImagesProjectNode(myProject, settings, file, updateQueue)),
if (alwaysOnTop) { new GroupByTypeComparator(myProject, ImagesProjectViewPane.ID));
final boolean d1 = o1.isDirectory();
final boolean d2 = o2.isDirectory();
if (d1 && !d2) {
return -1;
}
if (!d1 && d2) {
return 1;
}
}
return StringUtil.naturalCompare(o1.getName(), o2.getName());
});
for (VirtualFile file : files) {
nodes.add(new ImagesProjectNode(myProject, file));
}
return nodes;
} }
@Override @Override
protected void update(PresentationData data) { protected void update(@NotNull PresentationData data) {
data.setIcon(getValue().isDirectory() ? AllIcons.Nodes.Folder : getValue().getFileType().getIcon()); data.setIcon(getValue().isDirectory() ? AllIcons.Nodes.Folder : getValue().getFileType().getIcon());
data.setPresentableText(getValue().getName()); data.setPresentableText(getValue().getName());
} }
@ -135,38 +175,10 @@ public class ImagesProjectNode extends AbstractTreeNode<VirtualFile> {
FileEditorManager.getInstance(myProject).openFile(getValue(), false); FileEditorManager.getInstance(myProject).openFile(getValue(), false);
} }
private void subscribeToVFS(final Project project) { @Override
final Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, project); public int getTypeSortWeight(boolean sortByType) {
LocalFileSystem.getInstance().addVirtualFileListener(new VirtualFileListener() { // required for "Folder Always on Top"
{ return getVirtualFile().isDirectory() ? 1 : 0;
final VirtualFileListener me = this;
Disposer.register(project, () -> LocalFileSystem.getInstance().removeVirtualFileListener(me));
}
@Override
public void fileCreated(@NotNull VirtualFileEvent event) {
handle(event);
}
@Override
public void fileDeleted(@NotNull VirtualFileEvent event) {
handle(event);
}
void handle(VirtualFileEvent event) {
final String filename = event.getFileName().toLowerCase();
if (filename.endsWith(".png") || filename.endsWith(".jpg")) {
alarm.cancelAllRequests();
alarm.addRequest(() -> {
getImagesFiles(project).clear();
scanImages(project);
SwingUtilities.invokeLater(() -> ProjectView.getInstance(myProject)
.getProjectViewPaneById(ImagesProjectViewPane.ID)
.updateFromRoot(true));
}, 1000);
}
}
});
} }
} }

View File

@ -7,11 +7,13 @@ import com.intellij.ide.impl.ProjectViewSelectInTarget;
import com.intellij.ide.projectView.ViewSettings; import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.*; import com.intellij.ide.projectView.impl.*;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtil;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.DefaultTreeModel;
public class ImagesProjectViewPane extends AbstractProjectViewPaneWithAsyncSupport { public class ImagesProjectViewPane extends ProjectViewPane {
public static final String ID = "IMAGES"; public static final String ID = "IMAGES";
@ -70,7 +72,16 @@ public class ImagesProjectViewPane extends AbstractProjectViewPaneWithAsyncSuppo
return new ProjectTreeStructure(myProject, ID) { return new ProjectTreeStructure(myProject, ID) {
@Override @Override
protected ImagesProjectNode createRoot(@NotNull Project project, @NotNull ViewSettings settings) { protected ImagesProjectNode createRoot(@NotNull Project project, @NotNull ViewSettings settings) {
return new ImagesProjectNode(project); return new ImagesProjectNode(project, settings, getProjectDir(project), ImagesProjectViewPane.this);
}
@NotNull
private static VirtualFile getProjectDir(Project project) {
VirtualFile guessedProjectDir = ProjectUtil.guessProjectDir(project);
if (guessedProjectDir == null) {
throw new IllegalStateException("Could not get project directory");
}
return guessedProjectDir;
} }
// Children will be searched in async mode // Children will be searched in async mode