| /* |
| * 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.psi.util; |
| |
| import com.intellij.lang.Language; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.scope.PsiScopeProcessor; |
| import com.intellij.psi.search.PsiElementProcessor; |
| import com.intellij.psi.stubs.StubBase; |
| import com.intellij.psi.stubs.StubElement; |
| import com.intellij.psi.templateLanguages.OuterLanguageElement; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.PairProcessor; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.Contract; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public class PsiTreeUtil { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.psi.util.PsiTreeUtil"); |
| |
| private static final Key<Integer> INDEX = Key.create("PsiTreeUtil.copyElements.INDEX"); |
| private static final Key<Object> MARKER = Key.create("PsiTreeUtil.copyElements.MARKER"); |
| |
| /** |
| * Checks whether one element in the psi tree is under another. |
| * |
| * @param ancestor parent candidate. <code>false</code> will be returned if ancestor is null. |
| * @param element child candidate |
| * @param strict whether return true if ancestor and parent are the same. |
| * @return true if element has ancestor as its parent somewhere in the hierarchy and false otherwise. |
| */ |
| @Contract("null, _, _ -> false") |
| public static boolean isAncestor(@Nullable PsiElement ancestor, @NotNull PsiElement element, boolean strict) { |
| if (ancestor == null) return false; |
| // fast path to avoid loading tree |
| if (ancestor instanceof StubBasedPsiElement && ((StubBasedPsiElement)ancestor).getStub() != null || |
| element instanceof StubBasedPsiElement && ((StubBasedPsiElement)element).getStub() != null) { |
| if (ancestor.getContainingFile() != element.getContainingFile()) return false; |
| } |
| |
| boolean stopAtFileLevel = !(ancestor instanceof PsiFile || ancestor instanceof PsiDirectory); |
| |
| PsiElement parent = strict ? element.getParent() : element; |
| while (true) { |
| if (parent == null) return false; |
| if (parent.equals(ancestor)) return true; |
| if (stopAtFileLevel && parent instanceof PsiFile) return false; |
| parent = parent.getParent(); |
| } |
| } |
| |
| /** |
| * Checks whether one element in the psi tree is under another in {@link com.intellij.psi.PsiElement#getContext()} hierarchy. |
| * |
| * @param ancestor parent candidate. <code>false</code> will be returned if ancestor is null. |
| * @param element child candidate |
| * @param strict whether return true if ancestor and parent are the same. |
| * @return true if element has ancestor as its parent somewhere in the hierarchy and false otherwise. |
| */ |
| @Contract("null, _, _ -> false") |
| public static boolean isContextAncestor(@Nullable PsiElement ancestor, @NotNull PsiElement element, boolean strict) { |
| if (ancestor == null) return false; |
| boolean stopAtFileLevel = !(ancestor instanceof PsiFile || ancestor instanceof PsiDirectory); |
| PsiElement parent = strict ? element.getContext() : element; |
| while (true) { |
| if (parent == null) return false; |
| if (parent.equals(ancestor)) return true; |
| if (stopAtFileLevel && parent instanceof PsiFile) { |
| final PsiElement context = parent.getContext(); |
| if (context == null) return false; |
| } |
| parent = parent.getContext(); |
| } |
| } |
| |
| @Nullable |
| public static PsiElement findCommonParent(@NotNull List<? extends PsiElement> elements) { |
| if (elements.isEmpty()) return null; |
| PsiElement toReturn = null; |
| for (PsiElement element : elements) { |
| if (element == null) continue; |
| toReturn = toReturn == null ? element : findCommonParent(toReturn, element); |
| if (toReturn == null) return null; |
| } |
| |
| return toReturn; |
| } |
| |
| @Nullable |
| public static PsiElement findCommonParent(@NotNull PsiElement... elements) { |
| if (elements.length == 0) return null; |
| PsiElement toReturn = null; |
| for (PsiElement element : elements) { |
| if (element == null) continue; |
| toReturn = toReturn == null ? element : findCommonParent(toReturn, element); |
| if (toReturn == null) return null; |
| } |
| |
| return toReturn; |
| } |
| |
| @Nullable |
| public static PsiElement findCommonParent(@NotNull PsiElement element1, @NotNull PsiElement element2) { |
| // optimization |
| if (element1 == element2) return element1; |
| final PsiFile containingFile = element1.getContainingFile(); |
| final PsiElement topLevel = containingFile == element2.getContainingFile() ? containingFile : null; |
| |
| ArrayList<PsiElement> parents1 = getParents(element1, topLevel); |
| ArrayList<PsiElement> parents2 = getParents(element2, topLevel); |
| int size = Math.min(parents1.size(), parents2.size()); |
| PsiElement parent = topLevel; |
| for (int i = 1; i <= size; i++) { |
| PsiElement parent1 = parents1.get(parents1.size() - i); |
| PsiElement parent2 = parents2.get(parents2.size() - i); |
| if (!parent1.equals(parent2)) break; |
| parent = parent1; |
| } |
| return parent; |
| } |
| |
| @NotNull |
| private static ArrayList<PsiElement> getParents(@NotNull PsiElement element, @Nullable PsiElement topLevel) { |
| ArrayList<PsiElement> parents = new ArrayList<PsiElement>(); |
| PsiElement parent = element; |
| while (parent != topLevel && parent != null) { |
| parents.add(parent); |
| parent = parent.getParent(); |
| } |
| return parents; |
| } |
| |
| @Nullable |
| public static PsiElement findCommonContext(@NotNull PsiElement... elements) { |
| return findCommonContext(Arrays.asList(elements)); |
| } |
| |
| @Nullable |
| public static PsiElement findCommonContext(@NotNull Collection<? extends PsiElement> elements) { |
| if (elements.isEmpty()) return null; |
| PsiElement toReturn = null; |
| for (PsiElement element : elements) { |
| if (element == null) continue; |
| toReturn = toReturn == null ? element : findCommonContext(toReturn, element); |
| if (toReturn == null) return null; |
| } |
| return toReturn; |
| } |
| |
| @Nullable |
| public static PsiElement findCommonContext(@NotNull PsiElement element1, @NotNull PsiElement element2) { |
| // optimization |
| if (element1 == element2) return element1; |
| final PsiFile containingFile = element1.getContainingFile(); |
| final PsiElement topLevel = containingFile == element2.getContainingFile() ? containingFile : null; |
| |
| ArrayList<PsiElement> parents1 = getContexts(element1, topLevel); |
| ArrayList<PsiElement> parents2 = getContexts(element2, topLevel); |
| int size = Math.min(parents1.size(), parents2.size()); |
| PsiElement parent = topLevel; |
| for (int i = 1; i <= size; i++) { |
| PsiElement parent1 = parents1.get(parents1.size() - i); |
| PsiElement parent2 = parents2.get(parents2.size() - i); |
| if (!parent1.equals(parent2)) break; |
| parent = parent1; |
| } |
| return parent; |
| } |
| |
| @NotNull |
| private static ArrayList<PsiElement> getContexts(@NotNull PsiElement element, @Nullable PsiElement topLevel) { |
| ArrayList<PsiElement> parents = new ArrayList<PsiElement>(); |
| PsiElement parent = element; |
| while (parent != topLevel && parent != null) { |
| parents.add(parent); |
| parent = parent.getContext(); |
| } |
| return parents; |
| } |
| |
| @Nullable |
| public static <T extends PsiElement> T findChildOfType(@Nullable final PsiElement element, @NotNull final Class<T> aClass) { |
| //noinspection unchecked |
| return findChildOfAnyType(element, true, aClass); |
| } |
| |
| @Nullable |
| public static <T extends PsiElement> T findChildOfType(@Nullable final PsiElement element, |
| @NotNull final Class<T> aClass, |
| final boolean strict) { |
| //noinspection unchecked |
| return findChildOfAnyType(element, strict, aClass); |
| } |
| |
| /** |
| * Recursive (depth first) strict({@code element} isn't included) search for first element of any of given {@code classes}. |
| * |
| * @param element a PSI element to start search from. |
| * @param classes element types to search for. |
| * @param <T> type to cast found element to. |
| * @return first found element, or null if nothing found. |
| */ |
| @Nullable |
| @Contract("null, _ -> null") |
| public static <T extends PsiElement> T findChildOfAnyType(@Nullable final PsiElement element, @NotNull final Class<? extends T>... classes) { |
| return findChildOfAnyType(element, true, classes); |
| } |
| |
| /** |
| * Recursive (depth first) search for first element of any of given {@code classes}. |
| * |
| * @param element a PSI element to start search from. |
| * @param strict if false the {@code element} is also included in the search. |
| * @param classes element types to search for. |
| * @param <T> type to cast found element to. |
| * @return first found element, or null if nothing found. |
| */ |
| @Nullable |
| @Contract("null, _, _ -> null") |
| public static <T extends PsiElement> T findChildOfAnyType(@Nullable final PsiElement element, |
| final boolean strict, |
| @NotNull final Class<? extends T>... classes) { |
| PsiElementProcessor.FindElement<PsiElement> processor = new PsiElementProcessor.FindElement<PsiElement>() { |
| @Override |
| public boolean execute(@NotNull PsiElement each) { |
| if (strict && each == element) return true; |
| if (instanceOf(each, classes)) { |
| return setFound(each); |
| } |
| return true; |
| } |
| }; |
| |
| processElements(element, processor); |
| //noinspection unchecked |
| return (T)processor.getFoundElement(); |
| } |
| |
| @NotNull |
| public static <T extends PsiElement> Collection<T> findChildrenOfType(@Nullable PsiElement element, @NotNull Class<? extends T> aClass) { |
| return findChildrenOfAnyType(element, aClass); |
| } |
| |
| @NotNull |
| public static <T extends PsiElement> Collection<T> findChildrenOfAnyType(@Nullable final PsiElement element, |
| @NotNull final Class<? extends T>... classes) { |
| if (element == null) { |
| return ContainerUtil.emptyList(); |
| } |
| |
| PsiElementProcessor.CollectElements<T> processor = new PsiElementProcessor.CollectElements<T>() { |
| @Override |
| public boolean execute(@NotNull T each) { |
| if (each == element) return true; |
| if (instanceOf(each, classes)) { |
| return super.execute(each); |
| } |
| return true; |
| } |
| }; |
| processElements(element, processor); |
| return processor.getCollection(); |
| } |
| |
| /** |
| * Non-recursive search for element of type T amongst given {@code element} children. |
| * |
| * @param element a PSI element to start search from. |
| * @param aClass element type to search for. |
| * @param <T> element type to search for. |
| * @return first found element, or null if nothing found. |
| */ |
| @Nullable |
| public static <T extends PsiElement> T getChildOfType(@Nullable PsiElement element, @NotNull Class<T> aClass) { |
| if (element == null) return null; |
| for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { |
| if (aClass.isInstance(child)) { |
| //noinspection unchecked |
| return (T)child; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static PsiElement findFirstParent(@Nullable PsiElement element, Condition<PsiElement> condition) { |
| return findFirstParent(element, false, condition); |
| } |
| |
| @Nullable |
| public static PsiElement findFirstParent(@Nullable PsiElement element, boolean strict, Condition<PsiElement> condition) { |
| if (strict && element != null) { |
| element = element.getParent(); |
| } |
| |
| while (element != null) { |
| if (condition.value(element)) { |
| return element; |
| } |
| element = element.getParent(); |
| } |
| return null; |
| } |
| |
| @NotNull |
| public static <T extends PsiElement> T getRequiredChildOfType(@NotNull PsiElement element, @NotNull Class<T> aClass) { |
| final T child = getChildOfType(element, aClass); |
| assert child != null : "Missing required child of type " + aClass.getName(); |
| return child; |
| } |
| |
| @Nullable |
| public static <T extends PsiElement> T[] getChildrenOfType(@Nullable PsiElement element, @NotNull Class<T> aClass) { |
| if (element == null) return null; |
| |
| List<T> result = null; |
| for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { |
| if (aClass.isInstance(child)) { |
| if (result == null) result = new SmartList<T>(); |
| //noinspection unchecked |
| result.add((T)child); |
| } |
| } |
| return result == null ? null : ArrayUtil.toObjectArray(result, aClass); |
| } |
| |
| @NotNull |
| public static <T extends PsiElement> List<T> getChildrenOfTypeAsList(@Nullable PsiElement element, @NotNull Class<T> aClass) { |
| if (element == null) return Collections.emptyList(); |
| |
| List<T> result = new SmartList<T>(); |
| for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { |
| if (aClass.isInstance(child)) { |
| //noinspection unchecked |
| result.add((T)child); |
| } |
| } |
| return result; |
| } |
| |
| public static boolean instanceOf(final Object object, final Class<?>... classes) { |
| if (classes != null) { |
| for (final Class<?> c : classes) { |
| if (c.isInstance(object)) return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns a direct child of the specified element which has any of the specified classes. |
| * |
| * @param element the element to get the child for. |
| * @param classes the array of classes. |
| * @return the element, or null if none was found. |
| * @since 5.1 |
| */ |
| @Nullable |
| @Contract("null, _ -> null") |
| public static <T extends PsiElement> T getChildOfAnyType(@Nullable PsiElement element, @NotNull Class<? extends T>... classes) { |
| if (element == null) return null; |
| for (PsiElement child = element.getFirstChild(); child != null; child = child.getNextSibling()) { |
| for (Class<? extends T> aClass : classes) { |
| if (aClass.isInstance(child)) { |
| //noinspection unchecked |
| return (T)child; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static <T extends PsiElement> T getNextSiblingOfType(@Nullable PsiElement sibling, @NotNull Class<T> aClass) { |
| if (sibling == null) return null; |
| for (PsiElement child = sibling.getNextSibling(); child != null; child = child.getNextSibling()) { |
| if (aClass.isInstance(child)) { |
| //noinspection unchecked |
| return (T)child; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static <T extends PsiElement> T getPrevSiblingOfType(@Nullable PsiElement sibling, @NotNull Class<T> aClass) { |
| if (sibling == null) return null; |
| for (PsiElement child = sibling.getPrevSibling(); child != null; child = child.getPrevSibling()) { |
| if (aClass.isInstance(child)) { |
| //noinspection unchecked |
| return (T)child; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static <T extends PsiElement> T getTopmostParentOfType(@Nullable PsiElement element, @NotNull Class<T> aClass) { |
| T answer = getParentOfType(element, aClass); |
| |
| do { |
| T next = getParentOfType(answer, aClass); |
| if (next == null) break; |
| answer = next; |
| } |
| while (true); |
| |
| return answer; |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static <T extends PsiElement> T getParentOfType(@Nullable PsiElement element, @NotNull Class<T> aClass) { |
| return getParentOfType(element, aClass, true); |
| } |
| |
| @Nullable |
| @Contract("null -> null") |
| public static PsiElement getStubOrPsiParent(@Nullable PsiElement element) { |
| if (element instanceof StubBasedPsiElement) { |
| StubBase stub = (StubBase)((StubBasedPsiElement)element).getStub(); |
| if (stub != null) { |
| //noinspection unchecked |
| final StubElement parentStub = stub.getParentStub(); |
| return parentStub != null ? parentStub.getPsi() : null; |
| } |
| } |
| return element != null ? element.getParent() : null; |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static <E extends PsiElement> E getStubOrPsiParentOfType(@Nullable PsiElement element, @NotNull Class<E> parentClass) { |
| if (element instanceof StubBasedPsiElement) { |
| StubBase stub = (StubBase)((StubBasedPsiElement)element).getStub(); |
| if (stub != null) { |
| //noinspection unchecked |
| return (E)stub.getParentStubOfType(parentClass); |
| } |
| } |
| return getParentOfType(element, parentClass); |
| } |
| |
| @Nullable |
| @Contract("null, _, _, _ -> null") |
| public static <T extends PsiElement> T getContextOfType(@Nullable PsiElement element, |
| @NotNull Class<T> aClass, |
| boolean strict, |
| Class<? extends PsiElement>... stopAt) { |
| if (element == null) return null; |
| if (strict) { |
| element = element.getContext(); |
| } |
| |
| while (element != null && !aClass.isInstance(element)) { |
| if (instanceOf(element, stopAt)) return null; |
| element = element.getContext(); |
| } |
| |
| //noinspection unchecked |
| return (T)element; |
| } |
| |
| @Nullable |
| @Contract("null, _, _ -> null") |
| public static <T extends PsiElement> T getContextOfType(@Nullable PsiElement element, |
| @NotNull Class<? extends T> aClass, |
| boolean strict) { |
| return getContextOfType(element, strict, aClass); |
| } |
| |
| @Nullable |
| public static <T extends PsiElement> T getContextOfType(@Nullable PsiElement element, @NotNull Class<? extends T>... classes) { |
| return getContextOfType(element, true, classes); |
| } |
| |
| @Nullable |
| @Contract("null, _, _ -> null") |
| public static <T extends PsiElement> T getContextOfType(@Nullable PsiElement element, |
| boolean strict, |
| @NotNull Class<? extends T>... classes) { |
| if (element == null) return null; |
| if (strict) { |
| element = element.getContext(); |
| } |
| |
| while (element != null && !instanceOf(element, classes)) { |
| element = element.getContext(); |
| } |
| |
| //noinspection unchecked |
| return (T)element; |
| } |
| |
| @Nullable |
| @Contract("null, _, _ -> null") |
| public static <T extends PsiElement> T getParentOfType(@Nullable PsiElement element, @NotNull Class<T> aClass, boolean strict) { |
| return getParentOfType(element, aClass, strict, -1); |
| } |
| |
| @Contract("null, _, _, _ -> null") |
| public static <T extends PsiElement> T getParentOfType(@Nullable PsiElement element, @NotNull Class<T> aClass, boolean strict, int minStartOffset) { |
| if (element == null) { |
| return null; |
| } |
| |
| if (strict) { |
| element = element.getParent(); |
| } |
| |
| while (element != null && (minStartOffset == -1 || element.getNode().getStartOffset() >= minStartOffset)) { |
| if (aClass.isInstance(element)) { |
| //noinspection unchecked |
| return (T)element; |
| } |
| if (element instanceof PsiFile) { |
| return null; |
| } |
| element = element.getParent(); |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| @Contract("null, _, _, _ -> null") |
| public static <T extends PsiElement> T getParentOfType(@Nullable PsiElement element, |
| @NotNull Class<T> aClass, |
| boolean strict, |
| @NotNull Class<? extends PsiElement>... stopAt) { |
| if (element == null) return null; |
| if (strict) { |
| element = element.getParent(); |
| } |
| |
| while (element != null && !aClass.isInstance(element)) { |
| if (instanceOf(element, stopAt)) return null; |
| if (element instanceof PsiFile) return null; |
| element = element.getParent(); |
| } |
| |
| //noinspection unchecked |
| return (T)element; |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static PsiElement skipSiblingsForward(@Nullable PsiElement element, @NotNull Class... elementClasses) { |
| if (element == null) return null; |
| NextSibling: |
| for (PsiElement e = element.getNextSibling(); e != null; e = e.getNextSibling()) { |
| if (instanceOf(e, elementClasses)) continue NextSibling; |
| return e; |
| } |
| return null; |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static PsiElement skipSiblingsBackward(@Nullable PsiElement element, @NotNull Class... elementClasses) { |
| if (element == null) return null; |
| NextSibling: |
| for (PsiElement e = element.getPrevSibling(); e != null; e = e.getPrevSibling()) { |
| if (instanceOf(e, elementClasses)) continue NextSibling; |
| return e; |
| } |
| return null; |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static PsiElement skipParentsOfType(@Nullable PsiElement element, @NotNull Class... parentClasses) { |
| if (element == null) return null; |
| NextSibling: |
| for (PsiElement e = element.getParent(); e != null; e = e.getParent()) { |
| if (instanceOf(e, parentClasses)) continue NextSibling; |
| return e; |
| } |
| return null; |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static <T extends PsiElement> T getParentOfType(@Nullable final PsiElement element, |
| @NotNull final Class<? extends T>... classes) { |
| if (element == null) return null; |
| PsiElement parent = element.getParent(); |
| if (parent == null) return null; |
| return getNonStrictParentOfType(parent, classes); |
| } |
| |
| @Nullable |
| @Contract("null, _ -> null") |
| public static <T extends PsiElement> T getNonStrictParentOfType(@Nullable final PsiElement element, |
| @NotNull final Class<? extends T>... classes) { |
| PsiElement run = element; |
| while (run != null) { |
| if (instanceOf(run, classes)) { |
| //noinspection unchecked |
| return (T)run; |
| } |
| if (run instanceof PsiFile) break; |
| run = run.getParent(); |
| } |
| |
| return null; |
| } |
| |
| @NotNull |
| public static PsiElement[] collectElements(@Nullable PsiElement element, @NotNull PsiElementFilter filter) { |
| PsiElementProcessor.CollectFilteredElements<PsiElement> processor = new PsiElementProcessor.CollectFilteredElements<PsiElement>(filter); |
| processElements(element, processor); |
| return processor.toArray(); |
| } |
| |
| @NotNull |
| public static <T extends PsiElement> Collection<T> collectElementsOfType(@Nullable final PsiElement element, |
| @NotNull final Class<T>... classes) { |
| PsiElementProcessor.CollectFilteredElements<T> processor = new PsiElementProcessor.CollectFilteredElements<T>(new PsiElementFilter() { |
| |
| @Override |
| public boolean isAccepted(PsiElement element) { |
| for (Class<T> clazz : classes) { |
| if (clazz.isInstance(element)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| }); |
| processElements(element, processor); |
| return processor.getCollection(); |
| } |
| |
| @Contract("null, _ -> true") |
| public static boolean processElements(@Nullable PsiElement element, @NotNull final PsiElementProcessor processor) { |
| if (element == null) return true; |
| if (element instanceof PsiCompiledElement || !element.isPhysical()) { // DummyHolders cannot be visited by walking visitors because children/parent relationship is broken there |
| //noinspection unchecked |
| if (!processor.execute(element)) return false; |
| for (PsiElement child : element.getChildren()) { |
| if (!processElements(child, processor)) return false; |
| } |
| return true; |
| } |
| final boolean[] result = {true}; |
| element.accept(new PsiRecursiveElementWalkingVisitor() { |
| @Override |
| public void visitElement(PsiElement element) { |
| //noinspection unchecked |
| if (processor.execute(element)) { |
| super.visitElement(element); |
| } |
| else { |
| stopWalking(); |
| result[0] = false; |
| } |
| } |
| }); |
| |
| return result[0]; |
| } |
| |
| public static boolean processElements(@NotNull PsiElementProcessor processor, @Nullable PsiElement... elements) { |
| if (elements == null || elements.length == 0) return true; |
| for (PsiElement element : elements) { |
| if (!processElements(element, processor)) return false; |
| } |
| return true; |
| } |
| |
| @NotNull |
| public static PsiElement[] copyElements(@NotNull PsiElement[] elements) { |
| ArrayList<PsiElement> roots = new ArrayList<PsiElement>(); |
| for (int i = 0; i < elements.length; i++) { |
| PsiElement rootCandidate = elements[i]; |
| boolean failed = false; |
| for (int j = 0; j < elements.length; j++) { |
| PsiElement element = elements[j]; |
| if (i != j && isAncestor(element, rootCandidate, true)) { |
| failed = true; |
| break; |
| } |
| } |
| if (!failed) { |
| roots.add(rootCandidate); |
| } |
| } |
| for (int i = 0; i < elements.length; i++) { |
| PsiElement element = elements[i]; |
| element.putCopyableUserData(INDEX, Integer.valueOf(i)); |
| } |
| PsiElement[] newRoots = new PsiElement[roots.size()]; |
| for (int i = 0; i < roots.size(); i++) { |
| PsiElement root = roots.get(i); |
| newRoots[i] = root.copy(); |
| } |
| |
| final PsiElement[] result = new PsiElement[elements.length]; |
| for (PsiElement newRoot : newRoots) { |
| decodeIndices(newRoot, result); |
| } |
| return result; |
| } |
| |
| private static void decodeIndices(@NotNull PsiElement element, @NotNull PsiElement[] result) { |
| final Integer data = element.getCopyableUserData(INDEX); |
| if (data != null) { |
| element.putCopyableUserData(INDEX, null); |
| int index = data.intValue(); |
| result[index] = element; |
| } |
| PsiElement child = element.getFirstChild(); |
| while (child != null) { |
| decodeIndices(child, result); |
| child = child.getNextSibling(); |
| } |
| } |
| |
| public static void mark(@NotNull PsiElement element, @NotNull Object marker) { |
| element.putCopyableUserData(MARKER, marker); |
| } |
| |
| @Nullable |
| public static PsiElement releaseMark(@NotNull PsiElement root, @NotNull Object marker) { |
| if (marker.equals(root.getCopyableUserData(MARKER))) { |
| root.putCopyableUserData(MARKER, null); |
| return root; |
| } |
| else { |
| PsiElement child = root.getFirstChild(); |
| while (child != null) { |
| final PsiElement result = releaseMark(child, marker); |
| if (result != null) return result; |
| child = child.getNextSibling(); |
| } |
| return null; |
| } |
| } |
| |
| @Nullable |
| public static <T extends PsiElement> T findElementOfClassAtOffset(@NotNull PsiFile file, |
| int offset, |
| @NotNull Class<T> clazz, |
| boolean strictStart) { |
| final List<PsiFile> psiRoots = file.getViewProvider().getAllFiles(); |
| T result = null; |
| for (PsiElement root : psiRoots) { |
| final PsiElement elementAt = root.findElementAt(offset); |
| if (elementAt != null) { |
| final T parent = getParentOfType(elementAt, clazz, strictStart); |
| if (parent != null) { |
| final TextRange range = parent.getTextRange(); |
| if (!strictStart || range.getStartOffset() == offset) { |
| if (result == null || result.getTextRange().getEndOffset() > range.getEndOffset()) { |
| result = parent; |
| } |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| @Nullable |
| public static <T extends PsiElement> T findElementOfClassAtOffsetWithStopSet(@NotNull PsiFile file, |
| int offset, |
| @NotNull Class<T> clazz, |
| boolean strictStart, |
| @NotNull Class<? extends PsiElement>... stopAt) { |
| final List<PsiFile> psiRoots = file.getViewProvider().getAllFiles(); |
| T result = null; |
| for (PsiElement root : psiRoots) { |
| final PsiElement elementAt = root.findElementAt(offset); |
| if (elementAt != null) { |
| final T parent = getParentOfType(elementAt, clazz, strictStart, stopAt); |
| if (parent != null) { |
| final TextRange range = parent.getTextRange(); |
| if (!strictStart || range.getStartOffset() == offset) { |
| if (result == null || result.getTextRange().getEndOffset() > range.getEndOffset()) { |
| result = parent; |
| } |
| } |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * @return maximal element of specified Class starting at startOffset exactly and ending not farther than endOffset |
| */ |
| @Nullable |
| public static <T extends PsiElement> T findElementOfClassAtRange(@NotNull PsiFile file, |
| int startOffset, |
| int endOffset, |
| @NotNull Class<T> clazz) { |
| final FileViewProvider viewProvider = file.getViewProvider(); |
| T result = null; |
| for (Language lang : viewProvider.getLanguages()) { |
| PsiElement elementAt = viewProvider.findElementAt(startOffset, lang); |
| T run = getParentOfType(elementAt, clazz, false); |
| T prev = run; |
| while (run != null && run.getTextRange().getStartOffset() == startOffset && |
| run.getTextRange().getEndOffset() <= endOffset) { |
| prev = run; |
| run = getParentOfType(run, clazz); |
| } |
| |
| if (prev == null) continue; |
| final int elementStartOffset = prev.getTextRange().getStartOffset(); |
| final int elementEndOffset = prev.getTextRange().getEndOffset(); |
| if (elementStartOffset != startOffset || elementEndOffset > endOffset) continue; |
| |
| if (result == null || result.getTextRange().getEndOffset() < elementEndOffset) { |
| result = prev; |
| } |
| } |
| |
| return result; |
| } |
| |
| @NotNull |
| public static PsiElement getDeepestFirst(@NotNull PsiElement elt) { |
| @NotNull PsiElement res = elt; |
| do { |
| final PsiElement firstChild = res.getFirstChild(); |
| if (firstChild == null) return res; |
| res = firstChild; |
| } |
| while (true); |
| } |
| |
| @NotNull |
| public static PsiElement getDeepestLast(@NotNull PsiElement elt) { |
| @NotNull PsiElement res = elt; |
| do { |
| final PsiElement lastChild = res.getLastChild(); |
| if (lastChild == null) return res; |
| res = lastChild; |
| } |
| while (true); |
| } |
| |
| @Nullable |
| public static PsiElement prevLeaf(@NotNull PsiElement current) { |
| final PsiElement prevSibling = current.getPrevSibling(); |
| if (prevSibling != null) return lastChild(prevSibling); |
| final PsiElement parent = current.getParent(); |
| if (parent == null || parent instanceof PsiFile) return null; |
| return prevLeaf(parent); |
| } |
| |
| @Nullable |
| public static PsiElement nextLeaf(@NotNull PsiElement current) { |
| final PsiElement nextSibling = current.getNextSibling(); |
| if (nextSibling != null) return firstChild(nextSibling); |
| final PsiElement parent = current.getParent(); |
| if (parent == null || parent instanceof PsiFile) return null; |
| return nextLeaf(parent); |
| } |
| |
| public static PsiElement lastChild(@NotNull PsiElement element) { |
| PsiElement lastChild = element.getLastChild(); |
| if (lastChild != null) return lastChild(lastChild); |
| return element; |
| } |
| |
| public static PsiElement firstChild(@NotNull final PsiElement element) { |
| PsiElement child = element.getFirstChild(); |
| if (child != null) return firstChild(child); |
| return element; |
| } |
| |
| @Nullable |
| public static PsiElement prevLeaf(@NotNull final PsiElement element, final boolean skipEmptyElements) { |
| PsiElement prevLeaf = prevLeaf(element); |
| while (skipEmptyElements && prevLeaf != null && prevLeaf.getTextLength() == 0) prevLeaf = prevLeaf(prevLeaf); |
| return prevLeaf; |
| } |
| |
| @Nullable |
| public static PsiElement prevVisibleLeaf(@NotNull final PsiElement element) { |
| PsiElement prevLeaf = prevLeaf(element, true); |
| while (prevLeaf != null && StringUtil.isEmptyOrSpaces(prevLeaf.getText())) prevLeaf = prevLeaf(prevLeaf, true); |
| return prevLeaf; |
| } |
| |
| @Nullable |
| public static PsiElement nextVisibleLeaf(@NotNull final PsiElement element) { |
| PsiElement nextLeaf = nextLeaf(element, true); |
| while (nextLeaf != null && StringUtil.isEmptyOrSpaces(nextLeaf.getText())) nextLeaf = nextLeaf(nextLeaf, true); |
| return nextLeaf; |
| } |
| |
| @Nullable |
| public static PsiElement nextLeaf(final PsiElement element, final boolean skipEmptyElements) { |
| PsiElement nextLeaf = nextLeaf(element); |
| while (skipEmptyElements && nextLeaf != null && nextLeaf.getTextLength() == 0) nextLeaf = nextLeaf(nextLeaf); |
| return nextLeaf; |
| } |
| |
| public static boolean hasErrorElements(@NotNull final PsiElement element) { |
| if (element instanceof PsiErrorElement) return true; |
| |
| for (PsiElement child : element.getChildren()) { |
| if (hasErrorElements(child)) return true; |
| } |
| |
| return false; |
| } |
| |
| @NotNull |
| public static PsiElement[] filterAncestors(@NotNull PsiElement[] elements) { |
| if (LOG.isDebugEnabled()) { |
| for (PsiElement element : elements) { |
| LOG.debug("element = " + element); |
| } |
| } |
| |
| ArrayList<PsiElement> filteredElements = new ArrayList<PsiElement>(); |
| ContainerUtil.addAll(filteredElements, elements); |
| |
| int previousSize; |
| do { |
| previousSize = filteredElements.size(); |
| outer: |
| for (PsiElement element : filteredElements) { |
| for (PsiElement element2 : filteredElements) { |
| if (element == element2) continue; |
| if (isAncestor(element, element2, false)) { |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("removing " + element2); |
| } |
| filteredElements.remove(element2); |
| break outer; |
| } |
| } |
| } |
| } |
| while (filteredElements.size() != previousSize); |
| |
| if (LOG.isDebugEnabled()) { |
| for (PsiElement element : filteredElements) { |
| LOG.debug("filtered element = " + element); |
| } |
| } |
| |
| return PsiUtilCore.toPsiElementArray(filteredElements); |
| } |
| |
| public static boolean treeWalkUp(@NotNull final PsiScopeProcessor processor, |
| @NotNull final PsiElement entrance, |
| @Nullable final PsiElement maxScope, |
| @NotNull final ResolveState state) { |
| PsiElement prevParent = entrance; |
| PsiElement scope = entrance; |
| |
| while (scope != null) { |
| if (!scope.processDeclarations(processor, state, prevParent, entrance)) return false; |
| |
| if (scope == maxScope) break; |
| prevParent = scope; |
| scope = prevParent.getContext(); |
| } |
| |
| return true; |
| } |
| |
| public static boolean treeWalkUp(@NotNull final PsiElement entrance, |
| @Nullable final PsiElement maxScope, |
| PairProcessor<PsiElement, PsiElement> eachScopeAndLastParent) { |
| PsiElement prevParent = null; |
| PsiElement scope = entrance; |
| |
| while (scope != null) { |
| if (!eachScopeAndLastParent.process(scope, prevParent)) return false; |
| |
| if (scope == maxScope) break; |
| prevParent = scope; |
| scope = prevParent.getContext(); |
| } |
| |
| return true; |
| |
| } |
| |
| @NotNull |
| public static PsiElement findPrevParent(@NotNull PsiElement ancestor, @NotNull PsiElement descendant) { |
| PsiElement cur = descendant; |
| while (cur != null) { |
| final PsiElement parent = cur.getParent(); |
| if (parent == ancestor) { |
| return cur; |
| } |
| cur = parent; |
| } |
| throw new AssertionError(descendant + " is not a descendant of " + ancestor); |
| } |
| |
| public static List<PsiElement> getInjectedElements(@NotNull OuterLanguageElement outerLanguageElement) { |
| PsiElement psi = outerLanguageElement.getContainingFile().getViewProvider().getPsi(outerLanguageElement.getLanguage()); |
| TextRange injectionRange = outerLanguageElement.getTextRange(); |
| List<PsiElement> res = ContainerUtil.newArrayList(); |
| |
| assert psi != null : outerLanguageElement; |
| for (PsiElement element = psi.findElementAt(injectionRange.getStartOffset()); |
| element != null && injectionRange.intersectsStrict(element.getTextRange()); |
| element = element.getNextSibling()) { |
| res.add(element); |
| } |
| |
| return res; |
| } |
| } |