/* * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan * * 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.goide.psi; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.StubBasedPsiElement; import com.intellij.psi.impl.source.tree.TreeUtil; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.SmartList; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; public class GoPsiTreeUtil extends PsiTreeUtil { @Nullable public static <T extends PsiElement> T getStubChildOfType(@Nullable PsiElement element, @NotNull Class<T> aClass) { if (element == null) return null; StubElement<?> stub = element instanceof StubBasedPsiElement ? ((StubBasedPsiElement)element).getStub() : null; if (stub == null) { return getChildOfType(element, aClass); } for (StubElement childStub : stub.getChildrenStubs()) { PsiElement child = childStub.getPsi(); if (aClass.isInstance(child)) { //noinspection unchecked return (T)child; } } return null; } @NotNull public static <T extends PsiElement> List<T> getStubChildrenOfTypeAsList(@Nullable PsiElement element, @NotNull Class<T> aClass) { if (element == null) return Collections.emptyList(); StubElement<?> stub = element instanceof StubBasedPsiElement ? ((StubBasedPsiElement)element).getStub() : null; if (stub == null) { return getChildrenOfTypeAsList(element, aClass); } List<T> result = new SmartList<T>(); for (StubElement childStub : stub.getChildrenStubs()) { PsiElement child = childStub.getPsi(); if (aClass.isInstance(child)) { //noinspection unchecked result.add((T)child); } } return result; } @Nullable private static Couple<PsiElement> getElementRange(@NotNull GoFile file, int startOffset, int endOffset) { PsiElement startElement = findNotWhiteSpaceElementAtOffset(file, startOffset, true); PsiElement endElement = findNotWhiteSpaceElementAtOffset(file, endOffset - 1, false); if (startElement == null || endElement == null) return null; ASTNode startNode = TreeUtil.findFirstLeaf(startElement.getNode()); ASTNode endNode = TreeUtil.findLastLeaf(endElement.getNode()); if (startNode == null || endNode == null) return null; startElement = startNode.getPsi(); endElement = endNode.getPsi(); if (startElement == null || endElement == null) return null; return Couple.of(startElement, endElement); } /** * Return element range which contains TextRange(start, end) of top level elements * common parent of elements is straight parent for each element */ @Nullable private static Couple<PsiElement> getTopmostElementRange(@NotNull Couple<PsiElement> elementRange) { if (elementRange.first == null || elementRange.second == null) return null; PsiElement commonParent = PsiTreeUtil.findCommonParent(elementRange.first, elementRange.second); if (commonParent == null) return null; if (commonParent.isEquivalentTo(elementRange.first) || commonParent.isEquivalentTo(elementRange.second)) { commonParent = commonParent.getParent(); } PsiElement startElement = PsiTreeUtil.findPrevParent(commonParent, elementRange.first); PsiElement endElement = PsiTreeUtil.findPrevParent(commonParent, elementRange.second); if (!startElement.getParent().isEquivalentTo(endElement.getParent())) return null; int start = elementRange.first.getTextRange().getStartOffset(); int end = elementRange.second.getTextRange().getEndOffset(); TextRange range = commonParent.getTextRange(); PsiElement[] children = commonParent.getChildren(); if (range.equalsToRange(start, end) || range.getStartOffset() == start && (children.length == 0 || children[0].getTextRange().getStartOffset() > start) || range.getEndOffset() == end && (children.length == 0 || children[children.length - 1].getTextRange().getEndOffset() < end)) { startElement = commonParent; endElement = commonParent; } if (startElement.isEquivalentTo(endElement)) { while (startElement.getTextRange().equals(startElement.getParent().getTextRange())) { startElement = startElement.getParent(); } return Couple.of(startElement, startElement); } return Couple.of(startElement, endElement); } @NotNull public static PsiElement[] getTopLevelElementsInRange(@NotNull GoFile file, int startOffset, int endOffset, @NotNull Class<? extends PsiElement> clazz) { Couple<PsiElement> elementRange = getElementRange(file, startOffset, endOffset); if (elementRange == null) return PsiElement.EMPTY_ARRAY; Couple<PsiElement> topmostElementRange = getTopmostElementRange(elementRange); if (topmostElementRange == null) return PsiElement.EMPTY_ARRAY; if (!clazz.isInstance(topmostElementRange.first) || !clazz.isInstance(topmostElementRange.second)) { return PsiElement.EMPTY_ARRAY; } List<PsiElement> result = ContainerUtil.newSmartList(); PsiElement start = topmostElementRange.first; while (start != null && !start.isEquivalentTo(topmostElementRange.second)) { if (clazz.isInstance(start)) result.add(start); start = start.getNextSibling(); } result.add(topmostElementRange.second); return PsiUtilCore.toPsiElementArray(result); } @Nullable private static PsiElement findNotWhiteSpaceElementAtOffset(@NotNull GoFile file, int offset, boolean forward) { PsiElement element = file.findElementAt(offset); while (element instanceof PsiWhiteSpace) { element = file.findElementAt(forward ? element.getTextRange().getEndOffset() : element.getTextRange().getStartOffset() - 1); } return element; } }