blob: a97d6d12646fa4895adf3d5d2f7513d257208fb2 [file] [log] [blame]
/*
* 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.psi.formatter.java;
import com.intellij.formatting.*;
import com.intellij.lang.ASTNode;
import com.intellij.psi.JavaTokenType;
import com.intellij.psi.PsiStatement;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
import com.intellij.psi.formatter.FormatterUtil;
import com.intellij.psi.impl.source.tree.ElementType;
import com.intellij.psi.impl.source.tree.JavaDocElementType;
import com.intellij.psi.impl.source.tree.JavaElementType;
import com.intellij.psi.impl.source.tree.StdTokenSets;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
public class BlockContainingJavaBlock extends AbstractJavaBlock{
private static final TokenSet TYPES_OF_STATEMENTS_WITH_OPTIONAL_BRACES = TokenSet.create(
JavaElementType.IF_STATEMENT, JavaElementType.WHILE_STATEMENT, JavaElementType.FOR_STATEMENT
);
private static final int BEFORE_FIRST = 0;
private static final int BEFORE_BLOCK = 1;
private static final int AFTER_ELSE = 2;
private final List<Indent> myIndentsBefore = new ArrayList<Indent>();
public BlockContainingJavaBlock(ASTNode node,
Wrap wrap,
Alignment alignment,
Indent indent,
CommonCodeStyleSettings settings,
JavaCodeStyleSettings javaSettings) {
super(node, wrap, alignment, indent, settings, javaSettings);
}
@Override
protected List<Block> buildChildren() {
final ArrayList<Block> result = new ArrayList<Block>();
Alignment childAlignment = createChildAlignment();
Wrap childWrap = createChildWrap();
buildChildren(result, childAlignment, childWrap);
return result;
}
private void buildChildren(final ArrayList<Block> result, final Alignment childAlignment, final Wrap childWrap) {
ASTNode child = myNode.getFirstChildNode();
ASTNode prevChild = null;
int state = BEFORE_FIRST;
while (child != null) {
if (!FormatterUtil.containsWhiteSpacesOnly(child) && child.getTextLength() > 0){
final Indent indent = calcIndent(child, state);
myIndentsBefore.add(calcIndentBefore(child, state));
state = calcNewState(child, state);
// The general idea is that it's possible that there are comment lines before method declaration line and that they have
// different indents. Example:
//
// // This is comment before method
// void foo() {}
//
// We want to have the comment and method as distinct blocks then in order to correctly process indentation for inner method
// elements. See IDEA-53778 for example of situation when it is significant.
if (prevChild != null && myNode.getElementType() == JavaElementType.METHOD
&& ElementType.JAVA_COMMENT_BIT_SET.contains(prevChild.getElementType())
&& !ElementType.JAVA_COMMENT_BIT_SET.contains(child.getElementType()))
{
prevChild = child;
child = composeCodeBlock(result, child, Indent.getNoneIndent(), 0, null);
}
else {
prevChild = child;
child = processChild(result, child, childAlignment, childWrap, indent);
}
for (int i = myIndentsBefore.size(); i < result.size(); i++) {
myIndentsBefore.add(Indent.getContinuationIndent(myIndentSettings.USE_RELATIVE_INDENTS));
}
}
if (child != null) {
child = child.getTreeNext();
}
}
}
private static int calcNewState(final ASTNode child, final int state) {
if (state == BEFORE_FIRST) {
if (child.getElementType() == JavaTokenType.ELSE_KEYWORD) {
return AFTER_ELSE;
}
if (StdTokenSets.COMMENT_BIT_SET.contains(child.getElementType())) {
return BEFORE_FIRST;
}
if (child.getElementType() == JavaElementType.CATCH_SECTION) {
return BEFORE_FIRST;
}
} else if (state == BEFORE_BLOCK){
if (child.getElementType() == JavaTokenType.ELSE_KEYWORD) {
return AFTER_ELSE;
}
if (child.getElementType() == JavaElementType.BLOCK_STATEMENT) {
return BEFORE_FIRST;
}
if (child.getElementType() == JavaElementType.CODE_BLOCK) {
return BEFORE_FIRST;
}
}
return BEFORE_BLOCK;
}
private Indent calcIndent(final ASTNode child, final int state) {
if (state == AFTER_ELSE && child.getElementType() == JavaElementType.IF_STATEMENT) {
if (mySettings.SPECIAL_ELSE_IF_TREATMENT) {
return Indent.getNoneIndent();
} else {
return getCodeBlockInternalIndent(1);
}
}
if (isSimpleStatement(child)){
return createNormalIndent(1);
}
if (child.getElementType() == JavaTokenType.ELSE_KEYWORD)
return Indent.getNoneIndent();
if (state == BEFORE_FIRST || child.getElementType() == JavaTokenType.WHILE_KEYWORD) {
return Indent.getNoneIndent();
}
else {
if (isPartOfCodeBlock(child)) {
return getCodeBlockExternalIndent();
}
else if (isSimpleStatement(child) || StdTokenSets.COMMENT_BIT_SET.contains(child.getElementType())){
return getCodeBlockInternalIndent(1);
}
else {
return Indent.getContinuationIndent(myIndentSettings.USE_RELATIVE_INDENTS);
}
}
}
private Indent calcIndentBefore(final ASTNode child, final int state) {
if (state == AFTER_ELSE) {
if (!mySettings.SPECIAL_ELSE_IF_TREATMENT) {
return getCodeBlockInternalIndent(1);
} else {
return getCodeBlockExternalIndent();
}
}
if (state == BEFORE_BLOCK && (isSimpleStatement(child) || child.getElementType() == JavaElementType.BLOCK_STATEMENT)){
return getCodeBlockInternalIndent(0);
}
if (state == BEFORE_FIRST) {
return getCodeBlockExternalIndent();
}
if (child.getElementType() == JavaTokenType.ELSE_KEYWORD)
return getCodeBlockExternalIndent();
return Indent.getContinuationIndent(myIndentSettings.USE_RELATIVE_INDENTS);
}
private static boolean isSimpleStatement(final ASTNode child) {
if (child.getElementType() == JavaElementType.BLOCK_STATEMENT) return false;
if (!(child.getPsi() instanceof PsiStatement)) return false;
return isStatement(child, child.getTreeParent());
}
private static boolean isPartOfCodeBlock(final ASTNode child) {
if (child == null) return false;
if (child.getElementType() == JavaElementType.BLOCK_STATEMENT) return true;
if (child.getElementType() == JavaElementType.CODE_BLOCK) return true;
if (FormatterUtil.containsWhiteSpacesOnly(child)) return isPartOfCodeBlock(child.getTreeNext());
if (child.getElementType() == JavaTokenType.END_OF_LINE_COMMENT) return isPartOfCodeBlock(child.getTreeNext());
return child.getElementType() == JavaDocElementType.DOC_COMMENT;
}
@Override
@NotNull
public ChildAttributes getChildAttributes(final int newChildIndex) {
if (isAfter(newChildIndex, new IElementType[]{JavaDocElementType.DOC_COMMENT})) {
return new ChildAttributes(Indent.getNoneIndent(), null);
}
if (myNode.getElementType() == JavaElementType.FOR_STATEMENT && mySettings.ALIGN_MULTILINE_FOR && isInsideForParens(newChildIndex)) {
Alignment prev = getUsedAlignment(newChildIndex);
if (prev != null) {
return new ChildAttributes(null, prev);
}
}
if (newChildIndex == 0) {
return new ChildAttributes(getCodeBlockExternalIndent(), null);
}
boolean useExternalIndent = false;
if (newChildIndex == getSubBlocks().size()) {
useExternalIndent = true;
}
else if (TYPES_OF_STATEMENTS_WITH_OPTIONAL_BRACES.contains(myNode.getElementType())) {
// There is a possible case that we have situation like below:
// if (true) <enter was pressed here>
// <caret>
// System.out.println();
// We would like to indent current caret position then because there is a high probability that the user starts
// typing there (populating statement body). So, we perform dedicated check for that here and use 'external indent'
// if necessary.
Block prevBlock = getSubBlocks().get(newChildIndex - 1);
Block nextBlock = getSubBlocks().get(newChildIndex);
if (prevBlock instanceof ASTBlock && nextBlock instanceof ASTBlock) {
ASTNode prevNode = ((ASTBlock)prevBlock).getNode();
ASTNode nextNode = ((ASTBlock)nextBlock).getNode();
if (prevNode != null && nextNode != null && prevNode.getElementType() == JavaTokenType.RPARENTH
&& nextNode.getElementType() != JavaTokenType.LBRACE)
{
useExternalIndent = true;
}
}
}
if (useExternalIndent) {
return new ChildAttributes(getCodeBlockChildExternalIndent(newChildIndex), null);
}
else {
return new ChildAttributes(myIndentsBefore.get(newChildIndex), null);
}
}
private boolean isInsideForParens(final int newChildIndex) {
final List<Block> subBlocks = getSubBlocks();
for (int i = 0; i < newChildIndex; i++) {
if (i >= subBlocks.size()) return false;
final Block block = subBlocks.get(i);
if (block instanceof LeafBlock) {
if (((LeafBlock)block).getTreeNode().getElementType() == JavaTokenType.RPARENTH) return false;
}
}
return true;
}
}