CPP-8459: Calculate controls state in background

This commit is contained in:
Dmitry Kozhevnikov
2018-05-30 14:02:29 +03:00
committed by Dmitry Kozhevnikov
parent 0207796666
commit 4f5cb24fe0
3 changed files with 195 additions and 70 deletions
@@ -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<TreeNode> nodes = getAllNodes(tree);
DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot();
Enumeration enumeration = root.preorderEnumeration();
List<TreeNode> 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<TreeNode> 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<Navigatable> myDescriptors;
private final int mySelectedPos;
private Descriptors(@NotNull List<Navigatable> 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<Navigatable> 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<TreeNode> nodes = getAllNodes(myTree);
TreeNode selectedNode = getSelectedNode(myTree);
List<Navigatable> 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<TreeNode> getAllNodes(@NotNull JTree tree) {
DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot();
Enumeration enumeration = root.preorderEnumeration();
List<TreeNode> nodes = new ArrayList<>();
while (enumeration.hasMoreElements()) {
TreeNode node = (TreeNode)enumeration.nextElement();
nodes.add(node);
}
return nodes;
}
}
@@ -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);
}
}
@@ -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<Future<?>> 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<Node> 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<? super Node> 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<VirtualFile> 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;