From 4f5cb24fe003123b5d593a7954a7b9880cedd216 Mon Sep 17 00:00:00 2001 From: Dmitry Kozhevnikov Date: Tue, 24 Apr 2018 21:02:18 +0300 Subject: [PATCH] CPP-8459: Calculate controls state in background --- .../ide/OccurenceNavigatorSupport.java | 165 ++++++++++++------ .../usages/actions/RerunSearchAction.java | 2 +- .../intellij/usages/impl/UsageViewImpl.java | 98 +++++++++-- 3 files changed, 195 insertions(+), 70 deletions(-) diff --git a/platform/platform-api/src/com/intellij/ide/OccurenceNavigatorSupport.java b/platform/platform-api/src/com/intellij/ide/OccurenceNavigatorSupport.java index 0bcd1a185ec5..2964475b08d3 100644 --- a/platform/platform-api/src/com/intellij/ide/OccurenceNavigatorSupport.java +++ b/platform/platform-api/src/com/intellij/ide/OccurenceNavigatorSupport.java @@ -15,8 +15,11 @@ */ package com.intellij.ide; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.pom.Navigatable; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,6 +41,13 @@ public abstract class OccurenceNavigatorSupport implements OccurenceNavigator { @Nullable protected abstract Navigatable createDescriptorForNode(DefaultMutableTreeNode node); + @Nullable + private Navigatable createDescriptorAndCheckCanNavigate(DefaultMutableTreeNode node) { + Navigatable navigatable = createDescriptorForNode(node); + if (navigatable == null || !navigatable.canNavigate()) return null; + return navigatable; + } + @Override public OccurenceInfo goNextOccurence() { Counters counters = new Counters(); @@ -45,7 +55,7 @@ public abstract class OccurenceNavigatorSupport implements OccurenceNavigator { if (node == null) return null; TreePath treePath = new TreePath(node.getPath()); TreeUtil.selectPath(myTree, treePath); - Navigatable editSourceDescriptor = createDescriptorForNode(node); + Navigatable editSourceDescriptor = createDescriptorAndCheckCanNavigate(node); if (editSourceDescriptor == null) return null; return new OccurenceInfo(editSourceDescriptor, counters.myFoundOccurenceNumber, counters.myOccurencesCount); } @@ -57,21 +67,19 @@ public abstract class OccurenceNavigatorSupport implements OccurenceNavigator { if (node == null) return null; TreePath treePath = new TreePath(node.getPath()); TreeUtil.selectPath(myTree, treePath); - Navigatable editSourceDescriptor = createDescriptorForNode(node); + Navigatable editSourceDescriptor = createDescriptorAndCheckCanNavigate(node); if (editSourceDescriptor == null) return null; return new OccurenceInfo(editSourceDescriptor, counters.myFoundOccurenceNumber, counters.myOccurencesCount); } @Override public boolean hasNextOccurence() { - DefaultMutableTreeNode node = findNode(myTree, true, null); - return node != null; + return getAllDescriptors().canNavigate(true); } @Override public boolean hasPreviousOccurence() { - DefaultMutableTreeNode node = findNode(myTree, false, null); - return node != null; + return getAllDescriptors().canNavigate(false); } protected static class Counters { @@ -86,51 +94,13 @@ public abstract class OccurenceNavigatorSupport implements OccurenceNavigator { } private DefaultMutableTreeNode findNode(@NotNull JTree tree, boolean forward, Counters counters) { - TreePath selectionPath = tree.getSelectionPath(); - TreeNode selectedNode = null; - if (selectionPath != null) { - selectedNode = (TreeNode)selectionPath.getLastPathComponent(); - } - return findNode(tree, selectedNode, forward, counters); + return findNode(tree, getSelectedNode(tree), forward, counters); } public DefaultMutableTreeNode findNode(@NotNull JTree tree, TreeNode selectedNode, boolean forward, Counters counters) { - boolean[] ready = {selectedNode == null}; + List nodes = getAllNodes(tree); - DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot(); - - Enumeration enumeration = root.preorderEnumeration(); - List nodes = new ArrayList<>(); - while (enumeration.hasMoreElements()) { - TreeNode node = (TreeNode)enumeration.nextElement(); - nodes.add(node); - } - - DefaultMutableTreeNode result = null; - - if (forward) { - for (TreeNode node : nodes) { - DefaultMutableTreeNode nextNode = getNode(node, selectedNode, ready); - if (nextNode != null) { - result = nextNode; - break; - } - } - } - else { - for (int i=nodes.size() - 1; i >= 0; i--) { - TreeNode node = nodes.get(i); - DefaultMutableTreeNode nextNode = getNode(node, selectedNode, ready); - if (nextNode != null) { - result = nextNode; - break; - } - } - } - - if (result == null) { - return null; - } + DefaultMutableTreeNode result = getNextNode(selectedNode, forward, nodes); if (counters != null) { counters.myFoundOccurenceNumber = 0; @@ -138,7 +108,7 @@ public abstract class OccurenceNavigatorSupport implements OccurenceNavigator { for (TreeNode node : nodes) { if (!(node instanceof DefaultMutableTreeNode)) continue; - Navigatable descriptor = createDescriptorForNode((DefaultMutableTreeNode)node); + Navigatable descriptor = createDescriptorAndCheckCanNavigate((DefaultMutableTreeNode)node); if (descriptor == null) continue; counters.myOccurencesCount++; @@ -151,17 +121,98 @@ public abstract class OccurenceNavigatorSupport implements OccurenceNavigator { return result; } - protected DefaultMutableTreeNode getNode(TreeNode node, TreeNode selectedNode, boolean[] ready) { - if (!ready[0]) { - if (node == selectedNode) { - ready[0] = true; + private DefaultMutableTreeNode getNextNode(TreeNode selectedNode, boolean forward, List nodes) { + boolean ready = false; + for (TreeNode node : forward ? nodes : ContainerUtil.reverse(nodes)) { + if (!ready) { + if (node == selectedNode) ready = true; + continue; + } + if (node instanceof DefaultMutableTreeNode) { + Navigatable descriptor = createDescriptorAndCheckCanNavigate((DefaultMutableTreeNode)node); + if (descriptor != null) { + return (DefaultMutableTreeNode)node; + } } - return null; } - if (!(node instanceof DefaultMutableTreeNode)) return null; + return null; + } - Navigatable descriptor = createDescriptorForNode((DefaultMutableTreeNode)node); - if (descriptor == null) return null; - return (DefaultMutableTreeNode)node; + @Nullable + private static TreeNode getSelectedNode(@NotNull JTree tree) { + TreePath selectionPath = tree.getSelectionPath(); + TreeNode selectedNode = null; + if (selectionPath != null) { + selectedNode = (TreeNode)selectionPath.getLastPathComponent(); + } + return selectedNode; + } + + public static class Descriptors { + @NotNull + private final List myDescriptors; + private final int mySelectedPos; + + private Descriptors(@NotNull List descriptors, int pos) { + myDescriptors = descriptors; + mySelectedPos = pos; + } + + /** + * Can be called in a non-EDT thread in a read action. + * To prevent races, all related canNavigate() call should be made + * in the same read action. + */ + public boolean canNavigate(boolean forward) { + ApplicationManager.getApplication().assertReadAccessAllowed(); + + if (!(mySelectedPos >= 0 && mySelectedPos < myDescriptors.size())) return false; + + List subList = forward + ? myDescriptors.subList(mySelectedPos + 1, myDescriptors.size()) + : ContainerUtil.reverse(myDescriptors.subList(0, mySelectedPos)); + + for (Navigatable descriptor : subList) { + if (descriptor != null && descriptor.canNavigate()) { + return true; + } + } + return false; + } + } + + /** + * @return descriptors for all nodes (to be able to inspect it later, possibly in a background thread) + */ + @ApiStatus.Experimental + @NotNull + public Descriptors getAllDescriptors() { + List nodes = getAllNodes(myTree); + TreeNode selectedNode = getSelectedNode(myTree); + List result = new ArrayList<>(nodes.size()); + int selectedPos = -1; + for (TreeNode node : nodes) { + Navigatable descriptor = node instanceof DefaultMutableTreeNode + ? createDescriptorForNode((DefaultMutableTreeNode)node) + : null; + if (node == selectedNode) { + selectedPos = result.size(); + } + result.add(descriptor); + } + return new Descriptors(result, selectedPos); + } + + @NotNull + private static List getAllNodes(@NotNull JTree tree) { + DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot(); + + Enumeration enumeration = root.preorderEnumeration(); + List nodes = new ArrayList<>(); + while (enumeration.hasMoreElements()) { + TreeNode node = (TreeNode)enumeration.nextElement(); + nodes.add(node); + } + return nodes; } } diff --git a/platform/usageView/src/com/intellij/usages/actions/RerunSearchAction.java b/platform/usageView/src/com/intellij/usages/actions/RerunSearchAction.java index fe7848f0703e..f44ac25eeee3 100644 --- a/platform/usageView/src/com/intellij/usages/actions/RerunSearchAction.java +++ b/platform/usageView/src/com/intellij/usages/actions/RerunSearchAction.java @@ -37,7 +37,7 @@ public class RerunSearchAction extends DumbAwareAction { @Override public void update(@NotNull AnActionEvent e) { UsageView usageView = UsageView.USAGE_VIEW_KEY.getData(e.getDataContext()); - boolean enabled = usageView instanceof UsageViewImpl && ((UsageViewImpl)usageView).canPerformReRun(); + boolean enabled = usageView instanceof UsageViewImpl && ((UsageViewImpl)usageView).isReRunActionEnabled(); e.getPresentation().setEnabled(enabled); } } diff --git a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java index 886e105181c6..561742c3ce35 100644 --- a/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java +++ b/platform/usageView/src/com/intellij/usages/impl/UsageViewImpl.java @@ -9,6 +9,7 @@ import com.intellij.navigation.NavigationItem; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.extensions.Extensions; @@ -55,6 +56,8 @@ import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeModelAdapter; import com.intellij.util.ui.tree.TreeUtil; +import com.intellij.util.ui.update.MergingUpdateQueue; +import com.intellij.util.ui.update.Update; import gnu.trove.THashSet; import gnu.trove.TIntArrayList; import org.jetbrains.annotations.NonNls; @@ -85,6 +88,7 @@ public class UsageViewImpl implements UsageViewEx { private final UsageNodeTreeBuilder myBuilder; private MyPanel myRootPanel; // accessed in EDT only private JTree myTree; // accessed in EDT only + private ActionToolbar myActionsToolbar; // accessed in EDT only private final ScheduledFuture myFireEventsFuture; private Content myContent; @@ -154,6 +158,10 @@ public class UsageViewImpl implements UsageViewEx { private boolean myDisposeSmartPointersOnClose = true; private final Queue> updateRequests = new Queue<>(10); // guarded by insertionRequests + private final MergingUpdateQueue myControlsUpdateQueue = new MergingUpdateQueue("UsageView controls", 200, true, myRootPanel, this, null, false);; + @NotNull + private volatile ControlsState myCachedControlsState = new ControlsState(false, false, false); + public UsageViewImpl(@NotNull final Project project, @NotNull UsageViewPresentation presentation, @NotNull UsageTarget[] targets, @@ -230,8 +238,9 @@ public class UsageViewImpl implements UsageViewEx { myRootPanel.add(toolWindowPanel, BorderLayout.CENTER); JPanel toolbarPanel = new JPanel(new BorderLayout()); - toolbarPanel.add(createActionsToolbar(), BorderLayout.WEST); - toolbarPanel.add(createFiltersToolbar(), BorderLayout.CENTER); + myActionsToolbar = createActionsToolbar(); + toolbarPanel.add(myActionsToolbar.getComponent(), BorderLayout.WEST); + toolbarPanel.add(createFiltersToolbar().getComponent(), BorderLayout.CENTER); toolWindowPanel.setToolbar(toolbarPanel); myCentralPanel = new JPanel(new BorderLayout()); @@ -285,6 +294,8 @@ public class UsageViewImpl implements UsageViewEx { } } }); + + updateControlsImmediately(); } }); } @@ -695,7 +706,7 @@ public class UsageViewImpl implements UsageViewEx { } @NotNull - private JComponent createActionsToolbar() { + private ActionToolbar createActionsToolbar() { ApplicationManager.getApplication().assertIsDispatchThread(); DefaultActionGroup group = new DefaultActionGroup() { @@ -721,11 +732,11 @@ public class UsageViewImpl implements UsageViewEx { } @NotNull - private JComponent toUsageViewToolbar(@NotNull DefaultActionGroup group) { + private ActionToolbar toUsageViewToolbar(@NotNull DefaultActionGroup group) { ApplicationManager.getApplication().assertIsDispatchThread(); ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, group, false); actionToolbar.setTargetComponent(myRootPanel); - return actionToolbar.getComponent(); + return actionToolbar; } @SuppressWarnings("WeakerAccess") // used in rider @@ -734,7 +745,7 @@ public class UsageViewImpl implements UsageViewEx { } @NotNull - private JComponent createFiltersToolbar() { + private ActionToolbar createFiltersToolbar() { ApplicationManager.getApplication().assertIsDispatchThread(); final DefaultActionGroup group = new DefaultActionGroup(); @@ -1269,6 +1280,42 @@ public class UsageViewImpl implements UsageViewEx { checkNodeValidity(root, new TreePath(root), toUpdate); queueUpdateBulk(toUpdate, EmptyRunnable.getInstance()); updateOnSelectionChanged(); + queueControlsUpdate(); + } + + private void queueControlsUpdate() { + if (isDisposed()) return; + + OccurenceNavigatorSupport.Descriptors descriptors = myRootPanel.getAllDescriptors(); + myControlsUpdateQueue.queue(new Update("controls update") { + @Override + public void run() { + if (isDisposed()) return; + + if (!runReadActionWithRetries(() -> updateControlsImmediately(descriptors))) { + ApplicationManager.getApplication().invokeLater(UsageViewImpl.this::queueControlsUpdate); + } + } + + @Override + public boolean canEat(Update update) { + return true; + } + }); + } + + private void updateControlsImmediately() { + updateControlsImmediately(myRootPanel.getAllDescriptors()); + } + + private void updateControlsImmediately(@Nullable OccurenceNavigatorSupport.Descriptors descriptors) { + boolean prevOccurence = descriptors != null && descriptors.canNavigate(false); + boolean nextOccurence = descriptors != null && descriptors.canNavigate(true); + boolean rerun = canPerformReRun(); + + myCachedControlsState = new ControlsState(prevOccurence, nextOccurence, rerun); + + GuiUtils.invokeLaterIfNeeded(() -> myActionsToolbar.updateActionsImmediately(), ModalityState.defaultModalityState()); } private void queueUpdateBulk(@NotNull List toUpdate, @NotNull Runnable onCompletedInEdt) { @@ -1341,6 +1388,7 @@ public class UsageViewImpl implements UsageViewEx { catch (IndexNotReadyException ignore) { } } + queueControlsUpdate(); } private void checkNodeValidity(@NotNull TreeNode node, @NotNull TreePath path, @NotNull List result) { @@ -1403,6 +1451,7 @@ public class UsageViewImpl implements UsageViewEx { ToolTipManager.sharedInstance().unregisterComponent(myTree); } myUpdateAlarm.cancelAllRequests(); + myControlsUpdateQueue.cancelAllUpdates(); } if (myDisposeSmartPointersOnClose) { disposeSmartPointers(); @@ -1553,6 +1602,10 @@ public class UsageViewImpl implements UsageViewEx { } } + public boolean isReRunActionEnabled() { + return myCachedControlsState.canPerformRerun; + } + private boolean checkReadonlyUsages() { final Set readOnlyUsages = getReadOnlyUsagesFiles(); @@ -1728,7 +1781,7 @@ public class UsageViewImpl implements UsageViewEx { Object userObject = node.getUserObject(); if (userObject instanceof Navigatable) { final Navigatable navigatable = (Navigatable)userObject; - return navigatable.canNavigate() ? new Navigatable() { + return new Navigatable() { @Override public void navigate(boolean requestFocus) { navigatable.navigate(allowRequestFocus && requestFocus); @@ -1743,7 +1796,7 @@ public class UsageViewImpl implements UsageViewEx { public boolean canNavigateToSource() { return navigatable.canNavigateToSource(); } - } : null; + }; } return null; } @@ -1826,24 +1879,33 @@ public class UsageViewImpl implements UsageViewEx { mySupport = null; } + @Nullable + OccurenceNavigatorSupport.Descriptors getAllDescriptors() { + return mySupport == null ? null : mySupport.getAllDescriptors(); + } + @Override public boolean hasNextOccurence() { - return mySupport != null && mySupport.hasNextOccurence(); + return myCachedControlsState.hasNextOccurence; } @Override public boolean hasPreviousOccurence() { - return mySupport != null && mySupport.hasPreviousOccurence(); + return myCachedControlsState.hasPrevOccurence; } @Override public OccurenceInfo goNextOccurence() { - return mySupport != null ? mySupport.goNextOccurence() : null; + OccurenceInfo result = mySupport != null ? mySupport.goNextOccurence() : null; + updateControlsImmediately(); + return result; } @Override public OccurenceInfo goPreviousOccurence() { - return mySupport != null ? mySupport.goPreviousOccurence() : null; + OccurenceInfo result = mySupport != null ? mySupport.goPreviousOccurence() : null; + updateControlsImmediately(); + return result; } @Override @@ -1927,6 +1989,18 @@ public class UsageViewImpl implements UsageViewEx { } } + private static class ControlsState { + final boolean hasPrevOccurence; + final boolean hasNextOccurence; + final boolean canPerformRerun; + + private ControlsState(boolean hasPrevOccurence, boolean hasNextOccurence, boolean canPerformRerun) { + this.hasPrevOccurence = hasPrevOccurence; + this.hasNextOccurence = hasNextOccurence; + this.canPerformRerun = canPerformRerun; + } + } + private static class MyAutoScrollToSourceOptionProvider implements AutoScrollToSourceOptionProvider { @NotNull private final UsageViewSettings myUsageViewSettings;