From b211b4adebb9ed6572b47de9d6d0e422c0ea2d55 Mon Sep 17 00:00:00 2001 From: Sergei Tachenov Date: Fri, 13 Sep 2024 13:50:38 +0300 Subject: [PATCH] IJPL-158493 Extract a superclass from AsyncProjectViewSupport To avoid duplicating a lot of logic in the new coroutine-based implementation, extract the parts of AsyncProjectViewSupport that don't depend on the models into a superclass. Refactor the code around acceptAndUpdate a bit so the only model-dependent part is now in this function with a new signature, so it can be used in all places where a similar logic was used. Other than that, the code was mostly just moved. The part about myNodeUpdater is a bit ugly, as it's now initialized in the derived class and then used in the base class in setupListeners. It's OK, as it's an internal implementation class, it won't have a lot of subclasses, so we're not forcing anyone to figure out this ugliness before using it. GitOrigin-RevId: a8d858c5c579a538a4c917cf830bc0704468f43d --- .../ide/projectView/impl/PackageViewPane.java | 4 +- platform/lang-impl/api-dump-unreviewed.txt | 1 - .../impl/AbstractProjectViewPane.java | 6 +- ...stractProjectViewPaneWithAsyncSupport.java | 6 +- .../impl/AsyncProjectViewSupport.java | 200 ++-------------- .../ide/projectView/impl/ProjectViewPane.java | 4 +- .../impl/ProjectViewPaneSupport.java | 220 ++++++++++++++++++ 7 files changed, 252 insertions(+), 189 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPaneSupport.java diff --git a/java/java-impl/src/com/intellij/ide/projectView/impl/PackageViewPane.java b/java/java-impl/src/com/intellij/ide/projectView/impl/PackageViewPane.java index 503713365c3b..d96e93bba60a 100644 --- a/java/java-impl/src/com/intellij/ide/projectView/impl/PackageViewPane.java +++ b/java/java-impl/src/com/intellij/ide/projectView/impl/PackageViewPane.java @@ -30,6 +30,7 @@ import com.intellij.util.ObjectUtils; import com.intellij.util.PlatformUtils; import com.intellij.util.concurrency.annotations.RequiresBackgroundThread; import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -50,8 +51,9 @@ public class PackageViewPane extends AbstractProjectViewPaneWithAsyncSupport { super(project); } + @ApiStatus.Internal @Override - protected void configureAsyncSupport(@NotNull AsyncProjectViewSupport support) { + protected void configureAsyncSupport(@NotNull ProjectViewPaneSupport support) { support.setMultiSelectionEnabled(false); } diff --git a/platform/lang-impl/api-dump-unreviewed.txt b/platform/lang-impl/api-dump-unreviewed.txt index 1cd77da3ac98..b7547bb3f457 100644 --- a/platform/lang-impl/api-dump-unreviewed.txt +++ b/platform/lang-impl/api-dump-unreviewed.txt @@ -16329,7 +16329,6 @@ c:com.intellij.ide.projectView.impl.ProjectViewPane - sf:ID:java.lang.String - (com.intellij.openapi.project.Project):V - s:canBeSelectedInProjectView(com.intellij.openapi.project.Project,com.intellij.openapi.vfs.VirtualFile):Z -- configureAsyncSupport(com.intellij.ide.projectView.impl.AsyncProjectViewSupport):V - createSelectInTarget():com.intellij.ide.SelectInTarget - p:createStructure():com.intellij.ide.projectView.impl.ProjectAbstractTreeStructureBase - p:createTree(javax.swing.tree.DefaultTreeModel):com.intellij.ide.projectView.impl.ProjectViewTree diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPane.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPane.java index 81e433fd0120..94986349b34b 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPane.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPane.java @@ -222,11 +222,11 @@ public abstract class AbstractProjectViewPane implements UiCompatibleDataProvide public void updateFrom(Object element, boolean forceResort, boolean updateStructure) { if (element instanceof PsiElement) { - AsyncProjectViewSupport support = getAsyncSupport(); + var support = getAsyncSupport(); if (support != null) support.updateByElement((PsiElement)element, updateStructure); } else if (element instanceof TreePath) { - AsyncProjectViewSupport support = getAsyncSupport(); + var support = getAsyncSupport(); if (support != null) support.update((TreePath)element, updateStructure); } } @@ -1116,7 +1116,7 @@ public abstract class AbstractProjectViewPane implements UiCompatibleDataProvide return TreeUtil.getLastUserObject(AbstractTreeNode.class, path); } - AsyncProjectViewSupport getAsyncSupport() { + @Nullable ProjectViewPaneSupport getAsyncSupport() { return null; } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPaneWithAsyncSupport.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPaneWithAsyncSupport.java index 6bef2b41c515..59229d21956b 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPaneWithAsyncSupport.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPaneWithAsyncSupport.java @@ -47,7 +47,7 @@ import java.util.Comparator; import static com.intellij.ide.projectView.ProjectViewSelectionTopicKt.PROJECT_VIEW_SELECTION_TOPIC; public abstract class AbstractProjectViewPaneWithAsyncSupport extends AbstractProjectViewPane { - private AsyncProjectViewSupport myAsyncSupport; + private ProjectViewPaneSupport myAsyncSupport; private JComponent myComponent; protected AbstractProjectViewPaneWithAsyncSupport(@NotNull Project project) { @@ -118,7 +118,7 @@ public abstract class AbstractProjectViewPaneWithAsyncSupport extends AbstractPr } @ApiStatus.Internal - protected void configureAsyncSupport(@NotNull AsyncProjectViewSupport support) { + protected void configureAsyncSupport(@NotNull ProjectViewPaneSupport support) { } @Override @@ -236,7 +236,7 @@ public abstract class AbstractProjectViewPaneWithAsyncSupport extends AbstractPr } @Override - AsyncProjectViewSupport getAsyncSupport() { + ProjectViewPaneSupport getAsyncSupport() { return myAsyncSupport; } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java index cffede59b015..19466a289e28 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AsyncProjectViewSupport.java @@ -1,39 +1,25 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.ide.projectView.impl; -import com.intellij.ide.CopyPasteUtil; -import com.intellij.ide.bookmark.BookmarksListener; -import com.intellij.ide.bookmark.FileBookmarksListener; -import com.intellij.ide.projectView.ProjectViewPsiTreeChangeListener; -import com.intellij.ide.projectView.impl.ProjectViewPaneSelectionHelper.SelectionDescriptor; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.ide.util.treeView.AbstractTreeStructure; import com.intellij.ide.util.treeView.NodeDescriptor; import com.intellij.openapi.Disposable; import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.fileEditor.FileEditorManager; -import com.intellij.openapi.fileEditor.FileEditorManagerListener; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.vcs.FileStatusListener; -import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.problems.ProblemListener; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiManager; -import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.tree.*; import com.intellij.ui.tree.project.ProjectFileNodeUpdater; import com.intellij.util.SmartList; -import com.intellij.util.messages.MessageBusConnection; -import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; -import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import java.util.Comparator; import java.util.List; @@ -44,12 +30,10 @@ import static com.intellij.ide.util.treeView.TreeState.expand; import static com.intellij.ui.tree.project.ProjectFileNode.findArea; @ApiStatus.Internal -public final class AsyncProjectViewSupport { +public final class AsyncProjectViewSupport extends ProjectViewPaneSupport { private static final Logger LOG = Logger.getInstance(AsyncProjectViewSupport.class); - private final ProjectFileNodeUpdater myNodeUpdater; private final StructureTreeModel myStructureTreeModel; private final AsyncTreeModel myAsyncTreeModel; - private boolean myMultiSelectionEnabled = true; public AsyncProjectViewSupport(@NotNull Disposable parent, @NotNull Project project, @@ -77,84 +61,20 @@ public final class AsyncProjectViewSupport { } } }; - MessageBusConnection connection = project.getMessageBus().connect(parent); - connection.subscribe(BookmarksListener.TOPIC, new FileBookmarksListener(file -> updateByFile(file, !file.isDirectory()))); - PsiManager.getInstance(project).addPsiTreeChangeListener(new ProjectViewPsiTreeChangeListener(project) { - @Override - protected boolean isFlattenPackages() { - return structure instanceof AbstractProjectTreeStructure && ((AbstractProjectTreeStructure)structure).isFlattenPackages(); - } - - @Override - protected DefaultMutableTreeNode getRootNode() { - return null; - } - - @Override - protected void addSubtreeToUpdateByRoot() { - myNodeUpdater.updateFromRoot(); - } - - @Override - protected boolean addSubtreeToUpdateByElement(@NotNull PsiElement element) { - VirtualFile file = PsiUtilCore.getVirtualFile(element); - if (file != null) { - myNodeUpdater.updateFromFile(file); - } - else { - updateByElement(element, true); - } - return true; - } - }, parent); - FileStatusManager.getInstance(project).addFileStatusListener(new FileStatusListener() { - @Override - public void fileStatusesChanged() { - updateAllPresentations(); - } - - @Override - public void fileStatusChanged(@NotNull VirtualFile file) { - updateByFile(file, false); - } - }, parent); - CopyPasteUtil.addDefaultListener(parent, element -> updateByElement(element, false)); - project.getMessageBus().connect(parent).subscribe(ProblemListener.TOPIC, new ProblemListener() { - @Override - public void problemsAppeared(@NotNull VirtualFile file) { - updatePresentationsFromRootTo(file); - } - - @Override - public void problemsDisappeared(@NotNull VirtualFile file) { - updatePresentationsFromRootTo(file); - } - }); - project.getMessageBus().connect(parent).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { - - // structure = true because files may have children too, e.g. if the Show Members option is selected, and children inherit file colors - - @Override - public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { - updateByFile(file, true); - } - - @Override - public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) { - updateByFile(file, true); - } - }); + setupListeners(parent, project, structure); } public AsyncTreeModel getTreeModel() { return myAsyncTreeModel; } - public void setComparator(@NotNull Comparator> comparator) { + @Override + public void setComparator(@Nullable Comparator> comparator) { myStructureTreeModel.setComparator(comparator); } - public ActionCallback select(JTree tree, Object object, VirtualFile file) { + @Override + public @NotNull ActionCallback select(@NotNull JTree tree, @Nullable Object object, @Nullable VirtualFile file) { if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { SelectInProjectViewImplKt.getLOG().debug( "AsyncProjectViewSupport.select: " + @@ -230,45 +150,7 @@ public final class AsyncProjectViewSupport { myAsyncTreeModel.accept(visitor).onProcessed(path -> myAsyncTreeModel.onValidThread(task)); } - public void setMultiSelectionEnabled(boolean enabled) { - myMultiSelectionEnabled = enabled; - } - - private boolean selectPaths(@NotNull JTree tree, @NotNull List paths, @NotNull TreeVisitor visitor) { - if (paths.isEmpty()) { - SelectInProjectViewImplKt.getLOG().debug("Nothing to select"); - return false; - } - if (paths.size() > 1 && myMultiSelectionEnabled) { - if (visitor instanceof ProjectViewNodeVisitor nodeVisitor) { - return selectPaths(tree, new SelectionDescriptor(nodeVisitor.getElement(), nodeVisitor.getFile(), paths)); - } - if (visitor instanceof ProjectViewFileVisitor fileVisitor) { - return selectPaths(tree, new SelectionDescriptor(null, fileVisitor.getElement(), paths)); - } - } - if (!myMultiSelectionEnabled) { - SelectInProjectViewImplKt.getLOG().debug("Selecting only the first path because multi-selection is disabled"); - } - TreePath path = paths.get(0); - tree.expandPath(path); // request to expand found path - TreeUtil.selectPaths(tree, path); // select and scroll to center - if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { - SelectInProjectViewImplKt.getLOG().debug("Selected the only path: " + path); - } - return true; - } - - private static boolean selectPaths(@NotNull JTree tree, @NotNull SelectionDescriptor selectionDescriptor) { - List adjustedPaths = ProjectViewPaneSelectionHelper.getAdjustedPaths(selectionDescriptor); - adjustedPaths.forEach(it -> tree.expandPath(it)); - TreeUtil.selectPaths(tree, adjustedPaths); - if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { - SelectInProjectViewImplKt.getLOG().debug("Selected paths adjusted according to " + selectionDescriptor + ": " + adjustedPaths); - } - return true; - } - + @Override public void updateAll(Runnable onDone) { LOG.debug(new RuntimeException("reload a whole tree")); CompletableFuture future = myStructureTreeModel.invalidateAsync(); @@ -277,67 +159,25 @@ public final class AsyncProjectViewSupport { } } + @Override public void update(@NotNull TreePath path, boolean structure) { myStructureTreeModel.invalidate(path, structure); } - public void update(@NotNull List list, boolean structure) { - for (TreePath path : list) update(path, structure); - } - - public void updateByFile(@NotNull VirtualFile file, boolean structure) { - LOG.debug(structure ? "updateChildrenByFile: " : "updatePresentationByFile: ", file); - update(null, file, structure); - } - - public void updateByElement(@NotNull PsiElement element, boolean structure) { - LOG.debug(structure ? "updateChildrenByElement: " : "updatePresentationByElement: ", element); - update(element, null, structure); - } - - private void update(PsiElement element, VirtualFile file, boolean structure) { - SmartList list = new SmartList<>(); - TreeVisitor visitor = AbstractProjectViewPane.createVisitor(element, file, list); - if (visitor != null) acceptAndUpdate(visitor, list, structure); - } - - private void acceptAndUpdate(@NotNull TreeVisitor visitor, List list, boolean structure) { - myAsyncTreeModel.accept(visitor, false).onSuccess(path -> update(list, structure)); - } - - private void updatePresentationsFromRootTo(@NotNull VirtualFile file) { - // find first valid parent for removed file - while (!file.isValid()) { - file = file.getParent(); - if (file == null) return; - } - SmartList structures = new SmartList<>(); - SmartList presentations = new SmartList<>(); - myAsyncTreeModel.accept(new ProjectViewFileVisitor(file, structures::add) { - @Override - protected @NotNull Action visit(@NotNull TreePath path, @NotNull AbstractTreeNode node, @NotNull VirtualFile element) { - Action action = super.visit(path, node, element); - if (action == Action.CONTINUE) presentations.add(path); - return action; - } - }, false).onSuccess(path -> { - update(presentations, false); - update(structures, true); + @Override + protected void acceptAndUpdate( + @NotNull TreeVisitor visitor, + @Nullable List presentations, + @Nullable List structures + ) { + myAsyncTreeModel.accept(visitor, false).onSuccess(path -> { + if (presentations != null) update(presentations, false); + if (structures != null) update(structures, true); }); } - private void updateAllPresentations() { - SmartList list = new SmartList<>(); - acceptAndUpdate(new TreeVisitor() { - @Override - public @NotNull Action visit(@NotNull TreePath path) { - list.add(path); - return Action.CONTINUE; - } - }, list, false); - } - - void setModelTo(@NotNull JTree tree) { + @Override + public void setModelTo(@NotNull JTree tree) { RestoreSelectionListener listener = new RestoreSelectionListener(); tree.addTreeSelectionListener(listener); tree.setModel(myAsyncTreeModel); diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPane.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPane.java index 91ffced2ac37..a1b80fd19e35 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPane.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPane.java @@ -24,6 +24,7 @@ import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.ArchiveFileSystem; import com.intellij.util.PlatformUtils; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -44,8 +45,9 @@ public class ProjectViewPane extends AbstractProjectViewPaneWithAsyncSupport { super(project); } + @ApiStatus.Internal @Override - public void configureAsyncSupport(@NotNull AsyncProjectViewSupport support) { + public void configureAsyncSupport(@NotNull ProjectViewPaneSupport support) { support.setMultiSelectionEnabled(false); } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPaneSupport.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPaneSupport.java new file mode 100644 index 000000000000..0475d8a0020a --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/ProjectViewPaneSupport.java @@ -0,0 +1,220 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.ide.projectView.impl; + +import com.intellij.ide.CopyPasteUtil; +import com.intellij.ide.bookmark.BookmarksListener; +import com.intellij.ide.bookmark.FileBookmarksListener; +import com.intellij.ide.projectView.ProjectViewPsiTreeChangeListener; +import com.intellij.ide.util.treeView.AbstractTreeNode; +import com.intellij.ide.util.treeView.AbstractTreeStructure; +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.FileEditorManagerListener; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.ActionCallback; +import com.intellij.openapi.vcs.FileStatusListener; +import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.problems.ProblemListener; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiManager; +import com.intellij.psi.util.PsiUtilCore; +import com.intellij.ui.tree.TreeVisitor; +import com.intellij.ui.tree.project.ProjectFileNodeUpdater; +import com.intellij.util.SmartList; +import com.intellij.util.messages.MessageBusConnection; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreePath; +import java.util.Comparator; +import java.util.List; + +@ApiStatus.Internal +public abstract class ProjectViewPaneSupport { + private static final Logger LOG = Logger.getInstance(ProjectViewPaneSupport.class); + + private boolean myMultiSelectionEnabled = true; + protected ProjectFileNodeUpdater myNodeUpdater; + + protected void setupListeners(@NotNull Disposable parent, @NotNull Project project, @NotNull AbstractTreeStructure structure) { + MessageBusConnection connection = project.getMessageBus().connect(parent); + connection.subscribe(BookmarksListener.TOPIC, new FileBookmarksListener(file -> updateByFile(file, !file.isDirectory()))); + PsiManager.getInstance(project).addPsiTreeChangeListener(new ProjectViewPsiTreeChangeListener(project) { + @Override + protected boolean isFlattenPackages() { + return structure instanceof AbstractProjectTreeStructure && ((AbstractProjectTreeStructure)structure).isFlattenPackages(); + } + + @Override + protected DefaultMutableTreeNode getRootNode() { + return null; + } + + @Override + protected void addSubtreeToUpdateByRoot() { + myNodeUpdater.updateFromRoot(); + } + + @Override + protected boolean addSubtreeToUpdateByElement(@NotNull PsiElement element) { + VirtualFile file = PsiUtilCore.getVirtualFile(element); + if (file != null) { + myNodeUpdater.updateFromFile(file); + } + else { + updateByElement(element, true); + } + return true; + } + }, parent); + FileStatusManager.getInstance(project).addFileStatusListener(new FileStatusListener() { + @Override + public void fileStatusesChanged() { + updateAllPresentations(); + } + + @Override + public void fileStatusChanged(@NotNull VirtualFile file) { + updateByFile(file, false); + } + }, parent); + CopyPasteUtil.addDefaultListener(parent, element -> updateByElement(element, false)); + project.getMessageBus().connect(parent).subscribe(ProblemListener.TOPIC, new ProblemListener() { + @Override + public void problemsAppeared(@NotNull VirtualFile file) { + updatePresentationsFromRootTo(file); + } + + @Override + public void problemsDisappeared(@NotNull VirtualFile file) { + updatePresentationsFromRootTo(file); + } + }); + project.getMessageBus().connect(parent).subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, new FileEditorManagerListener() { + + // structure = true because files may have children too, e.g. if the Show Members option is selected, and children inherit file colors + + @Override + public void fileOpened(@NotNull FileEditorManager source, @NotNull VirtualFile file) { + updateByFile(file, true); + } + + @Override + public void fileClosed(@NotNull FileEditorManager source, @NotNull VirtualFile file) { + updateByFile(file, true); + } + }); + } + + public abstract void setModelTo(@NotNull JTree tree); + + public abstract void setComparator(@Nullable Comparator> comparator); + + public void setMultiSelectionEnabled(boolean enabled) { + myMultiSelectionEnabled = enabled; + } + + public abstract void updateAll(Runnable onDone); + + public void update(@NotNull List list, boolean structure) { + for (TreePath path : list) update(path, structure); + } + + public abstract void update(@NotNull TreePath path, boolean structure); + + public void updateByFile(@NotNull VirtualFile file, boolean structure) { + LOG.debug(structure ? "updateChildrenByFile: " : "updatePresentationByFile: ", file); + update(null, file, structure); + } + + public void updateByElement(@NotNull PsiElement element, boolean structure) { + LOG.debug(structure ? "updateChildrenByElement: " : "updatePresentationByElement: ", element); + update(element, null, structure); + } + + protected void update(@Nullable PsiElement element, @Nullable VirtualFile file, boolean structure) { + SmartList list = new SmartList<>(); + TreeVisitor visitor = AbstractProjectViewPane.createVisitor(element, file, list); + if (visitor != null) acceptAndUpdate(visitor, structure ? null : list, structure ? list : null); + } + + protected void updateAllPresentations() { + SmartList list = new SmartList<>(); + acceptAndUpdate(new TreeVisitor() { + @Override + public @NotNull Action visit(@NotNull TreePath path) { + list.add(path); + return Action.CONTINUE; + } + }, list, null); + } + + protected void updatePresentationsFromRootTo(@NotNull VirtualFile file) { + // find first valid parent for removed file + while (!file.isValid()) { + file = file.getParent(); + if (file == null) return; + } + SmartList structures = new SmartList<>(); + SmartList presentations = new SmartList<>(); + var visitor = new ProjectViewFileVisitor(file, structures::add) { + @Override + protected @NotNull Action visit(@NotNull TreePath path, @NotNull AbstractTreeNode node, @NotNull VirtualFile element) { + Action action = super.visit(path, node, element); + if (action == Action.CONTINUE) presentations.add(path); + return action; + } + }; + acceptAndUpdate(visitor, presentations, structures); + } + + protected abstract void acceptAndUpdate( + @NotNull TreeVisitor visitor, + @Nullable List presentations, + @Nullable List structures + ); + + public abstract @NotNull ActionCallback select(@NotNull JTree tree, @Nullable Object object, @Nullable VirtualFile file); + + protected boolean selectPaths(@NotNull JTree tree, @NotNull List paths, @NotNull TreeVisitor visitor) { + if (paths.isEmpty()) { + SelectInProjectViewImplKt.getLOG().debug("Nothing to select"); + return false; + } + if (paths.size() > 1 && myMultiSelectionEnabled) { + if (visitor instanceof ProjectViewNodeVisitor nodeVisitor) { + return selectPaths(tree, new ProjectViewPaneSelectionHelper.SelectionDescriptor(nodeVisitor.getElement(), nodeVisitor.getFile(), paths)); + } + if (visitor instanceof ProjectViewFileVisitor fileVisitor) { + return selectPaths(tree, new ProjectViewPaneSelectionHelper.SelectionDescriptor(null, fileVisitor.getElement(), paths)); + } + } + if (!myMultiSelectionEnabled) { + SelectInProjectViewImplKt.getLOG().debug("Selecting only the first path because multi-selection is disabled"); + } + TreePath path = paths.get(0); + tree.expandPath(path); // request to expand found path + TreeUtil.selectPaths(tree, path); // select and scroll to center + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Selected the only path: " + path); + } + return true; + } + + protected static boolean selectPaths(@NotNull JTree tree, @NotNull ProjectViewPaneSelectionHelper.SelectionDescriptor selectionDescriptor) { + List adjustedPaths = ProjectViewPaneSelectionHelper.getAdjustedPaths(selectionDescriptor); + adjustedPaths.forEach(it -> tree.expandPath(it)); + TreeUtil.selectPaths(tree, adjustedPaths); + if (SelectInProjectViewImplKt.getLOG().isDebugEnabled()) { + SelectInProjectViewImplKt.getLOG().debug("Selected paths adjusted according to " + selectionDescriptor + ": " + adjustedPaths); + } + return true; + } +}