async structure view: tool window

This commit is contained in:
Gregory.Shrago
2017-10-05 04:51:23 +03:00
parent d757d7edeb
commit 4dfe72a789
10 changed files with 647 additions and 582 deletions
@@ -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";
}
@@ -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<PsiAnchor> expanded = collectAnchors(state.getExpandedElements());
List<PsiAnchor> 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<PsiAnchor> collectAnchors(final Object[] expandedElements) {
List<PsiAnchor> 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
@@ -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);
}
}
@@ -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;
@@ -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<Object> 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();
}
@@ -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;
}
}
@@ -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));
}
}
@@ -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<DefaultMutableTreeNode> getSelectedNodes() {
private JBIterable<Object> 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<DefaultMutableTreeNode> 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<ResourceBundleEditorViewElement> getSelectedElements() {
final Collection<DefaultMutableTreeNode> selectedNodes = getSelectedNodes();
return ContainerUtil.mapNotNull(selectedNodes,
(NullableFunction<DefaultMutableTreeNode, ResourceBundleEditorViewElement>)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
@@ -387,6 +387,9 @@
<experimentalFeature id="project.view.async.tree.model" internalFeature="true" percentOfUsers="10" requireRestart="true">
<description>New implementation based on AsyncTreeModel + StructureTreeModel</description>
</experimentalFeature>
<experimentalFeature id="structure.view.async.tree.model" internalFeature="true" percentOfUsers="10" requireRestart="false">
<description>New implementation based on AsyncTreeModel + StructureTreeModel</description>
</experimentalFeature>
<editorActionHandler action="EditorChooseLookupItemReplace"
implementationClass="com.intellij.codeInsight.hint.NextParameterAfterCompletionHandler"/>