/* * Copyright 2000-2016 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.codeInsight.daemon.impl; import com.intellij.lang.Language; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.TextRange; import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiUtilCore; import com.intellij.reference.SoftReference; import com.intellij.util.Processor; import com.intellij.util.containers.Stack; import gnu.trove.TIntStack; import org.jetbrains.annotations.NotNull; import java.lang.ref.Reference; import java.util.ArrayList; import java.util.List; public class Divider { private static final int STARTING_TREE_HEIGHT = 10; public static class DividedElements { private final long modificationStamp; @NotNull public final PsiFile root; @NotNull private final TextRange restrictRange; @NotNull private final TextRange priorityRange; public final List<PsiElement> inside = new ArrayList<>(); final List<ProperTextRange> insideRanges = new ArrayList<>(); public final List<PsiElement> outside = new ArrayList<>(); final List<ProperTextRange> outsideRanges = new ArrayList<>(); public final List<PsiElement> parents = new ArrayList<>(); final List<ProperTextRange> parentRanges = new ArrayList<>(); private DividedElements(long modificationStamp, @NotNull PsiFile root, @NotNull TextRange restrictRange, @NotNull TextRange priorityRange) { this.modificationStamp = modificationStamp; this.root = root; this.restrictRange = restrictRange; this.priorityRange = priorityRange; } } private static final Key<Reference<DividedElements>> DIVIDED_ELEMENTS_KEY = Key.create("DIVIDED_ELEMENTS"); public static void divideInsideAndOutsideAllRoots(@NotNull PsiFile file, @NotNull TextRange restrictRange, @NotNull TextRange priorityRange, @NotNull Condition<PsiFile> rootFilter, @NotNull Processor<DividedElements> processor) { final FileViewProvider viewProvider = file.getViewProvider(); for (Language language : viewProvider.getLanguages()) { final PsiFile root = viewProvider.getPsi(language); if (!rootFilter.value(root)) { continue; } divideInsideAndOutsideInOneRoot(root, restrictRange, priorityRange, processor); } } static void divideInsideAndOutsideInOneRoot(@NotNull PsiFile root, @NotNull TextRange restrictRange, @NotNull TextRange priorityRange, @NotNull Processor<DividedElements> processor) { long modificationStamp = root.getModificationStamp(); DividedElements cached = SoftReference.dereference(root.getUserData(DIVIDED_ELEMENTS_KEY)); DividedElements elements; if (cached == null || cached.modificationStamp != modificationStamp || !cached.restrictRange.equals(restrictRange) || !cached.priorityRange.contains(priorityRange)) { elements = new DividedElements(modificationStamp, root, restrictRange, priorityRange); divideInsideAndOutsideInOneRoot(root, restrictRange, priorityRange, elements.inside, elements.insideRanges, elements.outside, elements.outsideRanges, elements.parents, elements.parentRanges, true); root.putUserData(DIVIDED_ELEMENTS_KEY, new java.lang.ref.SoftReference<>(elements)); } else { elements = cached; } processor.process(elements); } private static final PsiElement HAVE_TO_GET_CHILDREN = PsiUtilCore.NULL_PSI_ELEMENT; private static void divideInsideAndOutsideInOneRoot(@NotNull PsiFile root, @NotNull TextRange restrictRange, @NotNull TextRange priorityRange, @NotNull List<PsiElement> inside, @NotNull List<ProperTextRange> insideRanges, @NotNull List<PsiElement> outside, @NotNull List<ProperTextRange> outsideRanges, @NotNull List<PsiElement> outParents, @NotNull List<ProperTextRange> outParentRanges, boolean includeParents) { int startOffset = restrictRange.getStartOffset(); int endOffset = restrictRange.getEndOffset(); final Condition<PsiElement>[] filters = Extensions.getExtensions(CollectHighlightsUtil.EP_NAME); final TIntStack starts = new TIntStack(STARTING_TREE_HEIGHT); starts.push(startOffset); final Stack<PsiElement> elements = new Stack<>(STARTING_TREE_HEIGHT); final Stack<PsiElement> children = new Stack<>(STARTING_TREE_HEIGHT); PsiElement element = root; PsiElement child = HAVE_TO_GET_CHILDREN; int offset = 0; while (true) { ProgressManager.checkCanceled(); for (Condition<PsiElement> filter : filters) { if (!filter.value(element)) { assert child == HAVE_TO_GET_CHILDREN; child = null; // do not want to process children break; } } boolean startChildrenVisiting; if (child == HAVE_TO_GET_CHILDREN) { startChildrenVisiting = true; child = element.getFirstChild(); } else { startChildrenVisiting = false; } if (child == null) { if (startChildrenVisiting) { // leaf element offset += element.getTextLength(); } int start = starts.pop(); if (startOffset <= start && offset <= endOffset) { if (priorityRange.containsRange(start, offset)) { inside.add(element); insideRanges.add(new ProperTextRange(start, offset)); } else { outside.add(element); outsideRanges.add(new ProperTextRange(start, offset)); } } if (elements.isEmpty()) break; element = elements.pop(); child = children.pop(); } else { // composite element if (offset > endOffset) break; children.push(child.getNextSibling()); starts.push(offset); elements.push(element); element = child; child = HAVE_TO_GET_CHILDREN; } } if (includeParents) { PsiElement parent = !outside.isEmpty() ? outside.get(outside.size() - 1) : !inside.isEmpty() ? inside.get(inside.size() - 1) : CollectHighlightsUtil.findCommonParent(root, startOffset, endOffset); while (parent != null && !(parent instanceof PsiFile)) { parent = parent.getParent(); if (parent != null) { outParents.add(parent); TextRange textRange = parent.getTextRange(); assert textRange != null : "Text range for " + parent + " is null. " + parent.getClass() +"; root: "+root+": "+root.getVirtualFile(); outParentRanges.add(ProperTextRange.create(textRange)); } } } assert inside.size() == insideRanges.size(); assert outside.size() == outsideRanges.size(); assert outParents.size() == outParentRanges.size(); } }