From 2d8b758a662fafda3e6fcc0577ec3b088e360540 Mon Sep 17 00:00:00 2001 From: Alexey Merkulov Date: Mon, 23 Apr 2018 19:43:13 +0300 Subject: [PATCH] RUBY-12492 Supported aliases in file structure Also org.jetbrains.yaml.structureView package was refactored --- .../resources/messages/YAMLBundle.properties | 6 + .../DuplicatedPsiTreeElementBase.java | 175 ++++++++++++++++++ .../YAMLAliasResolveNodeProvider.java | 103 +++++++++++ .../YAMLStructureViewBuilder.java | 31 ---- .../YAMLStructureViewDocument.java | 36 ++++ .../YAMLStructureViewElement.java | 150 --------------- .../YAMLStructureViewFactory.java | 62 ++++++- .../structureView/YAMLStructureViewFile.java | 49 +++++ .../YAMLStructureViewKeyValue.java | 71 +++++++ .../YAMLStructureViewSequenceItem.java | 77 ++++++++ .../yaml/structure/YAMLStructureTest.java | 38 ++++ .../data/anchorsAndAliases.resolved.txt | 14 ++ .../data/anchorsAndAliases.unresolved.txt | 10 + .../yaml/structure/data/anchorsAndAliases.yml | 8 + 14 files changed, 646 insertions(+), 184 deletions(-) create mode 100644 plugins/yaml/src/org/jetbrains/yaml/structureView/DuplicatedPsiTreeElementBase.java create mode 100644 plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLAliasResolveNodeProvider.java delete mode 100644 plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewBuilder.java create mode 100644 plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewDocument.java delete mode 100644 plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewElement.java create mode 100644 plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewFile.java create mode 100644 plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewKeyValue.java create mode 100644 plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewSequenceItem.java create mode 100644 plugins/yaml/testSrc/org/jetbrains/yaml/structure/YAMLStructureTest.java create mode 100644 plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.resolved.txt create mode 100644 plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.unresolved.txt create mode 100644 plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.yml diff --git a/plugins/yaml/resources/messages/YAMLBundle.properties b/plugins/yaml/resources/messages/YAMLBundle.properties index 7f9e2137cec8..7560753c1d3d 100644 --- a/plugins/yaml/resources/messages/YAMLBundle.properties +++ b/plugins/yaml/resources/messages/YAMLBundle.properties @@ -40,6 +40,9 @@ YamlScalarType.error.scalar.value=Scalar value expected YamlIntegerType.error.integer.value=Integer value expected YamlNumberType.error.numeric.value=Numeric value expected +YAMLStructureViewDocument.element.name=YAML document +YAMLStructureViewSequenceItem.element.name=Sequence Item + YAMLParser.invalid.header.symbols=Invalid block scalar header YAMLParser.multiple.anchors=Multiple anchors are not permitted YAMLParser.multiple.tags=Multiple tags are not permitted @@ -47,6 +50,9 @@ YAMLParser.multiple.tags=Multiple tags are not permitted YAMLAnchorRenameProcessor.lost.alias=Alias will refer to another anchor YAMLAnchorRenameProcessor.reuse=Anchor already has the same name +YAMLAliasResolveNodeProvider.action.name=Show aliased sub-trees +YAMLAliasResolveNodeProvider.action.description=Resolve aliases and show referenced values in the structure + inspections.group.name=YAML inspections.unresolved.alias.name=Unresolved alias inspections.unresolved.alias.message=Can''t resolve alias {0} diff --git a/plugins/yaml/src/org/jetbrains/yaml/structureView/DuplicatedPsiTreeElementBase.java b/plugins/yaml/src/org/jetbrains/yaml/structureView/DuplicatedPsiTreeElementBase.java new file mode 100644 index 000000000000..2798cf9051d0 --- /dev/null +++ b/plugins/yaml/src/org/jetbrains/yaml/structureView/DuplicatedPsiTreeElementBase.java @@ -0,0 +1,175 @@ +// Copyright 2000-2018 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 org.jetbrains.yaml.structureView; + +import com.intellij.ide.structureView.StructureViewTreeElement; +import com.intellij.ide.structureView.impl.common.PsiTreeElementBase; +import com.intellij.ide.util.treeView.AbstractTreeUi; +import com.intellij.ide.util.treeView.NodeDescriptorProvidingKey; +import com.intellij.ide.util.treeView.TreeAnchorizer; +import com.intellij.navigation.ItemPresentation; +import com.intellij.pom.Navigatable; +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; +import java.util.Objects; + +/** + * File structure popup work with unique values (see {@link StructureViewTreeElement#getValue()}). + * If it is needed to show the same PSI element several times then each occurrence of such psi should be wrapped to some unique object. + * This class perform this task. + *

