blob: 159edca4b385263a231db83b3aed8ca421db7fc6 [file] [log] [blame]
/*
* 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.formatter.xml;
import com.intellij.formatting.*;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.formatter.common.AbstractBlock;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlElementType;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlTokenType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class SyntheticBlock extends AbstractSyntheticBlock implements Block, ReadOnlyBlockContainer {
private final List<Block> mySubBlocks;
private final Indent myChildIndent;
public SyntheticBlock(final List<Block> subBlocks, final Block parent, final Indent indent, XmlFormattingPolicy policy, final Indent childIndent) {
super(subBlocks, parent, policy, indent);
mySubBlocks = subBlocks;
myChildIndent = childIndent;
}
@Override
@NotNull
public TextRange getTextRange() {
return calculateTextRange(mySubBlocks);
}
@Override
@NotNull
public List<Block> getSubBlocks() {
return mySubBlocks;
}
@Override
public Spacing getSpacing(Block child1, @NotNull Block child2) {
if (child1 instanceof ReadOnlyBlock || child2 instanceof ReadOnlyBlock) {
return Spacing.getReadOnlySpacing();
}
if (!(child1 instanceof AbstractXmlBlock) || !(child2 instanceof AbstractXmlBlock)) {
return null;
}
final ASTNode node1 = ((AbstractBlock)child1).getNode();
final ASTNode node2 = ((AbstractBlock)child2).getNode();
final IElementType type1 = node1.getElementType();
final IElementType type2 = node2.getElementType();
boolean firstIsText = isTextFragment(node1);
boolean secondIsText = isTextFragment(node2);
if (((AbstractXmlBlock)child1).isPreserveSpace() && ((AbstractXmlBlock)child2).isPreserveSpace()) {
return Spacing.getReadOnlySpacing();
}
if (type1 == XmlTokenType.XML_CDATA_START || type2 == XmlTokenType.XML_CDATA_END) {
if (myXmlFormattingPolicy.getKeepWhiteSpacesInsideCDATA()) {
return Spacing.getReadOnlySpacing();
}
if (type1 == XmlTokenType.XML_CDATA_START && type2 == XmlTokenType.XML_CDATA_END) {
return Spacing.createSpacing(0, 0, 0, myXmlFormattingPolicy.getShouldKeepLineBreaks(), myXmlFormattingPolicy.getKeepBlankLines());
}
if (type1 == XmlTokenType.XML_CDATA_START && child2 instanceof AnotherLanguageBlockWrapper ||
type2 == XmlTokenType.XML_CDATA_END && child1 instanceof AnotherLanguageBlockWrapper) {
return Spacing.createSpacing(0, 0, 1, myXmlFormattingPolicy.getShouldKeepLineBreaks(), 0);
}
}
boolean firstIsTag = node1.getPsi() instanceof XmlTag && !firstIsText;
boolean secondIsTag = node2.getPsi() instanceof XmlTag && !secondIsText;
boolean firstIsEntityRef = isEntityRef(node1);
boolean secondIsEntityRef = isEntityRef(node2);
if (isSpaceInText(firstIsTag, secondIsTag, firstIsText, secondIsText) && keepWhiteSpaces()) {
return Spacing.getReadOnlySpacing();
}
if (firstIsEntityRef || secondIsEntityRef) {
return Spacing.createSafeSpacing(myXmlFormattingPolicy.getShouldKeepLineBreaks(), myXmlFormattingPolicy.getKeepBlankLines());
}
if (type1 == XmlElementType.XML_ATTRIBUTE && (type2 == XmlTokenType.XML_TAG_END || type2 == XmlTokenType.XML_EMPTY_ELEMENT_END)) {
final PsiElement psi1 = node1.getPsi();
if (psi1 instanceof XmlAttribute && myXmlFormattingPolicy.insertLineBreakAfterLastAttribute((XmlAttribute)psi1)) {
return Spacing.createSpacing(0, 0, 1,
myXmlFormattingPolicy.getShouldKeepLineBreaks(),
myXmlFormattingPolicy.getKeepBlankLines());
}
}
if (type2 == XmlTokenType.XML_EMPTY_ELEMENT_END && myXmlFormattingPolicy.addSpaceIntoEmptyTag()) {
return Spacing.createSpacing(1, 1, 0,
myXmlFormattingPolicy.getShouldKeepLineBreaks(),
myXmlFormattingPolicy.getKeepBlankLines());
}
if (secondIsTag && myXmlFormattingPolicy.keepWhiteSpacesInsideTag((XmlTag)node2.getPsi()) && node2.textContains('\n')) {
return Spacing.getReadOnlySpacing();
}
if (isXmlTagName(type1, type2)){
final int spaces = shouldAddSpaceAroundTagName(node1, node2) ? 1 : 0;
return Spacing.createSpacing(spaces, spaces, 0, myXmlFormattingPolicy.getShouldKeepLineBreaks(), myXmlFormattingPolicy.getKeepBlankLines());
}
if (type2 == XmlElementType.XML_ATTRIBUTE) {
int minLineFeeds = 0;
if (type1 == XmlTokenType.XML_NAME) {
final PsiElement psi2 = node2.getPsi();
minLineFeeds = psi2 instanceof XmlAttribute &&
myXmlFormattingPolicy.insertLineBreakBeforeFirstAttribute((XmlAttribute)psi2)
? 1 : 0;
}
return Spacing.createSpacing(1, 1, minLineFeeds, myXmlFormattingPolicy.getShouldKeepLineBreaks(), myXmlFormattingPolicy.getKeepBlankLines());
}
if (((AbstractXmlBlock)child1).isTextElement() && ((AbstractXmlBlock)child2).isTextElement()) {
return Spacing.createSafeSpacing(myXmlFormattingPolicy.getShouldKeepLineBreaksInText(), myXmlFormattingPolicy.getKeepBlankLines());
}
if (firstIsTag && insertLineFeedAfter((XmlTag)node1.getPsi())) {
return Spacing.createSpacing(0, 0, 1, true, myXmlFormattingPolicy.getKeepBlankLines());
}
if ((firstIsText || firstIsTag) && secondIsTag) {
//<tag/>text <tag/></tag>
if (((AbstractXmlBlock)child2).insertLineBreakBeforeTag()) {
return Spacing.createSpacing(0, Integer.MAX_VALUE, 2, myXmlFormattingPolicy.getShouldKeepLineBreaks(),
myXmlFormattingPolicy.getKeepBlankLines());
} else if (((AbstractXmlBlock)child2).removeLineBreakBeforeTag()) {
return Spacing.createSpacing(0, Integer.MAX_VALUE, 0, myXmlFormattingPolicy.getShouldKeepLineBreaks(),
myXmlFormattingPolicy.getKeepBlankLines());
}
}
final boolean saveSpacesBetweenTagAndText =
myXmlFormattingPolicy.shouldSaveSpacesBetweenTagAndText() && child1.getTextRange().getEndOffset() < child2.getTextRange().getStartOffset();
if (firstIsTag && secondIsText) { //<tag/>-text
if (((AbstractXmlBlock)child1).isTextElement() || saveSpacesBetweenTagAndText) {
return Spacing.createSafeSpacing(true, myXmlFormattingPolicy.getKeepBlankLines());
} else {
return Spacing.createSpacing(0, 0, 0, true, myXmlFormattingPolicy.getKeepBlankLines());
}
}
if ( firstIsText && secondIsTag) { //text-<tag/>
if (((AbstractXmlBlock)child2).isTextElement() || saveSpacesBetweenTagAndText) {
return Spacing.createSafeSpacing(true, myXmlFormattingPolicy.getKeepBlankLines());
} else {
return Spacing.createSpacing(0, 0, 0, true, myXmlFormattingPolicy.getKeepBlankLines());
}
}
if (firstIsTag && secondIsTag) {//<tag/><tag/>
return Spacing.createSpacing(0, Integer.MAX_VALUE, 0, true,
myXmlFormattingPolicy.getKeepBlankLines());
}
return Spacing.createSpacing(0, Integer.MAX_VALUE, 0, myXmlFormattingPolicy.getShouldKeepLineBreaksInText(), myXmlFormattingPolicy.getKeepBlankLines());
}
private boolean isEntityRef(final ASTNode node) {
return node.getElementType() == XmlElementType.XML_ENTITY_REF;
}
private boolean shouldAddSpaceAroundTagName(final ASTNode node1, final ASTNode node2) {
if (node1.getElementType() == XmlTokenType.XML_START_TAG_START && node1.textContains('%')) return true;
if (node2.getElementType() == XmlTokenType.XML_EMPTY_ELEMENT_END && node2.textContains('%')) return true;
return myXmlFormattingPolicy.getShouldAddSpaceAroundTagName();
}
private boolean isSpaceInText(final boolean firstIsTag,
final boolean secondIsTag,
final boolean firstIsText,
final boolean secondIsText) {
return
(firstIsText && secondIsText)
|| (firstIsTag && secondIsTag)
|| (firstIsTag && secondIsText)
|| (firstIsText && secondIsTag);
}
private boolean keepWhiteSpaces() {
return (myXmlFormattingPolicy.keepWhiteSpacesInsideTag( getTag()) || myXmlFormattingPolicy.getShouldKeepWhiteSpaces());
}
protected boolean isTextFragment(final ASTNode node) {
final ASTNode parent = node.getTreeParent();
return parent != null && parent.getElementType() == XmlElementType.XML_TEXT
|| node.getElementType() == XmlTokenType.XML_DATA_CHARACTERS
;
}
@Override
@NotNull
public ChildAttributes getChildAttributes(final int newChildIndex) {
if (isOuterLanguageBlock()) return ChildAttributes.DELEGATE_TO_NEXT_CHILD;
final List<Block> subBlocks = getSubBlocks();
final int prevBlockIndex = newChildIndex - 1;
if (prevBlockIndex >= 0 && prevBlockIndex < subBlocks.size()) {
final Block prevBlock = subBlocks.get(newChildIndex - 1);
if (isAttributeBlock(prevBlock)) {
return new ChildAttributes(myChildIndent, prevBlock.getAlignment());
}
}
return new ChildAttributes(myChildIndent, null);
}
private boolean isAttributeBlock(final Block block) {
if (block instanceof XmlBlock) {
return ((XmlBlock)block).getNode().getElementType() == XmlElementType.XML_ATTRIBUTE;
}
return false;
}
@Override
public boolean isIncomplete() {
return getSubBlocks().get(getSubBlocks().size() - 1).isIncomplete();
}
public boolean endsWithAttribute() {
return isAttributeBlock(getSubBlocks().get(getSubBlocks().size() - 1));
}
public Indent getChildIndent() {
return myChildIndent;
}
}