From 1f3bbf958022c0920a8d5a9a832be7c79b3e43a8 Mon Sep 17 00:00:00 2001 From: Sergei Tachenov Date: Fri, 13 Sep 2024 15:05:12 +0300 Subject: [PATCH] IJPL-158493 Reduce dependency on AsyncTreeModel Before migrating the Project View to the new models, we need to take care of existing code that relies on the exact implementation class. For "Can this thing calculate nodes on a BGT?" instanceof checks, introduce a new marker interface, BgtAwareTreeModel. We may add methods such as isProcessing() there later. Replace existing usages with the new interface. For "Can this thing accept a visitor on a BGT?" instanceof checks, introduce TreeVisitor.LoadingAwareAcceptor with the second accept() overload that controls node loading. Replace existing usages. GitOrigin-RevId: 67905cc6873aab2f917ee2028d0a167f60fa85f7 --- .../ui/classpath/ChooseLibrariesDialogBase.java | 2 +- platform/editor-ui-api/api-dump.txt | 4 ++++ .../src/com/intellij/ui/tree/TreeVisitor.java | 17 +++++++++++++++++ platform/lang-impl/exposed-private-api.txt | 1 - .../ide/bookmark/ui/tree/FolderNodeUpdater.kt | 4 ++-- .../impl/AbstractProjectViewPane.java | 3 ++- .../src/com/intellij/ide/todo/TodoPanel.java | 2 +- platform/platform-api/api-dump-unreviewed.txt | 2 ++ .../ui/treeStructure/BgtAwareTreeModel.java | 9 +++++++++ platform/platform-impl/api-dump-unreviewed.txt | 3 ++- .../com/intellij/ui/tree/AsyncTreeModel.java | 6 +++++- .../com/intellij/ui/tree/ui/DefaultTreeUI.java | 10 +++++----- .../structure/MavenProjectsStructure.java | 2 +- 13 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 platform/platform-api/src/com/intellij/ui/treeStructure/BgtAwareTreeModel.java diff --git a/java/idea-ui/src/com/intellij/util/ui/classpath/ChooseLibrariesDialogBase.java b/java/idea-ui/src/com/intellij/util/ui/classpath/ChooseLibrariesDialogBase.java index 8cc1d42ebea1..ec719b74b759 100644 --- a/java/idea-ui/src/com/intellij/util/ui/classpath/ChooseLibrariesDialogBase.java +++ b/java/idea-ui/src/com/intellij/util/ui/classpath/ChooseLibrariesDialogBase.java @@ -123,7 +123,7 @@ public abstract class ChooseLibrariesDialogBase extends DialogWrapper { protected void queueUpdateAndSelect(@NotNull final Library library) { myModel.invalidateAsync().thenRun(() -> { - ((AsyncTreeModel)myTree.getModel()).accept(path -> { + ((TreeVisitor.Acceptor)myTree.getModel()).accept(path -> { return TreeVisitor.Action.CONTINUE; // traverse to update myParentsMap }).onProcessed(path -> { myModel.select(library, myTree, p -> {}); diff --git a/platform/editor-ui-api/api-dump.txt b/platform/editor-ui-api/api-dump.txt index 0ce8da202562..6a7f5115da72 100644 --- a/platform/editor-ui-api/api-dump.txt +++ b/platform/editor-ui-api/api-dump.txt @@ -3028,6 +3028,10 @@ c:com.intellij.ui.tree.TreeVisitor$ByTreePath - visit(javax.swing.tree.TreePath):com.intellij.ui.tree.TreeVisitor$Action - p:visit(javax.swing.tree.TreePath,java.lang.Object):com.intellij.ui.tree.TreeVisitor$Action - p:visit(javax.swing.tree.TreePath,java.lang.Object,I):com.intellij.ui.tree.TreeVisitor$Action +com.intellij.ui.tree.TreeVisitor$LoadingAwareAcceptor +- com.intellij.ui.tree.TreeVisitor$Acceptor +- accept(com.intellij.ui.tree.TreeVisitor):org.jetbrains.concurrency.Promise +- a:accept(com.intellij.ui.tree.TreeVisitor,Z):org.jetbrains.concurrency.Promise *e:com.intellij.ui.tree.TreeVisitor$VisitThread - java.lang.Enum - sf:BGT:com.intellij.ui.tree.TreeVisitor$VisitThread diff --git a/platform/editor-ui-api/src/com/intellij/ui/tree/TreeVisitor.java b/platform/editor-ui-api/src/com/intellij/ui/tree/TreeVisitor.java index 8dde253f426c..b72473b72e98 100644 --- a/platform/editor-ui-api/src/com/intellij/ui/tree/TreeVisitor.java +++ b/platform/editor-ui-api/src/com/intellij/ui/tree/TreeVisitor.java @@ -71,6 +71,23 @@ public interface TreeVisitor { Promise accept(@NotNull TreeVisitor visitor); } + /** + * Represents a tree model that accepts a tree visitor and promises a result, optionally allowing to skip not loaded nodes. + */ + interface LoadingAwareAcceptor extends Acceptor { + @Override + default @NotNull Promise accept(@NotNull TreeVisitor visitor) { + return accept(visitor, true); + } + + /** + * @param visitor an object that controls visiting a tree structure + * @param allowLoading a flag that determines whether the nodes that weren't loaded yet will be loaded and visited or skipped + * @return a promise that will be resolved when visiting is finished + */ + @NotNull + Promise accept(@NotNull TreeVisitor visitor, boolean allowLoading); + } abstract class ByComponent implements TreeVisitor { private final Function converter; diff --git a/platform/lang-impl/exposed-private-api.txt b/platform/lang-impl/exposed-private-api.txt index 4a459e89136e..6b7b504011df 100644 --- a/platform/lang-impl/exposed-private-api.txt +++ b/platform/lang-impl/exposed-private-api.txt @@ -21,7 +21,6 @@ com/intellij/ide/actions/searcheverywhere/SearchEverywhereSpellingCorrector com/intellij/ide/actions/searcheverywhere/SearchListModel com/intellij/ide/plugins/ContainerDescriptor com/intellij/ide/plugins/IdeaPluginDescriptorImpl -com/intellij/ide/projectView/impl/AsyncProjectViewSupport com/intellij/ide/todo/FileTree com/intellij/ide/todo/TodoNodeVisitor com/intellij/ide/todo/TodoTreeBuilderCoroutineHelper diff --git a/platform/lang-impl/src/com/intellij/ide/bookmark/ui/tree/FolderNodeUpdater.kt b/platform/lang-impl/src/com/intellij/ide/bookmark/ui/tree/FolderNodeUpdater.kt index 61639cb1239b..d4744bd1c5c5 100644 --- a/platform/lang-impl/src/com/intellij/ide/bookmark/ui/tree/FolderNodeUpdater.kt +++ b/platform/lang-impl/src/com/intellij/ide/bookmark/ui/tree/FolderNodeUpdater.kt @@ -6,8 +6,8 @@ import com.intellij.ide.bookmark.FileBookmark import com.intellij.ide.bookmark.ui.BookmarksView import com.intellij.openapi.vfs.VfsUtilCore.isAncestor import com.intellij.openapi.vfs.VirtualFile -import com.intellij.ui.tree.AsyncTreeModel import com.intellij.ui.tree.TreeCollector +import com.intellij.ui.tree.TreeVisitor import com.intellij.ui.tree.project.ProjectFileNodeUpdater import javax.swing.tree.TreePath @@ -42,7 +42,7 @@ internal class FolderNodeUpdater(val view: BookmarksView) : ProjectFileNodeUpdat } private fun forEachTreePath(file: VirtualFile, action: (TreePath) -> Unit) { - val model = view.tree.model as? AsyncTreeModel ?: return + val model = view.tree.model as? TreeVisitor.LoadingAwareAcceptor ?: return val paths = mutableListOf() model.accept(VirtualFileVisitor(file, paths), false).onSuccess { paths.forEach(action) } } 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 94986349b34b..63801363bf8e 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 @@ -38,6 +38,7 @@ import com.intellij.ui.tree.AsyncTreeModel; import com.intellij.ui.tree.TreePathUtil; import com.intellij.ui.tree.TreeVisitor; import com.intellij.ui.tree.project.ProjectFileNode; +import com.intellij.ui.treeStructure.BgtAwareTreeModel; import com.intellij.ui.treeStructure.TreeStateListener; import com.intellij.util.ArrayUtil; import com.intellij.util.ArrayUtilRt; @@ -637,7 +638,7 @@ public abstract class AbstractProjectViewPane implements UiCompatibleDataProvide private boolean isExpandAllAllowed() { JTree tree = getTree(); TreeModel model = tree == null ? null : tree.getModel(); - return model == null || model instanceof AsyncTreeModel || model instanceof InvokerSupplier; + return model == null || model instanceof BgtAwareTreeModel || model instanceof InvokerSupplier; } @Override diff --git a/platform/lang-impl/src/com/intellij/ide/todo/TodoPanel.java b/platform/lang-impl/src/com/intellij/ide/todo/TodoPanel.java index 5f9caab2ad22..eb41a8ce01ce 100644 --- a/platform/lang-impl/src/com/intellij/ide/todo/TodoPanel.java +++ b/platform/lang-impl/src/com/intellij/ide/todo/TodoPanel.java @@ -143,7 +143,7 @@ public abstract class TodoPanel extends SimpleToolWindowPanel implements Occuren TodoView todoView = this.myProject.getService(TodoView.class); todoView.setSelectedContent(this); - AsyncTreeModel model = (AsyncTreeModel)myTree.getModel(); + var model = (TreeVisitor.Acceptor)myTree.getModel(); model.accept(new TreeVisitor() { @Override public @NotNull Action visit(@NotNull TreePath path) { diff --git a/platform/platform-api/api-dump-unreviewed.txt b/platform/platform-api/api-dump-unreviewed.txt index e98e0f0d19d5..1c65e2e7d2d9 100644 --- a/platform/platform-api/api-dump-unreviewed.txt +++ b/platform/platform-api/api-dump-unreviewed.txt @@ -10908,6 +10908,8 @@ c:com.intellij.ui.treeStructure.AutoExpandSimpleNodeListener - com.intellij.util.ui.tree.TreeModelAdapter - (javax.swing.JTree):V - treeNodesInserted(javax.swing.event.TreeModelEvent):V +com.intellij.ui.treeStructure.BgtAwareTreeModel +- javax.swing.tree.TreeModel a:com.intellij.ui.treeStructure.CachingSimpleNode - com.intellij.ui.treeStructure.SimpleNode - p:(com.intellij.openapi.project.Project,com.intellij.ide.util.treeView.NodeDescriptor):V diff --git a/platform/platform-api/src/com/intellij/ui/treeStructure/BgtAwareTreeModel.java b/platform/platform-api/src/com/intellij/ui/treeStructure/BgtAwareTreeModel.java new file mode 100644 index 000000000000..4c3e79337665 --- /dev/null +++ b/platform/platform-api/src/com/intellij/ui/treeStructure/BgtAwareTreeModel.java @@ -0,0 +1,9 @@ +// 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.ui.treeStructure; + +import javax.swing.tree.TreeModel; + +/** + * Marker interface to indicate that the model can load nodes in the background without freezing the EDT + */ +public interface BgtAwareTreeModel extends TreeModel { } diff --git a/platform/platform-impl/api-dump-unreviewed.txt b/platform/platform-impl/api-dump-unreviewed.txt index c67082a411c1..8e827e838826 100644 --- a/platform/platform-impl/api-dump-unreviewed.txt +++ b/platform/platform-impl/api-dump-unreviewed.txt @@ -25514,7 +25514,8 @@ a:com.intellij.ui.tree.AbstractTreeWalker - start(javax.swing.tree.TreePath,java.lang.Object):V f:com.intellij.ui.tree.AsyncTreeModel - com.intellij.ui.tree.Searchable -- com.intellij.ui.tree.TreeVisitor$Acceptor +- com.intellij.ui.tree.TreeVisitor$LoadingAwareAcceptor +- com.intellij.ui.treeStructure.BgtAwareTreeModel - com.intellij.util.ui.tree.AbstractTreeModel - (javax.swing.tree.TreeModel,com.intellij.openapi.Disposable):V - (javax.swing.tree.TreeModel,Z,com.intellij.openapi.Disposable):V diff --git a/platform/platform-impl/src/com/intellij/ui/tree/AsyncTreeModel.java b/platform/platform-impl/src/com/intellij/ui/tree/AsyncTreeModel.java index 5d4b6c607d5e..c56687b5e15c 100644 --- a/platform/platform-impl/src/com/intellij/ui/tree/AsyncTreeModel.java +++ b/platform/platform-impl/src/com/intellij/ui/tree/AsyncTreeModel.java @@ -11,6 +11,7 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Disposer; import com.intellij.ui.LoadingNode; +import com.intellij.ui.treeStructure.BgtAwareTreeModel; import com.intellij.ui.treeStructure.CachingTreePath; import com.intellij.util.concurrency.Invoker; import com.intellij.util.concurrency.InvokerSupplier; @@ -40,7 +41,9 @@ import static java.util.Collections.emptyList; import static org.jetbrains.concurrency.Promises.rejectedPromise; import static org.jetbrains.concurrency.Promises.resolvedPromise; -public final class AsyncTreeModel extends AbstractTreeModel implements Searchable, TreeVisitor.Acceptor, CachedTreePresentationSupport { +public final class AsyncTreeModel extends AbstractTreeModel + implements Searchable, TreeVisitor.LoadingAwareAcceptor, CachedTreePresentationSupport, BgtAwareTreeModel +{ private static final Logger LOG = Logger.getInstance(AsyncTreeModel.class); private final Invoker foreground; private final Invoker background; @@ -236,6 +239,7 @@ public final class AsyncTreeModel extends AbstractTreeModel implements Searchabl * @param allowLoading load all needed children if {@code true} * @return a promise that will be resolved when visiting is finished */ + @Override public @NotNull Promise accept(@NotNull TreeVisitor visitor, boolean allowLoading) { var walker = createWalker(visitor, allowLoading); if (allowLoading) { diff --git a/platform/platform-impl/src/com/intellij/ui/tree/ui/DefaultTreeUI.java b/platform/platform-impl/src/com/intellij/ui/tree/ui/DefaultTreeUI.java index f03e32eb1a36..d19b2a99c988 100644 --- a/platform/platform-impl/src/com/intellij/ui/tree/ui/DefaultTreeUI.java +++ b/platform/platform-impl/src/com/intellij/ui/tree/ui/DefaultTreeUI.java @@ -11,8 +11,8 @@ import com.intellij.ui.*; import com.intellij.ui.hover.TreeHoverListener; import com.intellij.ui.render.RenderingHelper; import com.intellij.ui.render.RenderingUtil; -import com.intellij.ui.tree.AsyncTreeModel; import com.intellij.ui.tree.TreePathBackgroundSupplier; +import com.intellij.ui.treeStructure.BgtAwareTreeModel; import com.intellij.ui.treeStructure.Tree; import com.intellij.ui.treeStructure.TreeUiBulkExpandCollapseSupport; import com.intellij.util.ObjectUtils; @@ -602,7 +602,7 @@ public class DefaultTreeUI extends BasicTreeUI implements TreeUiBulkExpandCollap @Override protected void setRootVisible(boolean newValue) { - if (treeModel instanceof AsyncTreeModel) { + if (treeModel instanceof BgtAwareTreeModel) { // this method must be called on EDT to be consistent with ATM, // because it modifies a list of visible nodes in the layout cache EdtInvocationManager.invokeLaterIfNeeded(() -> super.setRootVisible(newValue)); @@ -720,7 +720,7 @@ public class DefaultTreeUI extends BasicTreeUI implements TreeUiBulkExpandCollap JTree tree = getTree(); if (!shouldAutoExpand(tree, path)) return; TreeModel model = tree.getModel(); - if (model instanceof AsyncTreeModel && 1 == model.getChildCount(path.getLastPathComponent())) { + if (model instanceof BgtAwareTreeModel && 1 == model.getChildCount(path.getLastPathComponent())) { int pathCount = 1 + path.getPathCount(); for (int i = 0; i <= oldRowCount; i++) { TreePath row = getPathForRow(i); @@ -799,10 +799,10 @@ public class DefaultTreeUI extends BasicTreeUI implements TreeUiBulkExpandCollap if (!shouldAutoExpand(tree, row.getParentPath())) { return; } - if (tree.getModel() instanceof AsyncTreeModel asyncTreeModel) { + if (tree.getModel() instanceof BgtAwareTreeModel) { Object node = row.getLastPathComponent(); if (isAutoExpandAllowed(tree, node)) { - asyncTreeModel.onValidThread(() -> tree.expandPath(row)); + EdtInvocationManager.invokeLaterIfNeeded(() -> tree.expandPath(row)); } } } diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/navigator/structure/MavenProjectsStructure.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/navigator/structure/MavenProjectsStructure.java index 7db22327920d..7864fe30c10d 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/navigator/structure/MavenProjectsStructure.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/navigator/structure/MavenProjectsStructure.java @@ -236,7 +236,7 @@ public class MavenProjectsStructure extends SimpleTreeStructure { } public void accept(@NotNull TreeVisitor visitor) { - ((AsyncTreeModel)myTree.getModel()).accept(visitor); + ((TreeVisitor.Acceptor)myTree.getModel()).accept(visitor); } public void updateGoals() {