+ * This class could be moved to some common platform package if it will be needed for somebody else. + * The implementation of this class is based on {@link PsiTreeElementBase} implementation (copy-pasted and modified). + */ +abstract class DuplicatedPsiTreeElementBase implements StructureViewTreeElement, + ItemPresentation, + NodeDescriptorProvidingKey { + @NotNull + private final StoredData myValue; + + /** @param details is used to distinguish different occurrences of the same PSI element */ + protected DuplicatedPsiTreeElementBase(@NotNull T psiElement, @NotNull String details) { + myValue = new StoredData<>(psiElement, details); + } + + /** @return stored psi element */ + @NotNull + public final T getElement() { + return getValue().getElement(); + } + + /** @return additional information which is used to distinguish different occurrences of the same PSI element */ + @NotNull + public final String getDetails() { + return getValue().getDetails(); + } + + @NotNull + public abstract Collection getChildrenBase(); + + @NotNull + @Override + public ItemPresentation getPresentation() { + return this; + } + + @NotNull + @Override + public Object getKey() { + return String.valueOf(getElement()); + } + + @Contract(pure = true) + @NotNull + @Override + public final StoredData getValue() { + return myValue; + } + + public String toString() { + return getElement().toString(); + } + + @NotNull + @Override + public final StructureViewTreeElement[] getChildren() { + return AbstractTreeUi.calculateYieldingToWriteAction(this::doGetChildren); + } + + @NotNull + private StructureViewTreeElement[] doGetChildren() { + return PsiTreeElementBase.mergeWithExtensions(getElement(), getChildrenBase()); + } + + @Override + public void navigate(boolean requestFocus) { + getElement().navigate(requestFocus); + } + + @Override + public boolean canNavigate() { + return getElement().canNavigate(); + } + + @Override + public boolean canNavigateToSource() { + return canNavigate(); + } + + @Contract(value = "null -> false", pure = true) + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final DuplicatedPsiTreeElementBase that = (DuplicatedPsiTreeElementBase)o; + + return getValue().equals(that.getValue()); + } + + public int hashCode() { + return getValue().hashCode(); + } + + public boolean isValid() { + return true; + } + + /** This class is a wrapper over PSI element with additional information */ + private static class StoredData implements Navigatable { + @NotNull + private final Object myAnchor; + @NotNull + private final String myDetails; + + public StoredData(@NotNull T element, @NotNull String details) { + myAnchor = TreeAnchorizer.getService().createAnchor(element); + myDetails = details; + } + + @NotNull + public T getElement() { + @SuppressWarnings("unchecked") + T element = (T)TreeAnchorizer.getService().retrieveElement(myAnchor); + assert element != null; + return element; + } + + @NotNull + public String getDetails() { + return myDetails; + } + + @Override + public void navigate(boolean requestFocus) { + getElement().navigate(requestFocus); + } + + @Override + public boolean canNavigate() { + return getElement().canNavigate(); + } + + @Override + public boolean canNavigateToSource() { + return getElement().canNavigateToSource(); + } + + @Contract(value = "null -> false", pure = true) + @Override + public boolean equals(@Nullable Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + StoredData data = (StoredData)o; + return Objects.equals(getElement(), data.getElement()) && + Objects.equals(getDetails(), data.getDetails()); + } + + @Override + public int hashCode() { + return Objects.hash(getElement(), getDetails()); + } + } +} diff --git a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLAliasResolveNodeProvider.java b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLAliasResolveNodeProvider.java new file mode 100644 index 000000000000..ec11a5acbc62 --- /dev/null +++ b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLAliasResolveNodeProvider.java @@ -0,0 +1,103 @@ +// Copyright 2000-2018 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 org.jetbrains.yaml.structureView; + +import com.intellij.ide.structureView.StructureViewTreeElement; +import com.intellij.ide.util.ActionShortcutProvider; +import com.intellij.ide.util.FileStructureNodeProvider; +import com.intellij.ide.util.treeView.smartTree.ActionPresentation; +import com.intellij.ide.util.treeView.smartTree.ActionPresentationData; +import com.intellij.ide.util.treeView.smartTree.TreeElement; +import com.intellij.openapi.actionSystem.Shortcut; +import com.intellij.openapi.util.Ref; +import com.intellij.psi.PsiElement; +import com.intellij.util.IncorrectOperationException; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.YAMLBundle; +import org.jetbrains.yaml.psi.*; +import org.jetbrains.yaml.resolve.YAMLAliasReference; + +import java.util.Collection; +import java.util.Collections; + +public class YAMLAliasResolveNodeProvider implements FileStructureNodeProvider, ActionShortcutProvider { + @NonNls public static final String ID = "YAML_SHOW_RESOLVED_ALIAS_VALUES"; + + @NotNull + @Override + public String getCheckBoxText() { + return YAMLBundle.message("YAMLAliasResolveNodeProvider.action.name"); + } + + @NotNull + @Override + public Shortcut[] getShortcut() { + throw new IncorrectOperationException("see getActionIdForShortcut()"); + } + + @NotNull + @Override + public String getActionIdForShortcut() { + return "FileStructurePopup"; + } + + @NotNull + @Override + public Collection provideNodes(@NotNull TreeElement node) { + if (!(node instanceof DuplicatedPsiTreeElementBase)) { + return Collections.emptyList(); + } + DuplicatedPsiTreeElementBase yamlNode = (DuplicatedPsiTreeElementBase)node; + PsiElement psiElem = yamlNode.getElement(); + YAMLPsiElement yamlElem = psiElem instanceof YAMLPsiElement ? (YAMLPsiElement)psiElem : null; + YAMLValue value = getContainedValue(yamlElem); + if (!(value instanceof YAMLAlias)) { + return Collections.emptyList(); + } + return YAMLStructureViewFactory.createChildrenViewTreeElements(resolveAlias((YAMLAlias)value), yamlNode.getDetails()); + } + + @NotNull + @Override + public ActionPresentation getPresentation() { + return new ActionPresentationData(YAMLBundle.message("YAMLAliasResolveNodeProvider.action.name"), + YAMLBundle.message("YAMLAliasResolveNodeProvider.action.description"), + YAMLStructureViewFactory.ALIAS_ICON); + } + + @NotNull + @Override + public String getName() { + return ID; + } + + @Nullable + private static YAMLValue resolveAlias(@NotNull YAMLAlias alias) { + YAMLAliasReference ref = alias.getReference(); + YAMLAnchor anchor = ref == null ? null : ref.resolve(); + return anchor != null ? anchor.getMarkedValue() : null; + } + + @Nullable + @Contract("null -> null") + private static YAMLValue getContainedValue(@Nullable YAMLPsiElement element) { + if (element == null) { + return null; + } + Ref result = Ref.create(); + element.accept(new YamlPsiElementVisitor() { + @Override + public void visitKeyValue(@NotNull YAMLKeyValue keyValue) { + result.set(keyValue.getValue()); + } + + @Override + public void visitSequenceItem(@NotNull YAMLSequenceItem sequenceItem) { + result.set(sequenceItem.getValue()); + } + }); + return result.get(); + } +} diff --git a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewBuilder.java b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewBuilder.java deleted file mode 100644 index 0373ae182c3d..000000000000 --- a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewBuilder.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jetbrains.yaml.structureView; - -import com.intellij.ide.structureView.StructureViewModel; -import com.intellij.ide.structureView.StructureViewModelBase; -import com.intellij.ide.structureView.TreeBasedStructureViewBuilder; -import com.intellij.ide.util.treeView.smartTree.Sorter; -import com.intellij.openapi.editor.Editor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.yaml.psi.YAMLDocument; -import org.jetbrains.yaml.psi.YAMLFile; -import org.jetbrains.yaml.psi.YAMLKeyValue; - -/** - * @author oleg - */ -public class YAMLStructureViewBuilder extends TreeBasedStructureViewBuilder { - private final YAMLFile myPsiFile; - - public YAMLStructureViewBuilder(@NotNull final YAMLFile psiFile) { - myPsiFile = psiFile; - } - - @Override - @NotNull - public StructureViewModel createStructureViewModel(@Nullable Editor editor) { - return new StructureViewModelBase(myPsiFile, editor, new YAMLStructureViewElement(myPsiFile)) - .withSorters(Sorter.ALPHA_SORTER) - .withSuitableClasses(YAMLFile.class, YAMLDocument.class, YAMLKeyValue.class); - } -} \ No newline at end of file diff --git a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewDocument.java b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewDocument.java new file mode 100644 index 000000000000..6a1f0c9b1a53 --- /dev/null +++ b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewDocument.java @@ -0,0 +1,36 @@ +// Copyright 2000-2018 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 org.jetbrains.yaml.structureView; + +import com.intellij.ide.structureView.StructureViewTreeElement; +import com.intellij.ide.structureView.impl.common.PsiTreeElementBase; +import com.intellij.util.PlatformIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.yaml.YAMLBundle; +import org.jetbrains.yaml.psi.YAMLDocument; + +import javax.swing.*; +import java.util.Collection; +import java.util.Objects; + +class YAMLStructureViewDocument extends PsiTreeElementBase { + YAMLStructureViewDocument(@NotNull YAMLDocument psiElement) { + super(psiElement); + } + + @NotNull + @Override + public Collection getChildrenBase() { + YAMLDocument document = Objects.requireNonNull(getElement()); + return YAMLStructureViewFactory.createChildrenViewTreeElements(document.getTopLevelValue(), ""); + } + + @NotNull + public String getPresentableText() { + return YAMLBundle.message("YAMLStructureViewDocument.element.name"); + } + + @NotNull + public Icon getIcon(boolean open) { + return PlatformIcons.XML_TAG_ICON; + } +} diff --git a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewElement.java b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewElement.java deleted file mode 100644 index df7601aff403..000000000000 --- a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewElement.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.jetbrains.yaml.structureView; - -import com.intellij.ide.structureView.StructureViewTreeElement; -import com.intellij.navigation.ItemPresentation; -import com.intellij.util.PlatformIcons; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.jetbrains.yaml.psi.*; - -import javax.swing.*; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * @author oleg - */ -public class YAMLStructureViewElement implements StructureViewTreeElement { - private final YAMLPsiElement myElement; - - public YAMLStructureViewElement(final YAMLPsiElement element) { - myElement = element; - } - - @NotNull - public StructureViewTreeElement[] getChildren() { - final Collection children; - if (myElement instanceof YAMLFile) { - children = ((YAMLFile)myElement).getDocuments(); - } - else if (myElement instanceof YAMLDocument) { - children = getChildrenForValue(((YAMLDocument)myElement).getTopLevelValue()); - } - else if (myElement instanceof YAMLSequenceItem) { - children = getChildrenForValue(((YAMLSequenceItem)myElement).getValue()); - } - else if (myElement instanceof YAMLKeyValue) { - children = getChildrenForValue(((YAMLKeyValue)myElement).getValue()); - } - else { - children = Collections.emptyList(); - } - - final List structureElements = new ArrayList<>(); - for (YAMLPsiElement child : children) { - structureElements.add(new YAMLStructureViewElement(child)); - } - return structureElements.toArray(StructureViewTreeElement.EMPTY_ARRAY); - } - - @NotNull - private static Collection getChildrenForValue(@Nullable YAMLPsiElement element) { - if (element instanceof YAMLMapping) { - return ((YAMLMapping)element).getKeyValues(); - } - if (element instanceof YAMLSequence) { - return ((YAMLSequence)element).getItems(); - } - return Collections.emptyList(); - } - - - - @NotNull - public ItemPresentation getPresentation() { - if (myElement instanceof YAMLKeyValue){ - final YAMLKeyValue kv = (YAMLKeyValue)myElement; - return new ItemPresentation() { - public String getPresentableText() { - return kv.getKeyText(); - } - - public String getLocationString() { - if (kv.getValue() instanceof YAMLScalar) { - return kv.getValueText(); - } - else { - return null; - } - } - - public Icon getIcon(boolean open) { - final YAMLValue value = kv.getValue(); - return value instanceof YAMLScalar ? kv.getIcon(0) : PlatformIcons.XML_TAG_ICON; - } - }; - } - if (myElement instanceof YAMLDocument){ - return new ItemPresentation() { - public String getPresentableText() { - return "YAML document"; - } - - public String getLocationString() { - return null; - } - - public Icon getIcon(boolean open) { - return PlatformIcons.XML_TAG_ICON; - } - }; - } - if (myElement instanceof YAMLSequenceItem) { - final YAMLSequenceItem item = ((YAMLSequenceItem)myElement); - return new ItemPresentation() { - @Nullable - @Override - public String getPresentableText() { - if (item.getValue() instanceof YAMLScalar) { - return ((YAMLScalar)item.getValue()).getTextValue(); - } - else { - return "Sequence Item"; - } - } - - @Nullable - @Override - public String getLocationString() { - return null; - } - - @Nullable - @Override - public Icon getIcon(boolean unused) { - return item.getValue() instanceof YAMLScalar ? PlatformIcons.PROPERTY_ICON : PlatformIcons.XML_TAG_ICON; - } - }; - } - return myElement.getPresentation(); - } - - public YAMLPsiElement getValue() { - return myElement; - } - - public void navigate(boolean requestFocus) { - myElement.navigate(requestFocus); - } - - public boolean canNavigate() { - return myElement.canNavigate(); - } - - public boolean canNavigateToSource() { - return myElement.canNavigateToSource(); - } -} - diff --git a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewFactory.java b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewFactory.java index 9a7ae3db9689..04e52a927589 100644 --- a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewFactory.java +++ b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewFactory.java @@ -1,16 +1,34 @@ package org.jetbrains.yaml.structureView; -import com.intellij.ide.structureView.StructureViewBuilder; +import com.intellij.icons.AllIcons; +import com.intellij.ide.structureView.*; +import com.intellij.ide.util.treeView.smartTree.NodeProvider; +import com.intellij.ide.util.treeView.smartTree.Sorter; import com.intellij.lang.PsiStructureViewFactory; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.util.Ref; import com.intellij.psi.PsiFile; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.yaml.psi.YAMLFile; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.psi.*; + +import javax.swing.*; +import java.util.Collection; +import java.util.Collections; /** * @author oleg */ public class YAMLStructureViewFactory implements PsiStructureViewFactory { + static final Icon ALIAS_ICON = AllIcons.Nodes.ExpandNode; + + @Nullable public StructureViewBuilder getStructureViewBuilder(@NotNull final PsiFile psiFile) { + if (!(psiFile instanceof YAMLFile)) { + return null; + } + for (YAMLCustomStructureViewFactory extension : YAMLCustomStructureViewFactory.EP_NAME.getExtensions()) { final StructureViewBuilder builder = extension.getStructureViewBuilder((YAMLFile)psiFile); if (builder != null) { @@ -18,6 +36,44 @@ public class YAMLStructureViewFactory implements PsiStructureViewFactory { } } - return new YAMLStructureViewBuilder((YAMLFile) psiFile); + return new TreeBasedStructureViewBuilder() { + @Override + @NotNull + public StructureViewModel createStructureViewModel(@Nullable Editor editor) { + return new StructureViewModelBase(psiFile, editor, new YAMLStructureViewFile((YAMLFile)psiFile)){ + @NotNull + @Override + public Collection getNodeProviders() { + return Collections.singleton(new YAMLAliasResolveNodeProvider()); + } + } + .withSorters(Sorter.ALPHA_SORTER) + .withSuitableClasses(YAMLFile.class, YAMLDocument.class, YAMLKeyValue.class); + } + }; + } + + @NotNull + static String getAliasPresentableText(@NotNull YAMLAlias alias) { + return "*" + alias.getAliasName(); + } + + @NotNull + static Collection createChildrenViewTreeElements(@Nullable YAMLPsiElement element, String path) { + if (element == null) { + return Collections.emptyList(); + } + Ref> result = Ref.create(Collections.emptyList()); + element.accept(new YamlPsiElementVisitor() { + @Override + public void visitSequence(@NotNull YAMLSequence sequence) { + result.set(ContainerUtil.map(sequence.getItems(), i -> new YAMLStructureViewSequenceItem(i, path))); + } + @Override + public void visitMapping(@NotNull YAMLMapping mapping) { + result.set(ContainerUtil.map(mapping.getKeyValues(), kv -> new YAMLStructureViewKeyValue(kv, path))); + } + }); + return result.get(); } } diff --git a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewFile.java b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewFile.java new file mode 100644 index 000000000000..022815767719 --- /dev/null +++ b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewFile.java @@ -0,0 +1,49 @@ +// Copyright 2000-2018 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 org.jetbrains.yaml.structureView; + +import com.intellij.ide.structureView.StructureViewTreeElement; +import com.intellij.ide.structureView.impl.common.PsiTreeElementBase; +import com.intellij.navigation.ItemPresentation; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.psi.YAMLFile; + +import javax.swing.*; +import java.util.Collection; +import java.util.Objects; + +class YAMLStructureViewFile extends PsiTreeElementBase { + YAMLStructureViewFile(@NotNull YAMLFile psiElement) { + super(psiElement); + } + + @NotNull + @Override + public Collection getChildrenBase() { + YAMLFile file = Objects.requireNonNull(getElement()); + return ContainerUtil.map(file.getDocuments(), YAMLStructureViewDocument::new); + } + + @Nullable + @Override + public String getPresentableText() { + return getFilePresentation().getPresentableText(); + } + + @Nullable + public String getLocationString() { + return getFilePresentation().getLocationString(); + } + + @Nullable + public Icon getIcon(boolean unused) { + return getFilePresentation().getIcon(unused); + } + + @NotNull + private ItemPresentation getFilePresentation() { + YAMLFile file = Objects.requireNonNull(getElement()); + return Objects.requireNonNull(file.getPresentation()); + } +} diff --git a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewKeyValue.java b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewKeyValue.java new file mode 100644 index 000000000000..10b94aa8fec1 --- /dev/null +++ b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewKeyValue.java @@ -0,0 +1,71 @@ +// Copyright 2000-2018 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 org.jetbrains.yaml.structureView; + +import com.intellij.ide.structureView.StructureViewTreeElement; +import com.intellij.util.PlatformIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.psi.YAMLAlias; +import org.jetbrains.yaml.psi.YAMLKeyValue; +import org.jetbrains.yaml.psi.YAMLScalar; +import org.jetbrains.yaml.psi.YAMLValue; + +import javax.swing.*; +import java.util.Collection; +import java.util.Objects; + +import static org.jetbrains.yaml.structureView.YAMLStructureViewFactory.ALIAS_ICON; + +class YAMLStructureViewKeyValue extends DuplicatedPsiTreeElementBase { + YAMLStructureViewKeyValue(@NotNull YAMLKeyValue kv, String path) { + super(kv, path + '.' + kv.getKeyText()); + } + + @NotNull + @Override + public Collection getChildrenBase() { + return YAMLStructureViewFactory.createChildrenViewTreeElements(getVal(), getDetails()); + } + + @NotNull + public String getPresentableText() { + return getKeyValue().getKeyText(); + } + + @Nullable + public String getLocationString() { + if (getVal() instanceof YAMLScalar) { + return getKeyValue().getValueText(); + } + else if (getVal() instanceof YAMLAlias) { + return YAMLStructureViewFactory.getAliasPresentableText((YAMLAlias)getVal()); + } + else { + return null; + } + } + + @Nullable + public Icon getIcon(boolean open) { + if (getVal() instanceof YAMLScalar) { + return getKeyValue().getIcon(0); + } + else if (getVal() instanceof YAMLAlias) { + return ALIAS_ICON; + } + else { + return PlatformIcons.XML_TAG_ICON; + } + } + + @NotNull + private YAMLKeyValue getKeyValue() { + return Objects.requireNonNull(getElement()); + } + + // Base class already has getValue() method with different semantic + @Nullable + private YAMLValue getVal() { + return getKeyValue().getValue(); + } +} diff --git a/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewSequenceItem.java b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewSequenceItem.java new file mode 100644 index 000000000000..6fe920b13a1a --- /dev/null +++ b/plugins/yaml/src/org/jetbrains/yaml/structureView/YAMLStructureViewSequenceItem.java @@ -0,0 +1,77 @@ +// Copyright 2000-2018 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 org.jetbrains.yaml.structureView; + +import com.intellij.ide.structureView.StructureViewTreeElement; +import com.intellij.lang.ASTNode; +import com.intellij.util.PlatformIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.yaml.YAMLBundle; +import org.jetbrains.yaml.psi.YAMLAlias; +import org.jetbrains.yaml.psi.YAMLScalar; +import org.jetbrains.yaml.psi.YAMLSequenceItem; +import org.jetbrains.yaml.psi.YAMLValue; + +import javax.swing.*; +import java.util.Collection; + +import static org.jetbrains.yaml.structureView.YAMLStructureViewFactory.ALIAS_ICON; + +class YAMLStructureViewSequenceItem extends DuplicatedPsiTreeElementBase { + YAMLStructureViewSequenceItem(@NotNull YAMLSequenceItem item, String path) { + super(item, path + '-' + getItemNumber(item)); + } + + @NotNull + @Override + public Collection getChildrenBase() { + return YAMLStructureViewFactory.createChildrenViewTreeElements(getItemValue(), getDetails()); + } + + @NotNull + @Override + public String getPresentableText() { + if (getItemValue() instanceof YAMLScalar) { + return ((YAMLScalar)getItemValue()).getTextValue(); + } + else if (getItemValue() instanceof YAMLAlias) { + return YAMLStructureViewFactory.getAliasPresentableText((YAMLAlias)getItemValue()); + } + else { + return YAMLBundle.message("YAMLStructureViewSequenceItem.element.name"); + } + } + + @Nullable + @Override + public String getLocationString() { + return null; + } + + @NotNull + @Override + public Icon getIcon(boolean unused) { + if (getItemValue() instanceof YAMLScalar) { + return PlatformIcons.PROPERTY_ICON; + } + else if (getItemValue() instanceof YAMLAlias) { + return ALIAS_ICON; + } + else { + return PlatformIcons.XML_TAG_ICON; + } + } + + @Nullable + private YAMLValue getItemValue() { + return getElement().getValue(); + } + + private static int getItemNumber(@NotNull YAMLSequenceItem item) { + int num = 0; + for (ASTNode node = item.getNode().getTreePrev(); node != null; node = node.getTreePrev()) { + num++; + } + return num; + } +} diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/structure/YAMLStructureTest.java b/plugins/yaml/testSrc/org/jetbrains/yaml/structure/YAMLStructureTest.java new file mode 100644 index 000000000000..7c85c2404d42 --- /dev/null +++ b/plugins/yaml/testSrc/org/jetbrains/yaml/structure/YAMLStructureTest.java @@ -0,0 +1,38 @@ +// Copyright 2000-2018 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 org.jetbrains.yaml.structure; + +import com.intellij.openapi.application.ex.PathManagerEx; +import com.intellij.testFramework.PlatformTestUtil; +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.yaml.structureView.YAMLAliasResolveNodeProvider; + +public class YAMLStructureTest extends LightPlatformCodeInsightFixtureTestCase { + @Override + protected String getTestDataPath() { + return PathManagerEx.getCommunityHomePath() + "/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/"; + } + + + public void testAnchorsAndAliases() { + doTest(); + } + + private void doTest() { + String testName = getTestName(true); + myFixture.configureByFile(testName + ".yml"); + doStructureTest(testName, false); + doStructureTest(testName, true); + } + + private void doStructureTest(String testName, boolean resolveAliases) { + myFixture.testStructureView(svc -> { + svc.setActionActive(YAMLAliasResolveNodeProvider.ID, resolveAliases); + TreeUtil.expandAll(svc.getTree()); + PlatformTestUtil.waitForPromise(svc.select(svc.getTreeModel().getCurrentEditorElement(), false)); + String suffix = resolveAliases ? "resolved" : "unresolved"; + assertSameLinesWithFile(getTestDataPath() + testName + "." + suffix + ".txt", + PlatformTestUtil.print(svc.getTree(), false)); + }); + } +} diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.resolved.txt b/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.resolved.txt new file mode 100644 index 000000000000..1dec56302a6d --- /dev/null +++ b/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.resolved.txt @@ -0,0 +1,14 @@ +-anchorsAndAliases.yml + -YAML document + -First occurrence + -list + item1 + item2 + -Second occurrence + -list + item1 + item2 + -Override anchor + key + -Reuse anchor + key \ No newline at end of file diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.unresolved.txt b/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.unresolved.txt new file mode 100644 index 000000000000..6176e0923bb5 --- /dev/null +++ b/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.unresolved.txt @@ -0,0 +1,10 @@ +-anchorsAndAliases.yml + -YAML document + -First occurrence + -list + item1 + item2 + Second occurrence + -Override anchor + key + Reuse anchor \ No newline at end of file diff --git a/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.yml b/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.yml new file mode 100644 index 000000000000..0218bf533e0a --- /dev/null +++ b/plugins/yaml/testSrc/org/jetbrains/yaml/structure/data/anchorsAndAliases.yml @@ -0,0 +1,8 @@ +First occurrence: &xxx + list: + - item1 + - item2 +Second occurrence: *xxx +Override anchor: &xxx + key: val +Reuse anchor: *xxx