/* * Copyright 2000-2015 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.formatter.common; import com.intellij.formatting.*; import com.intellij.injected.editor.DocumentWindow; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.formatter.FormatterUtil; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.util.containers.ContainerUtilRt; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; public abstract class AbstractBlock implements ASTBlock, ExtraRangesProvider { public static final List<Block> EMPTY = Collections.emptyList(); @NotNull protected final ASTNode myNode; @Nullable protected final Wrap myWrap; @Nullable protected final Alignment myAlignment; private List<Block> mySubBlocks; private Boolean myIncomplete; private boolean myBuildIndentsOnly = false; protected AbstractBlock(@NotNull ASTNode node, @Nullable Wrap wrap, @Nullable Alignment alignment) { myNode = node; myWrap = wrap; myAlignment = alignment; } @Override @NotNull public TextRange getTextRange() { return myNode.getTextRange(); } @Override @NotNull public List<Block> getSubBlocks() { if (mySubBlocks == null) { List<Block> list = buildChildren(); if (list.isEmpty()) { list = buildInjectedBlocks(); } mySubBlocks = !list.isEmpty() ? list : EMPTY; } return mySubBlocks; } /** * Prevents from building injected blocks, which allows to build blocks faster * Initially was made for formatting-based indent detector */ public void setBuildIndentsOnly(boolean value) { myBuildIndentsOnly = value; } protected boolean isBuildIndentsOnly() { return myBuildIndentsOnly; } @NotNull private List<Block> buildInjectedBlocks() { if (myBuildIndentsOnly) { return EMPTY; } if (!(this instanceof SettingsAwareBlock)) { return EMPTY; } PsiElement psi = myNode.getPsi(); if (psi == null) { return EMPTY; } PsiFile file = psi.getContainingFile(); if (file == null) { return EMPTY; } if (InjectedLanguageUtil.getCachedInjectedDocuments(file).isEmpty()) { return EMPTY; } TextRange blockRange = myNode.getTextRange(); List<DocumentWindow> documentWindows = InjectedLanguageUtil.getCachedInjectedDocuments(file); for (DocumentWindow documentWindow : documentWindows) { int startOffset = documentWindow.injectedToHost(0); int endOffset = startOffset + documentWindow.getTextLength(); if (blockRange.containsRange(startOffset, endOffset)) { PsiFile injected = PsiDocumentManager.getInstance(psi.getProject()).getCachedPsiFile(documentWindow); if (injected != null) { List<Block> result = ContainerUtilRt.newArrayList(); DefaultInjectedLanguageBlockBuilder builder = new DefaultInjectedLanguageBlockBuilder(((SettingsAwareBlock)this).getSettings()); builder.addInjectedBlocks(result, myNode, getWrap(), getAlignment(), getIndent()); return result; } } } return EMPTY; } protected abstract List<Block> buildChildren(); @Nullable @Override public Wrap getWrap() { return myWrap; } @Override public Indent getIndent() { return null; } @Nullable @Override public Alignment getAlignment() { return myAlignment; } @NotNull @Override public ASTNode getNode() { return myNode; } @Override @NotNull public ChildAttributes getChildAttributes(final int newChildIndex) { return new ChildAttributes(getChildIndent(), getFirstChildAlignment()); } @Nullable private Alignment getFirstChildAlignment() { List<Block> subBlocks = getSubBlocks(); for (final Block subBlock : subBlocks) { Alignment alignment = subBlock.getAlignment(); if (alignment != null) { return alignment; } } return null; } @Nullable protected Indent getChildIndent() { return null; } @Override public boolean isIncomplete() { if (myIncomplete == null) { myIncomplete = FormatterUtil.isIncomplete(getNode()); } return myIncomplete; } @Override public String toString() { return myNode.getText() + " " + getTextRange(); } /** * @return additional range to reformat, when this block if formatted */ @Override @Nullable public List<TextRange> getExtraRangesToFormat(@NotNull FormattingRangesInfo info) { int startOffset = getTextRange().getStartOffset(); if (info.isOnInsertedLine(startOffset) && myNode.textContains('\n')) { return new NodeIndentRangesCalculator(myNode).calculateExtraRanges(); } return null; } }