diff --git a/platform/lang-impl/src/com/intellij/packageDependencies/ui/DependenciesPanel.java b/platform/lang-impl/src/com/intellij/packageDependencies/ui/DependenciesPanel.java index 5754e04111c2..449c96eddf0d 100644 --- a/platform/lang-impl/src/com/intellij/packageDependencies/ui/DependenciesPanel.java +++ b/platform/lang-impl/src/com/intellij/packageDependencies/ui/DependenciesPanel.java @@ -374,7 +374,7 @@ public final class DependenciesPanel extends JPanel implements Disposable, DataP Disposer.dispose(disposable); } tree.setModel(model); - monitor.restore(); + monitor.restoreAsync(); } private ActionGroup createTreePopupActions(boolean isRightTree) { diff --git a/platform/platform-impl/src/com/intellij/packageDependencies/ui/TreeExpansionMonitor.java b/platform/platform-impl/src/com/intellij/packageDependencies/ui/TreeExpansionMonitor.java index 9cc090c01cad..55eaa6f76ff1 100644 --- a/platform/platform-impl/src/com/intellij/packageDependencies/ui/TreeExpansionMonitor.java +++ b/platform/platform-impl/src/com/intellij/packageDependencies/ui/TreeExpansionMonitor.java @@ -2,7 +2,9 @@ package com.intellij.packageDependencies.ui; import com.intellij.openapi.util.Comparing; +import com.intellij.ui.tree.TreeVisitor; import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -11,10 +13,12 @@ import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.util.*; import java.util.function.BiPredicate; +import java.util.stream.Collectors; import static com.intellij.ui.tree.TreePathUtil.toTreePathArray; @@ -111,6 +115,11 @@ public abstract class TreeExpansionMonitor { myFrozen = false; } + @ApiStatus.Internal + public void restoreAsync() { + new AsyncRestorer().restore(); + } + protected abstract TreePath findPathByNode(final T node); public boolean isFreeze() { @@ -121,4 +130,67 @@ public abstract class TreeExpansionMonitor { private T getLastNode(@NotNull TreePath path) { return (T)path.getLastPathComponent(); } + + private class AsyncRestorer { + + private final TreeModel initialModel = myTree.getModel(); + + private boolean isValidModel() { + return myTree.getModel() == initialModel; + } + + void restore() { + freeze(); + var nodesToExpand = myExpandedPaths.stream().map(TreeExpansionMonitor.this::getLastNode).collect(Collectors.toUnmodifiableSet()); + var nodesToSelect = mySelectionNodes.stream().collect(Collectors.toUnmodifiableSet()); + var pathsToExpand = new HashSet(); + var pathsToSelect = new HashSet(); + TreeUtil.promiseVisit(myTree, path -> { + if (!isValidModel()) { + return TreeVisitor.Action.INTERRUPT; + } + var node = getLastNode(path); + if (nodesToExpand.contains(node)) { + pathsToExpand.add(path); + } + if (nodesToSelect.contains(node)) { + pathsToSelect.add(path); + } + return TreeVisitor.Action.CONTINUE; + }).onSuccess(ignored -> { + TreeUtil.promiseExpand(myTree, pathsToExpand.stream().map(PathVisitor::new)); + }).onSuccess(ignored -> { + TreeUtil.promiseSelect(myTree, pathsToSelect.stream().map(PathVisitor::new)); + }).onProcessed(ignored -> { + if (isValidModel()) { // otherwise, there's another restoreAsync() running for the new model, it's responsible for unfreezing + unfreeze(); + } + }); + } + + private class PathVisitor implements TreeVisitor { + + private final @NotNull TreePath pathToActUpon; + + PathVisitor(@NotNull TreePath pathToActUpon) { + this.pathToActUpon = pathToActUpon; + } + + @Override + public @NotNull Action visit(@NotNull TreePath path) { + if (!isValidModel()) { + return Action.SKIP_SIBLINGS; + } + if (path.equals(pathToActUpon)) { + return Action.INTERRUPT; + } + else if (path.isDescendant(pathToActUpon)) { + return Action.CONTINUE; + } + else { + return Action.SKIP_CHILDREN; + } + } + } + } }