From 4dfe72a7895114c94d0d7c3ac1f7f2963da2008a Mon Sep 17 00:00:00 2001 From: "Gregory.Shrago" Date: Thu, 5 Oct 2017 01:15:40 +0300 Subject: [PATCH] async structure view: tool window --- .../intellij/ide/structureView/HelpID.java | 11 + .../TemplateLanguageStructureViewBuilder.java | 61 +- .../StructureTreeBuilder.java | 22 +- .../StructureViewComponent.java | 930 ++++++++++-------- .../intellij/ide/util/FileStructurePopup.java | 61 +- .../intellij/ui/tree/StructureTreeModel.java | 30 +- .../impl/StructureViewState.java | 43 - .../properties/editor/NewPropertyAction.java | 8 +- .../editor/ResourceBundleEditor.java | 60 +- resources/src/idea/RichPlatformPlugin.xml | 3 + 10 files changed, 647 insertions(+), 582 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/ide/structureView/HelpID.java delete mode 100644 platform/structure-view-impl/src/com/intellij/ide/structureView/impl/StructureViewState.java diff --git a/platform/lang-impl/src/com/intellij/ide/structureView/HelpID.java b/platform/lang-impl/src/com/intellij/ide/structureView/HelpID.java new file mode 100644 index 000000000000..5826354dd986 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/ide/structureView/HelpID.java @@ -0,0 +1,11 @@ +// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.ide.structureView; + +import org.jetbrains.annotations.NonNls; + +/** + * @author gregsh + */ +public interface HelpID { + @NonNls String STRUCTURE_VIEW = "viewingStructure.fileStructureView"; +} diff --git a/platform/lang-impl/src/com/intellij/ide/structureView/impl/TemplateLanguageStructureViewBuilder.java b/platform/lang-impl/src/com/intellij/ide/structureView/impl/TemplateLanguageStructureViewBuilder.java index 6c44ce997e66..d9bd404c3373 100644 --- a/platform/lang-impl/src/com/intellij/ide/structureView/impl/TemplateLanguageStructureViewBuilder.java +++ b/platform/lang-impl/src/com/intellij/ide/structureView/impl/TemplateLanguageStructureViewBuilder.java @@ -17,9 +17,12 @@ package com.intellij.ide.structureView.impl; import com.intellij.ide.IdeBundle; import com.intellij.ide.impl.StructureViewWrapperImpl; -import com.intellij.ide.structureView.*; +import com.intellij.ide.structureView.StructureView; +import com.intellij.ide.structureView.StructureViewBuilder; +import com.intellij.ide.structureView.StructureViewFactoryEx; +import com.intellij.ide.structureView.StructureViewWrapper; import com.intellij.ide.structureView.newStructureView.StructureViewComponent; -import com.intellij.ide.util.treeView.smartTree.TreeElement; +import com.intellij.ide.util.treeView.TreeState; import com.intellij.lang.Language; import com.intellij.lang.LanguageStructureViewBuilder; import com.intellij.lang.PsiStructureViewFactory; @@ -33,7 +36,10 @@ import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.psi.*; +import com.intellij.psi.FileViewProvider; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; import com.intellij.psi.templateLanguages.TemplateLanguageFileViewProvider; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.util.Alarm; @@ -41,6 +47,7 @@ import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import javax.swing.tree.TreePath; import java.util.ArrayList; import java.util.List; @@ -64,7 +71,7 @@ public abstract class TemplateLanguageStructureViewBuilder implements StructureV private void updateAfterPsiChange() { if (myProject.isDisposed()) return; - if (myBaseStructureViewDescriptor != null && ((StructureViewComponent)myBaseStructureViewDescriptor.structureView).getTree() == null) return; + if (((StructureViewComponent)myBaseStructureViewDescriptor.structureView).isDisposed()) return; ApplicationManager.getApplication().runReadAction(() -> { if (!myVirtualFile.isValid() || getViewProvider() == null) return; @@ -84,20 +91,10 @@ public abstract class TemplateLanguageStructureViewBuilder implements StructureV } private static boolean isPsiValid(@NotNull StructureViewComposite.StructureViewDescriptor baseStructureViewDescriptor) { - final StructureViewComponent view = (StructureViewComponent)baseStructureViewDescriptor.structureView; + StructureViewComponent view = (StructureViewComponent)baseStructureViewDescriptor.structureView; if (view.isDisposed()) return false; - - final Object root = view.getTreeStructure().getRootElement(); - if (root instanceof StructureViewComponent.StructureViewTreeElementWrapper) { - final TreeElement value = ((StructureViewComponent.StructureViewTreeElementWrapper)root).getValue(); - if (value instanceof StructureViewTreeElement) { - final Object psi = ((StructureViewTreeElement)value).getValue(); - if (psi instanceof PsiElement) { - return ((PsiElement)psi).isValid(); - } - } - } - return true; + Object value = StructureViewComponent.unwrapValue(view.getTree().getModel().getRoot()); + return !(value instanceof PsiElement && !((PsiElement)value).isValid()); } @Nullable @@ -117,37 +114,11 @@ public abstract class TemplateLanguageStructureViewBuilder implements StructureV final StructureViewComponent view = (StructureViewComponent)myBaseStructureViewDescriptor.structureView; if (view.isDisposed()) return; - StructureViewState state = view.getState(); - List expanded = collectAnchors(state.getExpandedElements()); - List selected = collectAnchors(state.getSelectedElements()); + TreeState treeState = TreeState.createOn(view.getTree(), new TreePath(view.getTree().getModel().getRoot())); updateTemplateDataFileView(); if (view.isDisposed()) return; - - for (PsiAnchor pointer : expanded) { - PsiElement element = pointer.retrieve(); - if (element != null) { - view.expandPathToElement(element); - } - } - for (PsiAnchor pointer : selected) { - PsiElement element = pointer.retrieve(); - if (element != null) { - view.addSelectionPathTo(element); - } - } - } - - private static List collectAnchors(final Object[] expandedElements) { - List expanded = new ArrayList<>(expandedElements == null ? 0 : expandedElements.length); - if (expandedElements != null) { - for (Object element : expandedElements) { - if (element instanceof PsiElement && ((PsiElement) element).isValid()) { - expanded.add(PsiAnchor.create((PsiElement)element)); - } - } - } - return expanded; + treeState.applyTo(view.getTree()); } @Override diff --git a/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureTreeBuilder.java b/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureTreeBuilder.java index 7e9ff16aed9a..761215dc74eb 100644 --- a/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureTreeBuilder.java +++ b/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureTreeBuilder.java @@ -48,7 +48,7 @@ public class StructureTreeBuilder extends AbstractTreeBuilder { private final StructureViewModel myStructureModel; private final Alarm myUpdateAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, this); - + public StructureTreeBuilder(Project project, JTree tree, DefaultTreeModel treeModel, @@ -93,20 +93,16 @@ public class StructureTreeBuilder extends AbstractTreeBuilder { protected final boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) { StructureViewModel model = myStructureModel; if (model instanceof TreeModelWrapper) { - model = ((TreeModelWrapper) model).getModel(); + model = ((TreeModelWrapper)model).getModel(); } if (model instanceof StructureViewModel.ExpandInfoProvider) { StructureViewModel.ExpandInfoProvider provider = (StructureViewModel.ExpandInfoProvider)model; Object element = nodeDescriptor.getElement(); - if (element instanceof StructureViewComponent.StructureViewTreeElementWrapper) { - StructureViewComponent.StructureViewTreeElementWrapper wrapper = (StructureViewComponent.StructureViewTreeElementWrapper)element; - if (wrapper.getValue() instanceof StructureViewTreeElement) { - final StructureViewTreeElement value = (StructureViewTreeElement)wrapper.getValue(); - if (value != null) { - return provider.isAutoExpand(value); - } - } - } else if (element instanceof GroupWrapper) { + Object value = StructureViewComponent.unwrapValue(element); + if (value instanceof StructureViewTreeElement) { + return provider.isAutoExpand((StructureViewTreeElement)value); + } + else if (element instanceof GroupWrapper) { final Group group = ((GroupWrapper)element).getValue(); for (TreeElement treeElement : group.getChildren()) { if (treeElement instanceof StructureViewTreeElement && !provider.isAutoExpand((StructureViewTreeElement)treeElement)) { @@ -124,7 +120,7 @@ public class StructureTreeBuilder extends AbstractTreeBuilder { protected final boolean isSmartExpand() { StructureViewModel model = myStructureModel; if (model instanceof TreeModelWrapper) { - model = ((TreeModelWrapper) model).getModel(); + model = ((TreeModelWrapper)model).getModel(); } if (model instanceof StructureViewModel.ExpandInfoProvider) { return ((StructureViewModel.ExpandInfoProvider)model).isSmartExpand(); @@ -215,6 +211,6 @@ public class StructureTreeBuilder extends AbstractTreeBuilder { @Override @NotNull protected final AbstractTreeNode createSearchingTreeNodeWrapper() { - return new StructureViewComponent.StructureViewTreeElementWrapper(null,null, null); + return StructureViewComponent.createWrapper(null, null, null); } } diff --git a/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureViewComponent.java b/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureViewComponent.java index 45bdb01b7cdd..dd8f3a340634 100644 --- a/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureViewComponent.java +++ b/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureViewComponent.java @@ -22,7 +22,6 @@ import com.intellij.ide.PsiCopyPasteManager; import com.intellij.ide.dnd.aware.DnDAwareTree; import com.intellij.ide.structureView.*; import com.intellij.ide.structureView.impl.StructureViewFactoryImpl; -import com.intellij.ide.structureView.impl.StructureViewState; import com.intellij.ide.structureView.impl.common.PsiTreeElementBase; import com.intellij.ide.ui.customization.CustomizationUtil; import com.intellij.ide.util.FileStructurePopup; @@ -31,6 +30,7 @@ import com.intellij.ide.util.treeView.smartTree.*; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.Experiments; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.FileEditorManager; @@ -41,25 +41,36 @@ import com.intellij.openapi.util.*; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.pom.Navigatable; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.*; import com.intellij.ui.popup.HintUpdateSupply; +import com.intellij.ui.tree.AsyncTreeModel; +import com.intellij.ui.tree.StructureTreeModel; +import com.intellij.ui.tree.TreeVisitor; +import com.intellij.ui.treeStructure.Tree; import com.intellij.ui.treeStructure.actions.CollapseAllAction; import com.intellij.ui.treeStructure.actions.ExpandAllAction; import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure; import com.intellij.util.*; -import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.JBIterable; +import com.intellij.util.containers.JBTreeTraverser; +import com.intellij.util.ui.EdtInvocationManager; import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeModelAdapter; import com.intellij.util.ui.tree.TreeUtil; import gnu.trove.THashSet; -import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; +import org.jetbrains.concurrency.AsyncPromise; +import org.jetbrains.concurrency.Promise; +import org.jetbrains.concurrency.Promises; import javax.swing.*; +import javax.swing.event.TreeModelEvent; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; @@ -71,34 +82,50 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; public class StructureViewComponent extends SimpleToolWindowPanel implements TreeActionsOwner, DataProvider, StructureView.Scrollable { private static final Logger LOG = Logger.getInstance("#com.intellij.ide.structureView.newStructureView.StructureViewComponent"); - @NonNls private static final String ourHelpID = "viewingStructure.fileStructureView"; - private AbstractTreeBuilder myAbstractTreeBuilder; + private static final Key STRUCTURE_VIEW_STATE_KEY = Key.create("STRUCTURE_VIEW_STATE"); + private static AtomicInteger ourSettingsModificationCount = new AtomicInteger(); + private final boolean myUseATM = ApplicationManager.getApplication().isUnitTestMode() || + Experiments.isFeatureEnabled("structure.view.async.tree.model"); private FileEditor myFileEditor; private final TreeModelWrapper myTreeModelWrapper; - private StructureViewState myStructureViewState; - private boolean myAutoscrollFeedback; + private final Project myProject; + private final StructureViewModel myTreeModel; - private final Alarm myAutoscrollAlarm = new Alarm(); + private final Tree myTree; + private final SmartTreeStructure myTreeStructure; + private final StructureTreeBuilder myTreeBuilder; + + private final StructureTreeModel myStructureTreeModel; + private final AsyncTreeModel myAsyncTreeModel; + private final SingleAlarm myUpdateAlarm; + + private volatile AsyncPromise myCurrentFocusPromise; + + private TreeState myStructureViewState; + private boolean myAutoscrollFeedback; + private boolean myDisposed; + + private final Alarm myAutoscrollAlarm = new Alarm(this); private final CopyPasteDelegator myCopyPasteDelegator; private final MyAutoScrollToSourceHandler myAutoScrollToSourceHandler; private final AutoScrollFromSourceHandler myAutoScrollFromSourceHandler; - private static final Key STRUCTURE_VIEW_STATE_KEY = Key.create("STRUCTURE_VIEW_STATE"); - private final Project myProject; - private final StructureViewModel myTreeModel; - private static int ourSettingsModificationCount; - public StructureViewComponent(final FileEditor editor, + public StructureViewComponent(FileEditor editor, @NotNull StructureViewModel structureViewModel, @NotNull Project project, - final boolean showRootNode) { + boolean showRootNode) { super(true, true); myProject = project; @@ -106,11 +133,16 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre myTreeModel = structureViewModel; myTreeModelWrapper = new TreeModelWrapper(myTreeModel, this); - SmartTreeStructure treeStructure = new SmartTreeStructure(project, myTreeModelWrapper){ + myTreeStructure = new SmartTreeStructure(project, myTreeModelWrapper) { @Override public void rebuildTree() { - if (!isDisposed()) { - super.rebuildTree(); + if (isDisposed()) return; + super.rebuildTree(); + if (myUseATM) { + myStructureTreeModel.invalidate().processed(ignored -> { + // todo restoreState?? + //UIUtil.invokeLaterIfNeeded(StructureViewComponent.this::restoreState); + }); } } @@ -122,7 +154,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre @Override protected TreeElementWrapper createTree() { - return new StructureViewTreeElementWrapper(myProject, myModel.getRoot(), myModel); + return new MyNodeWrapper(myProject, myModel.getRoot(), myModel); } @Override @@ -131,57 +163,75 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre } }; - final DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode(treeStructure.getRootElement())); - JTree tree = new MyTree(model); - tree.setRootVisible(showRootNode); - tree.setShowsRootHandles(true); + if (myUseATM) { + myStructureTreeModel = new StructureTreeModel(true); + myStructureTreeModel.setStructure(myTreeStructure); + myAsyncTreeModel = new AsyncTreeModel(myStructureTreeModel, true); + myAsyncTreeModel.setRootImmediately(myStructureTreeModel.getRootImmediately()); + myTree = new MyTree(myAsyncTreeModel); + UIUtil.putClientProperty(myTree, TreeState.VISIT, visitor -> myAsyncTreeModel.accept(visitor, true)); - myAbstractTreeBuilder = new StructureTreeBuilder(project, tree, - (DefaultTreeModel)tree.getModel(),treeStructure,myTreeModelWrapper) { - @Override - protected boolean validateNode(Object child) { - return isValid(child); - } - }; - Disposer.register(this, myAbstractTreeBuilder); - Disposer.register(myAbstractTreeBuilder, new Disposable() { + Disposer.register(this, () -> myTreeModelWrapper.dispose()); + Disposer.register(this, myAsyncTreeModel); + + myAsyncTreeModel.addTreeModelListener(new MyExpandListener( + myTree, ObjectUtils.tryCast(myTreeModel, StructureViewModel.ExpandInfoProvider.class))); + + myUpdateAlarm = new SingleAlarm(this::rebuild, 200, this); + myTreeBuilder = null; + } + else { + myStructureTreeModel = null; + myAsyncTreeModel = null; + myUpdateAlarm = null; + myTree = new MyTree(new DefaultTreeModel(new DefaultMutableTreeNode(myTreeStructure.getRootElement()))); + myTreeBuilder = new StructureTreeBuilder(project, myTree, (DefaultTreeModel)myTree.getModel(), + myTreeStructure, myTreeModelWrapper) { + @Override + protected boolean validateNode(Object child) { + return !(child instanceof ValidateableNode) || ((ValidateableNode)child).isValid(); + } + }; + Disposer.register(this, myTreeBuilder); + } + myTree.setRootVisible(showRootNode); + myTree.getEmptyText().setText("Structure is empty"); + + + Disposer.register(this, new Disposable() { @Override public void dispose() { storeState(); } }); - setContent(ScrollPaneFactory.createScrollPane(myAbstractTreeBuilder.getTree())); - - myAbstractTreeBuilder.getTree().setCellRenderer(new NodeRenderer()); + setContent(ScrollPaneFactory.createScrollPane(myTree)); myAutoScrollToSourceHandler = new MyAutoScrollToSourceHandler(); myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(myProject, this); - - setToolbar(createToolbar()); - - installTree(); - - myCopyPasteDelegator = new CopyPasteDelegator(myProject, getTree()) { + myCopyPasteDelegator = new CopyPasteDelegator(myProject, myTree) { @Override @NotNull protected PsiElement[] getSelectedElements() { - return getSelectedPsiElements(); + return PsiUtilCore.toPsiElementArray(StructureViewComponent.this.getSelectedElements().filter(PsiElement.class).toList()); } }; + + setToolbar(createToolbar()); + setupTree(); } - - private static class MyTree extends DnDAwareTree implements PlaceProvider { - MyTree(javax.swing.tree.TreeModel model) { - super(model); - HintUpdateSupply.installDataContextHintUpdateSupply(this); - } + protected boolean showScrollToFromSourceActions() { + return true; + } - @Override - public String getPlace() { - return ActionPlaces.STRUCTURE_VIEW_TOOLBAR; - } + @Override + public FileEditor getFileEditor() { + return myFileEditor; + } + + private StructureViewFactoryImpl.State getSettings() { + return ((StructureViewFactoryImpl)StructureViewFactory.getInstance(myProject)).getState(); } public void showToolbar() { @@ -192,20 +242,22 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre return ActionManager.getInstance().createActionToolbar(ActionPlaces.STRUCTURE_VIEW_TOOLBAR, createActionGroup(), true).getComponent(); } - private void installTree() { - getTree().getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); - myAutoScrollToSourceHandler.install(getTree()); + private void setupTree() { + myTree.setCellRenderer(new NodeRenderer()); + myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); + myTree.setShowsRootHandles(true); + MyPsiTreeChangeListener psiListener = new MyPsiTreeChangeListener( + PsiManager.getInstance(myProject).getModificationTracker(), this::queueUpdate); + PsiManager.getInstance(myProject).addPsiTreeChangeListener(psiListener, this); + + myAutoScrollToSourceHandler.install(myTree); myAutoScrollFromSourceHandler.install(); TreeUtil.installActions(getTree()); new TreeSpeedSearch(getTree(), treePath -> { - final DefaultMutableTreeNode node = (DefaultMutableTreeNode)treePath.getLastPathComponent(); - final Object userObject = node.getUserObject(); - if (userObject != null) { - return FileStructurePopup.getSpeedSearchText(userObject); - } - return null; + Object userObject = TreeUtil.getUserObject(treePath.getLastPathComponent()); + return userObject != null ? FileStructurePopup.getSpeedSearchText(userObject) : null; }); addTreeKeyListener(); @@ -213,68 +265,60 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre restoreState(); } - private PsiElement[] getSelectedPsiElements() { - return filterPsiElements(getSelectedElements()); + @NotNull + public Project getProject() { + return myProject; } @NotNull - private static PsiElement[] filterPsiElements(Object[] selectedElements) { - if (selectedElements == null) { - return PsiElement.EMPTY_ARRAY; - } - ArrayList psiElements = new ArrayList<>(); - - for (Object selectedElement : selectedElements) { - if (selectedElement instanceof PsiElement) { - psiElements.add((PsiElement)selectedElement); - } - } - return PsiUtilCore.toPsiElementArray(psiElements); + public JTree getTree() { + return myTree; } - private Object[] getSelectedElements() { - final JTree tree = getTree(); - return tree != null ? convertPathsToValues(tree.getSelectionPaths()): ArrayUtil.EMPTY_OBJECT_ARRAY; + public void queueUpdate() { + if (myUseATM) { + myUpdateAlarm.cancelAndRequest(); + } + else { + myTreeBuilder.queueUpdate(); + } + } + + public void rebuild() { + if (myUseATM) { + //storeState(); + myStructureTreeModel.getInvoker().invokeLaterIfNeeded(myTreeStructure::rebuildTree); + } + else { + myTreeBuilder.queueUpdate(); + } + } + + @NotNull + private static JBTreeTraverser traverser() { + return new JBTreeTraverser<>(o -> (o instanceof Group ? JBIterable.from(((Group)o).getChildren()) : JBIterable.empty())); + } + + private JBIterable getSelectedElements() { + return traverser() + .withRoots(JBIterable.of(getTree().getSelectionPaths()).map(TreePath::getLastPathComponent)) + .traverse() + .filter(StructureViewTreeElement.class) + .filterMap(o -> o.getValue()); } @Nullable private Object[] getSelectedTreeElements() { - final JTree tree = getTree(); - return tree != null ? convertPathsToTreeElements(tree.getSelectionPaths()) : null; - } - - - private static Object[] convertPathsToValues(@Nullable TreePath[] selectionPaths) { - if (selectionPaths == null) return null; - List result = new ArrayList<>(); - for (TreePath selectionPath : selectionPaths) { - Object value = getNodeValue((DefaultMutableTreeNode)selectionPath.getLastPathComponent()); - if (value instanceof StructureViewTreeElement) { - ContainerUtil.addIfNotNull(result, ((StructureViewTreeElement)value).getValue()); - } - else if (value instanceof Group) { - ((Group)value).getChildren().stream() - .filter(element -> element instanceof StructureViewTreeElement) - .map(element -> ((StructureViewTreeElement)element).getValue()) - .forEach(element -> ContainerUtil.addIfNotNull(result, element)); - } - } - return ArrayUtil.toObjectArray(result); + List list = JBIterable.of(getTree().getSelectionPaths()) + .map(TreePath::getLastPathComponent) + .map(StructureViewComponent::getNodeValue) + .toList(); + return list.isEmpty() ? null : ArrayUtil.toObjectArray(list); } @Nullable - private static Object[] convertPathsToTreeElements(TreePath[] selectionPaths) { - if (selectionPaths == null) return null; - List result = new ArrayList<>(); - for (TreePath selectionPath : selectionPaths) { - ContainerUtil.addIfNotNull(result, getNodeValue((DefaultMutableTreeNode)selectionPath.getLastPathComponent())); - } - return ArrayUtil.toObjectArray(result); - } - - @Nullable - private static Object getNodeValue(DefaultMutableTreeNode mutableTreeNode) { - Object userObject = mutableTreeNode.getUserObject(); + private static Object getNodeValue(Object node) { + Object userObject = TreeUtil.getUserObject(node); if (userObject instanceof FilteringTreeStructure.FilteringNode) { userObject = ((FilteringTreeStructure.FilteringNode)userObject).getDelegate(); } @@ -296,10 +340,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre OpenSourceUtil.openSourcesFrom(dataContext, false); } else if (KeyEvent.VK_ESCAPE == e.getKeyCode()) { - if (e.isConsumed()) - { - return; - } + if (e.isConsumed()) return; PsiCopyPasteManager copyPasteManager = PsiCopyPasteManager.getInstance(); boolean[] isCopied = new boolean[1]; if (copyPasteManager.getElements(isCopied) != null && !isCopied[0]) { @@ -313,29 +354,13 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre @Override public void storeState() { - if (!isDisposed()) { - myStructureViewState = getState(); - myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, myStructureViewState); - } + if (isDisposed()) return; + Object root = myTree.getModel().getRoot(); + if (root == null) return; + myStructureViewState = TreeState.createOn(myTree, new TreePath(root)); + myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, myStructureViewState); } - public StructureViewState getState() { - StructureViewState structureViewState = new StructureViewState(); - if (getTree() != null) { - structureViewState.setExpandedElements(getExpandedElements()); - structureViewState.setSelectedElements(getSelectedElements()); - } - return structureViewState; - } - - private Object[] getExpandedElements() { - final JTree tree = getTree(); - if (tree == null) return ArrayUtil.EMPTY_OBJECT_ARRAY; - final List expandedPaths = TreeUtil.collectExpandedPaths(tree); - return convertPathsToValues(expandedPaths.toArray(new TreePath[expandedPaths.size()])); - } - - @Override public void restoreState() { myStructureViewState = myFileEditor.getUserData(STRUCTURE_VIEW_STATE_KEY); @@ -343,71 +368,12 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre TreeUtil.expand(getTree(), 2); } else { - expandStoredElements(); - selectStoredElements(); + myStructureViewState.applyTo(myTree); myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, null); myStructureViewState = null; } } - private void selectStoredElements() { - Object[] selectedPsiElements = null; - - if (myStructureViewState != null) { - selectedPsiElements = myStructureViewState.getSelectedElements(); - } - - if (selectedPsiElements == null) { - getTree().setSelectionPath(new TreePath(getRootNode().getPath())); - } - else { - for (Object element : selectedPsiElements) { - if (element instanceof PsiElement && !((PsiElement)element).isValid()) { - continue; - } - addSelectionPathTo(element); - } - } - } - - public void addSelectionPathTo(final Object element) { - DefaultMutableTreeNode node = myAbstractTreeBuilder.getNodeForElement(element); - if (node != null) { - final JTree tree = getTree(); - final TreePath path = new TreePath(node.getPath()); - if (node == tree.getModel().getRoot() && !tree.isExpanded(path)) tree.expandPath(path); - tree.addSelectionPath(path); - } - } - - private DefaultMutableTreeNode getRootNode() { - return (DefaultMutableTreeNode)getTree().getModel().getRoot(); - } - - private void expandStoredElements() { - Object[] expandedPsiElements = null; - - if (myStructureViewState != null) { - expandedPsiElements = myStructureViewState.getExpandedElements(); - } - - if (expandedPsiElements == null) { - getTree().expandPath(new TreePath(getRootNode().getPath())); - } - else { - for (Object element : expandedPsiElements) { - if (element instanceof PsiElement && !((PsiElement)element).isValid()) { - continue; - } - expandPathToElement(element); - } - } - } - - private StructureViewFactoryImpl.State getSettings() { - return ((StructureViewFactoryImpl)StructureViewFactory.getInstance(myProject)).getState(); - } - protected ActionGroup createActionGroup() { DefaultActionGroup result = new DefaultActionGroup(); Sorter[] sorters = myTreeModel.getSorters(); @@ -452,72 +418,98 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre } } - protected boolean showScrollToFromSourceActions() { - return true; - } - - @Override - public FileEditor getFileEditor() { - return myFileEditor; - } - public AsyncResult expandPathToElement(Object element) { - if (myAbstractTreeBuilder == null) return AsyncResult.rejected(); - - ArrayList pathToElement = getPathToElement(element); - if (pathToElement.isEmpty()) return AsyncResult.rejected(); - - final AsyncResult result = new AsyncResult<>(); - final AbstractTreeNode toExpand = pathToElement.get(pathToElement.size() - 1); - myAbstractTreeBuilder.expand(toExpand, () -> result.setDone(toExpand)); - + AsyncResult result = new AsyncResult<>(); + expandSelectFocusInner(element, false, false).processed(p -> { + if (p == null) result.setRejected(); + else result.setDone(ObjectUtils.tryCast(TreeUtil.getUserObject(p.getLastPathComponent()), AbstractTreeNode.class)); + }); return result; } - public boolean select(final Object element, final boolean requestFocus) { - myAbstractTreeBuilder.getReady(this).doWhenDone(() -> expandPathToElement(element).doWhenDone( - (Consumer)abstractTreeNode -> { - myAbstractTreeBuilder.select(abstractTreeNode, () -> { + @NotNull + public Promise select(Object element, boolean requestFocus) { + return expandSelectFocusInner(element, true, requestFocus); + } + + @NotNull + private Promise expandSelectFocusInner(Object element, boolean select, boolean requestFocus) { + AsyncPromise result = myCurrentFocusPromise = new AsyncPromise<>(); + if (!myUseATM) { + ArrayList pathToElement = getPathToElement(element); + if (pathToElement.isEmpty()) return Promises.rejectedPromise(); + TreePath path = new TreePath(pathToElement.toArray()); + myTreeBuilder.expand(path.getLastPathComponent(), () -> { + if (myCurrentFocusPromise != result) { + result.setError("rejected"); + } + else { + if (select) myTreeBuilder.select(path.getLastPathComponent()); if (requestFocus) { - IdeFocusManager.getInstance(myProject).requestFocus(myAbstractTreeBuilder.getTree(), false); + IdeFocusManager.getInstance(myProject).requestFocus(myTree, false); } - }); - })); - return true; - } - - private ArrayList getPathToElement(Object element) { - ArrayList result = new ArrayList<>(); - final AbstractTreeStructure treeStructure = myAbstractTreeBuilder.getTreeStructure(); - if (treeStructure != null) { - addToPath((AbstractTreeNode)treeStructure.getRootElement(), element, result, new THashSet<>()); + result.setResult(path); + } + }); + return result; } - return result; - } - - private static boolean addToPath(AbstractTreeNode rootElement, Object element, ArrayList result, Collection processedElements) { - Object value = rootElement.getValue(); - if (value instanceof StructureViewTreeElement) { - value = ((StructureViewTreeElement) value).getValue(); - } - if (!processedElements.add(value)){ - return false; - } - - if (Comparing.equal(value, element)){ - result.add(0, rootElement); - return true; - } - - Collection children = rootElement.getChildren(); - for (AbstractTreeNode child : children) { - if (addToPath(child, element, result, processedElements)) { - result.add(0, rootElement); - return true; + int[] stage = { 1, 0 }; // 1 - first pass, 2 - optimization applied, 3 - retry w/o optimization + TreePath[] deepestPath = { null }; + TreeVisitor visitor = path -> { + if (myCurrentFocusPromise != result) { + result.setError("rejected"); + return TreeVisitor.Action.INTERRUPT; } - } - - return false; + Object userObject = TreeUtil.getUserObject(path.getLastPathComponent()); + Object unwrapped = unwrapValue(userObject); + if (Comparing.equal(unwrapped, element) || + userObject instanceof AbstractTreeNode && ((AbstractTreeNode)userObject).canRepresent(element)) { + return TreeVisitor.Action.INTERRUPT; + } + if (unwrapped instanceof PsiElement && element instanceof PsiElement) { + if (PsiTreeUtil.isAncestor((PsiElement)unwrapped, (PsiElement)element, true)) { + int count = path.getPathCount(); + if (stage[1] == 0 || stage[1] < count) { + stage[1] = count; + deepestPath[0] = path; + } + } + else if (stage[0] != 3) { + stage[0] = 2; + return TreeVisitor.Action.SKIP_CHILDREN; + } + } + return TreeVisitor.Action.CONTINUE; + }; + Function> action = path -> { + if (select) TreeUtil.selectPath(myTree, path); + else myTree.expandPath(path); + if (requestFocus) { + IdeFocusManager.getInstance(myProject).requestFocus(myTree, false); + } + return Promises.resolvedPromise(path); + }; + Function> fallback = new Function>() { + @Override + public Promise fun(TreePath path) { + if (myCurrentFocusPromise != result) { + result.setError("rejected"); + return Promises.rejectedPromise(); + } + else if (path == null && stage[0] == 2) { + // Some structure views merge unrelated psi elements into a structure node (MarkdownStructureViewModel). + // So turn off the isAncestor() optimization and retry once. + stage[0] = 3; + return myAsyncTreeModel.accept(visitor).thenAsync(this); + } + else { + TreePath adjusted = path == null ? deepestPath[0] : path; + return adjusted == null ? Promises.rejectedPromise() : action.fun(adjusted); + } + } + }; + myAsyncTreeModel.accept(visitor).thenAsync(fallback).processed(result); + return myCurrentFocusPromise; } private void scrollToSelectedElement() { @@ -533,7 +525,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre myAutoscrollAlarm.cancelAllRequests(); myAutoscrollAlarm.addRequest( () -> { - if (myAbstractTreeBuilder == null) return; + if (isDisposed()) return; if (UIUtil.isFocusAncestor(this)) return; scrollToSelectedElementInner(); }, 1000); @@ -555,14 +547,12 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre @Override public void dispose() { LOG.assertTrue(EventQueue.isDispatchThread(), Thread.currentThread().getName()); - myAbstractTreeBuilder = null; - // this will also dispose wrapped TreeModel - myTreeModelWrapper.dispose(); + myDisposed = true; myFileEditor = null; } public boolean isDisposed() { - return myAbstractTreeBuilder == null; + return myDisposed; } @Override @@ -577,17 +567,31 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre @Override public void setActionActive(String name, boolean state) { - StructureViewFactoryEx.getInstanceEx(myProject).setActiveAction(name, state); - rebuild(); - TreeUtil.expand(getTree(), 2); - } - - protected void rebuild() { + ApplicationManager.getApplication().assertIsDispatchThread(); storeState(); - ++ourSettingsModificationCount; - ((SmartTreeStructure)myAbstractTreeBuilder.getTreeStructure()).rebuildTree(); - myAbstractTreeBuilder.updateFromRoot(); - restoreState(); + StructureViewFactoryEx.getInstanceEx(myProject).setActiveAction(name, state); + ourSettingsModificationCount.incrementAndGet(); + + if (!ApplicationManager.getApplication().isUnitTestMode()) { + rebuild(); + } + else { + AtomicBoolean complete = new AtomicBoolean(false); + //noinspection TestOnlyProblems + Promise promise = rebuildAndUpdate().processed(ignore -> complete.set(true)); + while (!complete.get()) { + //noinspection TestOnlyProblems + UIUtil.dispatchAllInvocationEvents(); + try { + promise.blockingGet(20, TimeUnit.MILLISECONDS); + } + catch (Exception ignore) { + } + } + //noinspection TestOnlyProblems + UIUtil.dispatchAllInvocationEvents(); + } + TreeUtil.expand(getTree(), 2); } @Override @@ -595,18 +599,6 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre return !myProject.isDisposed() && StructureViewFactoryEx.getInstanceEx(myProject).isActionActive(name); } - public AbstractTreeStructure getTreeStructure() { - return myAbstractTreeBuilder.getTreeStructure(); - } - - public JTree getTree() { - return myAbstractTreeBuilder == null ? null : myAbstractTreeBuilder.getTree(); - } - - public AbstractTreeBuilder getTreeBuilder() { - return myAbstractTreeBuilder; - } - private final class MyAutoScrollToSourceHandler extends AutoScrollToSourceHandler { private boolean myShouldAutoScroll = true; @@ -627,7 +619,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre @Override protected void scrollToSource(Component tree) { - if (myAbstractTreeBuilder == null) return; + if (isDisposed()) return; myAutoscrollFeedback = true; Navigatable editSourceDescriptor = CommonDataKeys.NAVIGATABLE.getData(DataManager.getInstance().getDataContext(getTree())); @@ -704,7 +696,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre return element; } if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { - return convertToPsiElementsArray(getSelectedElements()); + return PsiUtilCore.toPsiElementArray(getSelectedElements().filter(PsiElement.class).toList()); } if (PlatformDataKeys.FILE_EDITOR.is(dataId)) { return myFileEditor; @@ -734,23 +726,9 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre return super.getData(dataId); } - @Nullable - private static PsiElement[] convertToPsiElementsArray(final Object[] selectedElements) { - if (selectedElements == null) return null; - ArrayList psiElements = new ArrayList<>(); - for (Object selectedElement : selectedElements) { - if (selectedElement instanceof PsiElement && ((PsiElement)selectedElement).isValid()) { - psiElements.add((PsiElement)selectedElement); - } - } - return PsiUtilCore.toPsiElementArray(psiElements); - } - @Nullable private TreePath getSelectedUniquePath() { - JTree tree = getTree(); - if (tree == null) return null; - TreePath[] paths = tree.getSelectionPaths(); + TreePath[] paths = getTree().getSelectionPaths(); return paths == null || paths.length != 1 ? null : paths[0]; } @@ -762,31 +740,65 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre @Override public boolean navigateToSelectedElement(boolean requestFocus) { - return select(myTreeModel.getCurrentEditorElement(), requestFocus); - } - - public void doUpdate() { - assert ApplicationManager.getApplication().isUnitTestMode(); - myAbstractTreeBuilder.queueUpdate(true); - } - -//todo [kirillk] dirty hack for discovering invalid psi elements, to delegate it to a proper place after 8.1 - public static boolean isValid(Object treeElement) { - if (treeElement instanceof StructureViewTreeElementWrapper) { - final StructureViewTreeElementWrapper wrapper = (StructureViewTreeElementWrapper)treeElement; - if (wrapper.getValue() instanceof PsiTreeElementBase) { - final PsiTreeElementBase psiNode = (PsiTreeElementBase)wrapper.getValue(); - return psiNode.isValid(); - } - } + select(myTreeModel.getCurrentEditorElement(), requestFocus); return true; } - public static class StructureViewTreeElementWrapper extends TreeElementWrapper implements NodeDescriptorProvidingKey { - private long childrenStamp = -1; - private long modificationCountForChildren = ourSettingsModificationCount; + @TestOnly + public AsyncPromise rebuildAndUpdate() { + AsyncPromise result = new AsyncPromise<>(); + rebuild(); + TreeVisitor visitor = path -> { + Object o = TreeUtil.getUserObject(path.getLastPathComponent()); + if (o instanceof AbstractTreeNode) ((AbstractTreeNode)o).update(); + return TreeVisitor.Action.CONTINUE; + }; + AtomicReference> thisRef = new AtomicReference<>(); + Consumer consumer = ignore -> EdtInvocationManager.getInstance().invokeLater(() -> { + if (myAsyncTreeModel.isProcessing()) myAsyncTreeModel.accept(visitor).processed(thisRef.get()); + else result.setResult(null); + }); + thisRef.set(consumer); + myStructureTreeModel.getInvoker().invokeLaterIfNeeded(() -> consumer.consume(null)); + return result; + } - public StructureViewTreeElementWrapper(Project project, TreeElement value, TreeModel treeModel) { + public String getHelpID() { + return HelpID.STRUCTURE_VIEW; + } + + @Override + public Dimension getCurrentSize() { + return getTree().getSize(); + } + + @Override + public void setReferenceSizeWhileInitializing(Dimension size) { + //_setRefSize(size); + // + //if (size != null) { + // todo com.intellij.ui.tree.AsyncTreeModelTest.invokeWhenProcessingDone() // + // myAbstractTreeBuilder.getReady(this).doWhenDone(() -> _setRefSize(null)); + //} + } + + //private void _setRefSize(Dimension size) { + // JTree tree = getTree(); + // tree.setPreferredSize(size); + // tree.setMinimumSize(size); + // tree.setMaximumSize(size); + // + // tree.revalidate(); + // tree.repaint(); + //} + + private static class MyNodeWrapper extends TreeElementWrapper + implements NodeDescriptorProvidingKey, ValidateableNode { + + private long childrenStamp = -1; + private int modificationCountForChildren = ourSettingsModificationCount.get(); + + MyNodeWrapper(Project project, TreeElement value, TreeModel treeModel) { super(project, value, treeModel); } @@ -795,19 +807,19 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre public Object getKey() { StructureViewTreeElement element = (StructureViewTreeElement)getValue(); if (element instanceof NodeDescriptorProvidingKey) return ((NodeDescriptorProvidingKey)element).getKey(); - Object value = element.getValue(); + Object value = element == null ? null : element.getValue(); return value == null ? this : value; } @Override @NotNull public Collection getChildren() { - if (ourSettingsModificationCount != modificationCountForChildren) { + if (ourSettingsModificationCount.get() != modificationCountForChildren) { resetChildren(); - modificationCountForChildren = ourSettingsModificationCount; + modificationCountForChildren = ourSettingsModificationCount.get(); } - final Object o = unwrapValue(getValue()); + Object o = unwrapElement(getValue()); long currentStamp = -1; if (o instanceof PsiElement) { if (!((PsiElement)o).isValid()) return Collections.emptyList(); @@ -816,7 +828,8 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre if (file != null) { currentStamp = file.getModificationStamp(); } - } else if (o instanceof ModificationTracker) { + } + else if (o instanceof ModificationTracker) { currentStamp = ((ModificationTracker)o).getModificationCount(); } if (childrenStamp != currentStamp) { @@ -859,91 +872,236 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre } @Override - protected TreeElementWrapper createChildNode(@NotNull final TreeElement child) { - return new StructureViewTreeElementWrapper(myProject, child, myTreeModel); + protected TreeElementWrapper createChildNode(@NotNull TreeElement child) { + return new MyNodeWrapper(myProject, child, myTreeModel); } @Override - protected GroupWrapper createGroupWrapper(final Project project, @NotNull Group group, final TreeModel treeModel) { - return new StructureViewGroup(project, group, treeModel); + protected GroupWrapper createGroupWrapper(Project project, @NotNull Group group, final TreeModel treeModel) { + return new MyGroupWrapper(project, group, treeModel); } public boolean equals(Object o) { - if (o instanceof StructureViewTreeElementWrapper) { - return Comparing.equal( - unwrapValue(getValue()), - unwrapValue(((StructureViewTreeElementWrapper)o).getValue()) - ); - } else if (o instanceof StructureViewTreeElement) { - return Comparing.equal( - unwrapValue(getValue()), - ((StructureViewTreeElement)o).getValue() - ); + if (o instanceof MyNodeWrapper) { + return Comparing.equal(unwrapElement(getValue()), unwrapElement(((MyNodeWrapper)o).getValue())); + } + else if (o instanceof StructureViewTreeElement) { + return Comparing.equal(unwrapElement(getValue()), ((StructureViewTreeElement)o).getValue()); } return false; } - private static Object unwrapValue(Object o) { - if (o instanceof StructureViewTreeElement) { - return ((StructureViewTreeElement)o).getValue(); - } - return o; + @Override + public boolean isValid() { + TreeElement value = getValue(); + PsiTreeElementBase psi = value instanceof PsiTreeElementBase ? (PsiTreeElementBase)value : null; + return psi == null || psi.isValid(); } public int hashCode() { - final Object o = unwrapValue(getValue()); + final Object o = unwrapElement(getValue()); return o != null ? o.hashCode() : 0; } + } - private static class StructureViewGroup extends GroupWrapper { - public StructureViewGroup(Project project, Group group, TreeModel treeModel) { - super(project, group, treeModel); + private static class MyGroupWrapper extends GroupWrapper { + MyGroupWrapper(Project project, Group group, TreeModel treeModel) { + super(project, group, treeModel); + } + + @Override + protected TreeElementWrapper createChildNode(@NotNull TreeElement child) { + return new MyNodeWrapper(getProject(), child, myTreeModel); + } + + + @Override + protected GroupWrapper createGroupWrapper(Project project, @NotNull Group group, TreeModel treeModel) { + return new MyGroupWrapper(project, group, treeModel); + } + + @Override + public boolean isAlwaysShowPlus() { + return true; + } + } + + private static class MyTree extends DnDAwareTree implements PlaceProvider { + MyTree(javax.swing.tree.TreeModel model) { + super(model); + HintUpdateSupply.installDataContextHintUpdateSupply(this); + } + + @Override + public String getPlace() { + return ActionPlaces.STRUCTURE_VIEW_TOOLBAR; + } + } + + private static class MyPsiTreeChangeListener extends PsiTreeChangeAdapter { + final PsiModificationTracker modTracker; + long prevModCount; + final Runnable onChange; + + private MyPsiTreeChangeListener(PsiModificationTracker modTracker, Runnable onChange) { + this.modTracker = modTracker; + this.onChange = onChange; + prevModCount = modTracker.getOutOfCodeBlockModificationCount(); + } + + @Override + public void childRemoved(@NotNull PsiTreeChangeEvent event) { + PsiElement child = event.getOldChild(); + if (child instanceof PsiWhiteSpace) return; //optimization + + childrenChanged(); + } + + @Override + public void childAdded(@NotNull PsiTreeChangeEvent event) { + PsiElement child = event.getNewChild(); + if (child instanceof PsiWhiteSpace) return; //optimization + childrenChanged(); + } + + @Override + public void childReplaced(@NotNull PsiTreeChangeEvent event) { + PsiElement oldChild = event.getOldChild(); + PsiElement newChild = event.getNewChild(); + if (oldChild instanceof PsiWhiteSpace && newChild instanceof PsiWhiteSpace) return; //optimization + childrenChanged(); + } + + @Override + public void childMoved(@NotNull PsiTreeChangeEvent event) { + childrenChanged(); + } + + @Override + public void childrenChanged(@NotNull PsiTreeChangeEvent event) { + childrenChanged(); + } + + private void childrenChanged() { + long newModificationCount = modTracker.getModificationCount(); + if (newModificationCount == prevModCount) return; + prevModCount = newModificationCount; + onChange.run(); + } + + @Override + public void propertyChanged(@NotNull PsiTreeChangeEvent event) { + childrenChanged(); + } + } + + public static Object unwrapValue(Object o) { + return unwrapElement(unwrapWrapper(o)); + } + + public static Object unwrapWrapper(Object o) { + return o instanceof MyNodeWrapper ? ((MyNodeWrapper)o).getValue() : o; + } + + private static Object unwrapElement(Object o) { + return o instanceof StructureViewTreeElement ? ((StructureViewTreeElement)o).getValue() : o; + } + + // for FileStructurePopup only + public static TreeElementWrapper createWrapper(Project project, TreeElement value, TreeModel treeModel) { + return new MyNodeWrapper(project, value, treeModel); + } + + private static class MyExpandListener extends TreeModelAdapter { + private final JTree tree; + final StructureViewModel.ExpandInfoProvider provider; + final boolean smartExpand; + + MyExpandListener(@NotNull JTree tree, @Nullable StructureViewModel.ExpandInfoProvider provider) { + this.tree = tree; + this.provider = provider; + smartExpand = provider != null && provider.isSmartExpand(); + } + + @Override + public void treeNodesInserted(TreeModelEvent e) { + Object[] children = e.getChildren(); + if (smartExpand && children.length == 1) { + ApplicationManager.getApplication().invokeLater( + () -> tree.expandPath(e.getTreePath().pathByAddingChild(children[0]))); } - - @Override - protected TreeElementWrapper createChildNode(@NotNull TreeElement child) { - return new StructureViewTreeElementWrapper(getProject(), child, myTreeModel); + else { + for (Object o : children) { + Object userObject = TreeUtil.getUserObject(o); + if (userObject instanceof NodeDescriptor && isAutoExpandNode((NodeDescriptor)userObject)) { + ApplicationManager.getApplication().invokeLater( + () -> tree.expandPath(e.getTreePath().pathByAddingChild(o))); + } + } } + } - - @Override - protected GroupWrapper createGroupWrapper(Project project, @NotNull Group group, TreeModel treeModel) { - return new StructureViewGroup(project, group, treeModel); + boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) { + if (provider != null) { + Object value = unwrapWrapper(nodeDescriptor.getElement()); + if (value instanceof StructureViewTreeElement) { + return provider.isAutoExpand((StructureViewTreeElement)value); + } + else if (value instanceof GroupWrapper) { + Group group = ObjectUtils.notNull(((GroupWrapper)value).getValue()); + for (TreeElement treeElement : group.getChildren()) { + if (treeElement instanceof StructureViewTreeElement && !provider.isAutoExpand((StructureViewTreeElement)treeElement)) { + return false; + } + } + } } + // expand root node & its immediate children + NodeDescriptor parent = nodeDescriptor.getParentDescriptor(); + return parent == null || parent.getParentDescriptor() == null; + } + } - @Override - public boolean isAlwaysShowPlus() { + // todo remove ASAP ------------------------------------ + + @Deprecated + @Nullable + public AbstractTreeBuilder getTreeBuilder() { + return myTreeBuilder; + } + + private ArrayList getPathToElement(Object element) { + ArrayList result = new ArrayList<>(); + final AbstractTreeStructure treeStructure = myTreeBuilder.getTreeStructure(); + if (treeStructure != null) { + addToPath((AbstractTreeNode)treeStructure.getRootElement(), element, result, new THashSet<>()); + } + return result; + } + + private static boolean addToPath(AbstractTreeNode rootElement, Object element, ArrayList result, Collection processedElements) { + Object value = rootElement.getValue(); + if (value instanceof StructureViewTreeElement) { + value = ((StructureViewTreeElement) value).getValue(); + } + if (!processedElements.add(value)){ + return false; + } + + if (Comparing.equal(value, element)){ + result.add(0, rootElement); + return true; + } + + Collection children = rootElement.getChildren(); + for (AbstractTreeNode child : children) { + if (addToPath(child, element, result, processedElements)) { + result.add(0, rootElement); return true; } } - } - public String getHelpID() { - return ourHelpID; - } - - @Override - public Dimension getCurrentSize() { - return getTree().getSize(); - } - - @Override - public void setReferenceSizeWhileInitializing(Dimension size) { - _setRefSize(size); - - if (size != null) { - myAbstractTreeBuilder.getReady(this).doWhenDone(() -> _setRefSize(null)); - } - } - - private void _setRefSize(Dimension size) { - JTree tree = getTree(); - tree.setPreferredSize(size); - tree.setMinimumSize(size); - tree.setMaximumSize(size); - - tree.revalidate(); - tree.repaint(); + return false; } } diff --git a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java index 1d021262c9b5..f00fd86cb2c6 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java +++ b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java @@ -25,7 +25,6 @@ import com.intellij.ide.dnd.aware.DnDAwareTree; import com.intellij.ide.structureView.ModelListener; import com.intellij.ide.structureView.StructureView; import com.intellij.ide.structureView.StructureViewModel; -import com.intellij.ide.structureView.StructureViewTreeElement; import com.intellij.ide.structureView.impl.StructureViewComposite; import com.intellij.ide.structureView.impl.common.PsiTreeElementBase; import com.intellij.ide.structureView.newStructureView.StructureViewComponent; @@ -34,8 +33,8 @@ import com.intellij.ide.structureView.newStructureView.TreeActionsOwner; import com.intellij.ide.structureView.newStructureView.TreeModelWrapper; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.ide.util.treeView.NodeRenderer; +import com.intellij.ide.util.treeView.ValidateableNode; import com.intellij.ide.util.treeView.smartTree.*; -import com.intellij.navigation.ItemPresentation; import com.intellij.navigation.LocationPresentation; import com.intellij.openapi.Disposable; import com.intellij.openapi.MnemonicHelper; @@ -181,7 +180,7 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner { @Override protected TreeElementWrapper createTree() { - return new StructureViewComponent.StructureViewTreeElementWrapper(myProject, myModel.getRoot(), myModel); + return StructureViewComponent.createWrapper(myProject, myModel.getRoot(), myModel); } @NonNls @@ -260,7 +259,9 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner { @Override protected boolean validateNode(Object child) { - return StructureViewComponent.isValid(child); + Object o = child instanceof FilteringTreeStructure.FilteringNode ? + ((FilteringTreeStructure.FilteringNode)child).getDelegate() : child; + return !(o instanceof ValidateableNode) || ((ValidateableNode)o).isValid(); } @Override @@ -504,17 +505,7 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner { @Nullable private PsiElement getPsi(FilteringTreeStructure.FilteringNode n) { - final Object delegate = n.getDelegate(); - if (delegate instanceof StructureViewComponent.StructureViewTreeElementWrapper) { - final TreeElement value = ((StructureViewComponent.StructureViewTreeElementWrapper)delegate).getValue(); - if (value instanceof StructureViewTreeElement) { - final Object element = ((StructureViewTreeElement)value).getValue(); - if (element instanceof PsiElement) { - return (PsiElement)element; - } - } - } - return null; + return ObjectUtils.tryCast(StructureViewComponent.unwrapValue(n.getDelegate()), PsiElement.class); } @Override @@ -924,38 +915,32 @@ public class FileStructurePopup implements Disposable, TreeActionsOwner { } @Nullable - public static String getSpeedSearchText(final Object userObject) { + public static String getSpeedSearchText(Object userObject) { String text = String.valueOf(userObject); + TreeElement unwrapped = (TreeElement)StructureViewComponent.unwrapWrapper(userObject); if (text != null) { - if (userObject instanceof StructureViewComponent.StructureViewTreeElementWrapper) { - final TreeElement value = ((StructureViewComponent.StructureViewTreeElementWrapper)userObject).getValue(); - if (value instanceof PsiTreeElementBase && ((PsiTreeElementBase)value).isSearchInLocationString()) { - final String locationString = ((PsiTreeElementBase)value).getLocationString(); - if (!StringUtil.isEmpty(locationString)) { - String locationPrefix = null; - String locationSuffix = null; - if (value instanceof LocationPresentation) { - locationPrefix = ((LocationPresentation)value).getLocationPrefix(); - locationSuffix = ((LocationPresentation)value).getLocationSuffix(); - } - - return text + - StringUtil.notNullize(locationPrefix, LocationPresentation.DEFAULT_LOCATION_PREFIX) + - locationString + - StringUtil.notNullize(locationSuffix, LocationPresentation.DEFAULT_LOCATION_SUFFIX); + if (unwrapped instanceof PsiTreeElementBase && ((PsiTreeElementBase)unwrapped).isSearchInLocationString()) { + String locationString = ((PsiTreeElementBase)unwrapped).getLocationString(); + if (!StringUtil.isEmpty(locationString)) { + String locationPrefix = null; + String locationSuffix = null; + if (unwrapped instanceof LocationPresentation) { + locationPrefix = ((LocationPresentation)unwrapped).getLocationPrefix(); + locationSuffix = ((LocationPresentation)unwrapped).getLocationSuffix(); } + + return text + + StringUtil.notNullize(locationPrefix, LocationPresentation.DEFAULT_LOCATION_PREFIX) + + locationString + + StringUtil.notNullize(locationSuffix, LocationPresentation.DEFAULT_LOCATION_SUFFIX); } } return text; } // NB!: this point is achievable if the following method returns null // see com.intellij.ide.util.treeView.NodeDescriptor.toString - if (userObject instanceof StructureViewComponent.StructureViewTreeElementWrapper) { - return ReadAction.compute(() -> { - final ItemPresentation presentation = - ((StructureViewComponent.StructureViewTreeElementWrapper)userObject).getValue().getPresentation(); - return presentation.getPresentableText(); - }); + if (unwrapped != null) { + return ReadAction.compute(() -> unwrapped.getPresentation().getPresentableText()); } return null; diff --git a/platform/platform-impl/src/com/intellij/ui/tree/StructureTreeModel.java b/platform/platform-impl/src/com/intellij/ui/tree/StructureTreeModel.java index 181f5cdb853e..b820371f73ee 100644 --- a/platform/platform-impl/src/com/intellij/ui/tree/StructureTreeModel.java +++ b/platform/platform-impl/src/com/intellij/ui/tree/StructureTreeModel.java @@ -25,20 +25,16 @@ import com.intellij.util.concurrency.Invoker; import com.intellij.util.concurrency.InvokerSupplier; import com.intellij.util.ui.tree.AbstractTreeModel; import org.jetbrains.annotations.NotNull; +import org.jetbrains.concurrency.AsyncPromise; +import org.jetbrains.concurrency.Promise; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; +import java.util.*; -import static java.util.Collections.emptyList; -import static java.util.Collections.enumeration; -import static java.util.Collections.unmodifiableList; +import static java.util.Collections.*; /** * @author Sergey.Malenkov @@ -95,14 +91,22 @@ public class StructureTreeModel extends AbstractTreeModel implements Disposable, return false; } - public final void invalidate() { + @NotNull + public final Promise invalidate() { + AsyncPromise promise = new AsyncPromise<>(); invoker.invokeLaterIfNeeded(() -> { - if (disposed) return; + if (disposed) { + promise.setError("rejected"); + return; + } root.invalidate(); Node node = root.get(); + LOG.debug("root invalidated: ", node); if (node != null) node.invalidate(); treeStructureChanged(null, null, null); + promise.setResult(null); }); + return promise; } public final void invalidate(@NotNull TreePath path, boolean structure) { @@ -127,7 +131,11 @@ public class StructureTreeModel extends AbstractTreeModel implements Disposable, public final Object getRoot() { if (disposed || !isValidThread()) return null; if (!root.isValid()) { - root.set(getValidRoot()); + Node validRoot = getValidRoot(); + LOG.debug("old root: " + root.get()); + LOG.debug("new root: " + validRoot); + LOG.debug(new IllegalStateException()); + root.set(validRoot); } return root.get(); } diff --git a/platform/structure-view-impl/src/com/intellij/ide/structureView/impl/StructureViewState.java b/platform/structure-view-impl/src/com/intellij/ide/structureView/impl/StructureViewState.java deleted file mode 100644 index 403487d4a8e1..000000000000 --- a/platform/structure-view-impl/src/com/intellij/ide/structureView/impl/StructureViewState.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.intellij.ide.structureView.impl; - - - -/** - * @author Yura Cangea - */ -public final class StructureViewState { - private Object[] myExpandedElements; - private Object[] mySelectedElements; - - public Object[] getExpandedElements() { - return myExpandedElements; - } - - public void setExpandedElements(Object[] expandedElements) { - myExpandedElements = expandedElements; - } - - public Object[] getSelectedElements() { - return mySelectedElements; - } - - public void setSelectedElements(Object[] selectedElements) { - mySelectedElements = selectedElements; - } -} diff --git a/plugins/properties/src/com/intellij/lang/properties/editor/NewPropertyAction.java b/plugins/properties/src/com/intellij/lang/properties/editor/NewPropertyAction.java index ed9d8b7a5bea..a5dfb8a89735 100644 --- a/plugins/properties/src/com/intellij/lang/properties/editor/NewPropertyAction.java +++ b/plugins/properties/src/com/intellij/lang/properties/editor/NewPropertyAction.java @@ -157,11 +157,9 @@ class NewPropertyAction extends AnAction { }); resourceBundleEditor.updateTreeRoot(); - resourceBundleEditor - .getStructureViewComponent() - .getTreeBuilder() - .queueUpdate() - .doWhenDone(() -> finalResourceBundleEditor.selectProperty(keyToInsert)); + resourceBundleEditor.getStructureViewComponent() + .select(keyToInsert, false) + .processed(p -> finalResourceBundleEditor.selectProperty(keyToInsert)); } } diff --git a/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditor.java b/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditor.java index 1967118a3c4a..719ac56fce6c 100644 --- a/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditor.java +++ b/plugins/properties/src/com/intellij/lang/properties/editor/ResourceBundleEditor.java @@ -25,7 +25,6 @@ import com.intellij.ide.structureView.newStructureView.StructureViewComponent; import com.intellij.ide.ui.customization.CustomActionsSchema; import com.intellij.ide.util.treeView.AbstractTreeNode; import com.intellij.ide.util.treeView.AbstractTreeUi; -import com.intellij.ide.util.treeView.smartTree.CachingChildrenTreeNode; import com.intellij.ide.util.treeView.smartTree.Sorter; import com.intellij.ide.util.treeView.smartTree.TreeElement; import com.intellij.lang.properties.IProperty; @@ -76,12 +75,13 @@ import com.intellij.ui.components.JBScrollPane; import com.intellij.util.Alarm; import com.intellij.util.EditorPopupHandler; import com.intellij.util.IncorrectOperationException; -import com.intellij.util.NullableFunction; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtilRt; +import com.intellij.util.containers.JBIterable; import com.intellij.util.containers.Stack; import com.intellij.util.ui.JBUI; import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeUtil; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; @@ -92,8 +92,6 @@ import javax.swing.*; import javax.swing.border.TitledBorder; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; -import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; @@ -228,10 +226,7 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document } public void updateTreeRoot() { - final Object element = myStructureViewComponent.getTreeStructure().getRootElement(); - if (element instanceof CachingChildrenTreeNode) { - ((CachingChildrenTreeNode)element).rebuildChildren(); - } + myStructureViewComponent.rebuild(); } @NotNull @@ -280,10 +275,6 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document return; } JTree tree = myStructureViewComponent.getTree(); - if (tree == null) { - return; - } - Object root = tree.getModel().getRoot(); if (AbstractTreeUi.isLoadingChildrenFor(root)) { boolean isEditorVisible = false; @@ -360,14 +351,6 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document } } - @Nullable - private static ResourceBundleEditorViewElement getSelectedElement(@NotNull DefaultMutableTreeNode node) { - Object userObject = node.getUserObject(); - if (!(userObject instanceof AbstractTreeNode)) return null; - Object value = ((AbstractTreeNode)userObject).getValue(); - return value instanceof ResourceBundleEditorViewElement ? (ResourceBundleEditorViewElement) value : null; - } - private void writeEditorPropertyValue(final @Nullable String propertyName, final @NotNull Editor editor, final @NotNull VirtualFile propertiesFile) { @@ -597,15 +580,13 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document } @NotNull - private Collection getSelectedNodes() { + private JBIterable getSelectedNodes() { if (!isValid()) { - return Collections.emptyList(); + return JBIterable.empty(); } JTree tree = myStructureViewComponent.getTree(); - if (tree == null) return Collections.emptyList(); - TreePath[] selected = tree.getSelectionModel().getSelectionPaths(); - if (selected == null || selected.length == 0) return Collections.emptyList(); - return ContainerUtil.map(selected, treePath -> (DefaultMutableTreeNode)treePath.getLastPathComponent()); + return JBIterable.of(tree.getSelectionModel().getSelectionPaths()) + .map(o -> TreeUtil.getUserObject(o.getLastPathComponent())); } @Nullable @@ -616,25 +597,22 @@ public class ResourceBundleEditor extends UserDataHolderBase implements Document @Nullable IProperty getSelectedProperty() { - final Collection selectedNode = getSelectedNodes(); - if (selectedNode.isEmpty()) { - return null; - } - final ResourceBundleEditorViewElement element = getSelectedElement(ContainerUtil.getFirstItem(selectedNode)); - return element instanceof ResourceBundlePropertyStructureViewElement ? ((ResourceBundlePropertyStructureViewElement)element).getProperty() - : null; + ResourceBundleEditorViewElement first = getSelectedNodes() + .filter(AbstractTreeNode.class) + .filterMap(AbstractTreeNode::getValue) + .filter(ResourceBundleEditorViewElement.class) + .first(); + return first instanceof ResourceBundlePropertyStructureViewElement ? + ((ResourceBundlePropertyStructureViewElement)first).getProperty() : null; } @NotNull public Collection getSelectedElements() { - final Collection selectedNodes = getSelectedNodes(); - return ContainerUtil.mapNotNull(selectedNodes, - (NullableFunction)selectedNode -> { - Object userObject = selectedNode.getUserObject(); - if (!(userObject instanceof AbstractTreeNode)) return null; - Object value = ((AbstractTreeNode)userObject).getValue(); - return value instanceof ResourceBundleEditorViewElement ? (ResourceBundleEditorViewElement) value : null; - }); + return getSelectedNodes() + .filter(AbstractTreeNode.class) + .filterMap(AbstractTreeNode::getValue) + .filter(ResourceBundleEditorViewElement.class) + .toList(); } @Nullable diff --git a/resources/src/idea/RichPlatformPlugin.xml b/resources/src/idea/RichPlatformPlugin.xml index 6924f8bae965..a016af51cc54 100644 --- a/resources/src/idea/RichPlatformPlugin.xml +++ b/resources/src/idea/RichPlatformPlugin.xml @@ -387,6 +387,9 @@ New implementation based on AsyncTreeModel + StructureTreeModel + + New implementation based on AsyncTreeModel + StructureTreeModel +