/* * Copyright 2000-2013 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.coldFusion.model.formatter; import com.intellij.coldFusion.model.CfmlLanguage; import com.intellij.coldFusion.model.files.CfmlFileType; import com.intellij.coldFusion.model.lexer.CfmlTokenTypes; import com.intellij.coldFusion.model.lexer.CfscriptTokenTypes; import com.intellij.coldFusion.model.parsers.CfmlElementTypes; import com.intellij.coldFusion.model.psi.stubs.CfmlStubElementTypes; import com.intellij.formatting.*; import com.intellij.formatting.templateLanguages.BlockWithParent; import com.intellij.formatting.templateLanguages.DataLanguageBlockWrapper; import com.intellij.formatting.templateLanguages.TemplateLanguageBlock; import com.intellij.formatting.templateLanguages.TemplateLanguageBlockFactory; import com.intellij.lang.ASTNode; import com.intellij.lang.html.HTMLLanguage; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.tree.IElementType; import com.intellij.psi.xml.XmlTokenType; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Arrays; import java.util.LinkedList; import java.util.List; public class CfmlBlock extends TemplateLanguageBlock { private Indent myIndent; private Wrap myChildWrap; private CommonCodeStyleSettings mySettings; private CodeStyleSettings superSettings; private CfmlIndentProcessor myIndentProcessor; private CfmlWrappingProcessor myWrappingProcessor; private CfmlSpacingProcessor mySpacingProcessor; private CfmlAlignmentProcessor myAlignmentProcessor; private final TextRange myTextRange; public CfmlBlock(ASTNode node, Wrap wrap, Alignment alignment, CodeStyleSettings settings, @NotNull TemplateLanguageBlockFactory blockFactory, @Nullable List<DataLanguageBlockWrapper> foreignChildren) { super(node, wrap, alignment, blockFactory, settings, foreignChildren); superSettings = settings; mySettings = getSettings().getCommonSettings(CfmlLanguage.INSTANCE); CfmlCodeStyleSettings cfmlSettings = getSettings().getCustomSettings(CfmlCodeStyleSettings.class); myIndentProcessor = new CfmlIndentProcessor(mySettings, settings.getIndentSize(CfmlFileType.INSTANCE)); myWrappingProcessor = new CfmlWrappingProcessor(node, mySettings); mySpacingProcessor = new CfmlSpacingProcessor(node, mySettings, cfmlSettings); myAlignmentProcessor = new CfmlAlignmentProcessor(node, mySettings); myIndent = getChildIndent(); myTextRange = trimRangeToNonWhiteSpaceIfNeeded(); } @Override protected IElementType getTemplateTextElementType() { return null; } @Nullable @Override protected Indent getChildIndent() { return myIndentProcessor.getChildIndent(myNode); } protected List<Block> buildChildren() { if (myNode.getElementType() == CfmlElementTypes.SQL) { //toDO: build blocks for SQL and merge trees. } return super.buildChildren(); } private static final IElementType[] WHITESPACE = new IElementType[]{XmlTokenType.XML_WHITE_SPACE, CfmlTokenTypes.WHITE_SPACE}; // TODO this is a hack to be removed when template blocks are implemented properly // for template text block we look for corresponding elements in HTML PSI tree and use contiguous range of non-whitespace elements private TextRange trimRangeToNonWhiteSpaceIfNeeded() { TextRange defaultRange = myNode.getTextRange(); if (myNode.getElementType() != CfmlElementTypes.TEMPLATE_TEXT) { return defaultRange; } PsiFile htmlFile = myNode.getPsi().getContainingFile().getViewProvider().getPsi(HTMLLanguage.INSTANCE); if (htmlFile == null) { return defaultRange; } @Nullable TextRange nonWhitespace = null; final PsiElement commonHtmlParent = findCommonHtmlParent(htmlFile.getViewProvider().findElementAt(defaultRange.getStartOffset(), HTMLLanguage.INSTANCE), htmlFile.getViewProvider().findElementAt(defaultRange.getEndOffset() - 1, HTMLLanguage.INSTANCE)); if (commonHtmlParent == null) { return defaultRange; } LinkedList<PsiElement> elements = new LinkedList<>(Arrays.asList(commonHtmlParent.getChildren())); while (!elements.isEmpty()) { PsiElement e = elements.remove(); if (!ArrayUtil.contains(e.getNode().getElementType(), WHITESPACE)) { if (defaultRange.contains(e.getTextRange())) { nonWhitespace = nonWhitespace == null ? e.getTextRange() : nonWhitespace.union(e.getTextRange()); } else if (defaultRange.intersects(e.getTextRange())) { elements.addAll(Arrays.asList(e.getChildren())); } } } if (nonWhitespace != null) { assert defaultRange.intersects(nonWhitespace); return defaultRange.intersection(nonWhitespace); } return defaultRange; } @Nullable private static PsiElement findCommonHtmlParent(@Nullable PsiElement start, @Nullable PsiElement end) { if (start == null || end == null || start == end) { return start; } final TextRange endRange = end.getTextRange(); PsiElement parent = start.getParent(); while (parent != null && !parent.getTextRange().contains(endRange)) { parent = parent.getParent(); } return parent; } @Override @NotNull public TextRange getTextRange() { return myTextRange; } public Indent getIndent() { return myIndent; } public Wrap getChildWrap() { return myChildWrap; } @Override public Spacing getSpacing(Block child1, @NotNull Block child2) { return mySpacingProcessor.getSpacing(child1, child2); } @Override public Wrap createChildWrap(ASTNode child) { Wrap defaultWrap = super.createChildWrap(child); IElementType childType = child.getElementType(); BlockWithParent parent = getParent(); Wrap childWrap = parent instanceof CfmlBlock ? ((CfmlBlock)parent).getChildWrap() : null; Wrap wrap = myWrappingProcessor.createChildWrap(child, defaultWrap, childWrap); if (CfmlFormatterUtil.isAssignmentOperator(childType)) { myChildWrap = wrap; } return wrap; } @Override @Nullable protected Alignment createChildAlignment(ASTNode child) { if (child.getElementType() != CfscriptTokenTypes.FOR_KEYWORD && child.getElementType() != CfscriptTokenTypes.L_BRACKET && child.getElementType() != CfmlElementTypes.BLOCK_OF_STATEMENTS) { return myAlignmentProcessor.createChildAlignment(); } return null; } @NotNull @Override public ChildAttributes getChildAttributes(int newChildIndex) { List<Block> childBlockList = getSubBlocks(); if (newChildIndex > 0 && newChildIndex - 1 < childBlockList.size()) { ASTBlock prevBlock = (ASTBlock)childBlockList.get(newChildIndex - 1); if (prevBlock != null) { Indent indent; Alignment alignment = myAlignmentProcessor.createChildAlignment(); ASTNode prevNode = prevBlock.getNode(); if (prevNode != null) { PsiElement prevTreePsiElement = prevNode.getTreePrev() != null ? prevNode.getTreePrev().getPsi() : null; IElementType prevBlockType = prevNode.getElementType(); if (prevBlockType == CfscriptTokenTypes.L_CURLYBRACKET) { if (myNode.getElementType() == CfmlElementTypes.FUNCTION_DEFINITION && mySettings.METHOD_BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED2) { indent = Indent.getSpaceIndent(superSettings.getIndentSize(CfmlFileType.INSTANCE) * 2); } else if (mySettings.BRACE_STYLE == CommonCodeStyleSettings.NEXT_LINE_SHIFTED2) { indent = Indent.getSpaceIndent(superSettings.getIndentSize(CfmlFileType.INSTANCE) * 2); } else { indent = Indent.getNormalIndent(); } } else if (myNode.getElementType() == CfmlStubElementTypes.CFML_FILE) { indent = Indent.getNoneIndent(); } else if ((prevBlockType == CfmlTokenTypes.R_ANGLEBRACKET) && prevTreePsiElement != null && !prevTreePsiElement.getText().equalsIgnoreCase("cfscript")) { indent = Indent.getNormalIndent(); } else if ((prevBlockType == CfmlTokenTypes.CLOSER) && prevTreePsiElement != null && !prevTreePsiElement.getText().equalsIgnoreCase("cfscript")) { indent = null; } else if (prevBlockType == CfmlElementTypes.TAG || prevBlockType == CfmlElementTypes.ARGUMENT_TAG || prevBlockType == CfmlElementTypes.COMPONENT_TAG || prevBlockType == CfmlElementTypes.FUNCTION_TAG || prevBlockType == CfmlElementTypes.SCRIPT_TAG) { indent = Indent.getNormalIndent(); } else if (prevBlockType == CfmlTokenTypes.COMMENT || prevBlockType == CfmlElementTypes.TEMPLATE_TEXT) { indent = prevBlock.getIndent();//getChildAttributes(newChildIndex - 1).getChildIndent(); } else { indent = Indent.getNormalIndent(); } } else { indent = ((DataLanguageBlockWrapper)prevBlock).getOriginal().getIndent(); } return new ChildAttributes(indent, alignment); } } return super.getChildAttributes(newChildIndex); } }