mirror of
https://github.com/JetBrains/intellij-sdk-code-samples.git
synced 2025-07-27 16:57:49 +08:00
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:
parent
d7dd55a7e0
commit
7ffca8f009
@ -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));
|
||||||
|
|
||||||
try {
|
|
||||||
final Collection<VirtualFile> files = ReadAction.compute(() -> FilenameIndex.getAllFilesByExt(project, ext));
|
|
||||||
|
|
||||||
for (VirtualFile file : files) {
|
for (VirtualFile file : files) {
|
||||||
while (file != null && !file.equals(projectDir)) {
|
while (file != null && !file.equals(projectDir)) {
|
||||||
imagesFiles.add(file);
|
imagesFiles.add(file);
|
||||||
file = file.getParent();
|
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
|
@Override
|
||||||
protected VirtualFile getVirtualFile() {
|
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
|
||||||
|
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) {
|
|
||||||
final Alarm alarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, project);
|
|
||||||
LocalFileSystem.getInstance().addVirtualFileListener(new VirtualFileListener() {
|
|
||||||
{
|
|
||||||
final VirtualFileListener me = this;
|
|
||||||
Disposer.register(project, () -> LocalFileSystem.getInstance().removeVirtualFileListener(me));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fileCreated(@NotNull VirtualFileEvent event) {
|
public int getTypeSortWeight(boolean sortByType) {
|
||||||
handle(event);
|
// required for "Folder Always on Top"
|
||||||
}
|
return getVirtualFile().isDirectory() ? 1 : 0;
|
||||||
|
|
||||||
@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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user