package com.jetbrains.lang.dart.ide.formatter; import com.intellij.formatting.Wrap; import com.intellij.formatting.WrapType; import com.intellij.lang.ASTNode; import com.intellij.openapi.util.Key; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.formatter.FormatterUtil; import com.intellij.psi.formatter.WrappingUtil; import com.intellij.psi.tree.IElementType; import com.intellij.psi.tree.TokenSet; import static com.jetbrains.lang.dart.DartTokenTypes.*; import static com.jetbrains.lang.dart.DartTokenTypesSets.*; // TODO Eliminate redundancy. This gets called multiple times by CodeStyleManagerImpl.reformatText(). // The first is by a call to CodeFormatterFacade.processText() at line 235. // The second is from a call to EditorEx.reinitSettings() at line 251. // The second is only done when reformatting the entire file; however, when // reformatting a selection this may be called three times. public class DartWrappingProcessor { // Consider using a single key -- the grammar doesn't allow mis-use. private static final Key<Wrap> DART_TERNARY_EXPRESSION_WRAP_KEY = Key.create("TERNARY_EXPRESSION_WRAP_KEY"); private static final Key<Wrap> DART_EXPRESSION_LIST_WRAP_KEY = Key.create("EXPRESSION_LIST_WRAP_KEY"); private static final Key<Wrap> DART_ARGUMENT_LIST_WRAP_KEY = Key.create("ARGUMENT_LIST_WRAP_KEY"); private static final Key<Wrap> DART_TYPE_LIST_WRAP_KEY = Key.create("TYPE_LIST_WRAP_KEY"); private static final TokenSet NAMED_ARGUMENTS = TokenSet.create(NAMED_ARGUMENT); private final ASTNode myNode; private final CommonCodeStyleSettings mySettings; public DartWrappingProcessor(ASTNode node, CommonCodeStyleSettings settings) { myNode = node; mySettings = settings; } Wrap createChildWrap(ASTNode child, Wrap defaultWrap, Wrap childWrap) { final IElementType childType = child.getElementType(); final IElementType elementType = myNode.getElementType(); if (childType == COMMA || childType == SEMICOLON) return defaultWrap; // // Function definition/call // if (elementType == ARGUMENT_LIST) { if (mySettings.CALL_PARAMETERS_WRAP != CommonCodeStyleSettings.DO_NOT_WRAP) { if (!mySettings.PREFER_PARAMETERS_WRAP && childWrap != null) { // Not used; PREFER_PARAMETERS_WRAP cannot be changed in the UI. return Wrap.createChildWrap(childWrap, WrappingUtil.getWrapType(mySettings.CALL_PARAMETERS_WRAP), true); } Wrap wrap; // First, do persistent object management. if (myNode.getFirstChildNode() == child && childType != NAMED_ARGUMENT) { ASTNode[] childs = myNode.getChildren(DartIndentProcessor.EXPRESSIONS); if (childs.length >= 7) { // Approximation; dart_style uses dynamic programming with cost-based analysis to choose. wrap = Wrap.createWrap(WrapType.ALWAYS, true); } else { wrap = Wrap.createWrap(WrapType.NORMAL, true); // NORMAL,CHOP_DOWN_IF_LONG } if (myNode.getLastChildNode() != child) { myNode.putUserData(DART_ARGUMENT_LIST_WRAP_KEY, wrap); } } else { if (childType == NAMED_ARGUMENT) { ASTNode[] named = myNode.getChildren(NAMED_ARGUMENTS); wrap = myNode.getUserData(DART_ARGUMENT_LIST_WRAP_KEY); if (child == named[0]) { if (named.length > 1) { ASTNode[] childs = myNode.getChildren(DartIndentProcessor.EXPRESSIONS); Wrap namedWrap; if (childs.length >= 7 || named.length > 4) { // Another approximation. namedWrap = Wrap.createWrap(WrapType.ALWAYS, true); } else { namedWrap = Wrap.createWrap(WrapType.CHOP_DOWN_IF_LONG, true); } myNode.putUserData(DART_ARGUMENT_LIST_WRAP_KEY, namedWrap); } } } else { wrap = myNode.getUserData(DART_ARGUMENT_LIST_WRAP_KEY); } if (myNode.getLastChildNode() == child) { myNode.putUserData(DART_ARGUMENT_LIST_WRAP_KEY, null); } } // Second, decide what object to return. if (childType == MULTI_LINE_COMMENT || childType == FUNCTION_EXPRESSION) { return Wrap.createWrap(WrapType.NONE, false); } return wrap != null ? wrap : Wrap.createWrap(WrappingUtil.getWrapType(mySettings.CALL_PARAMETERS_WRAP), false); } } if (elementType == FORMAL_PARAMETER_LIST) { if (mySettings.METHOD_PARAMETERS_WRAP != CommonCodeStyleSettings.DO_NOT_WRAP) { if (myNode.getFirstChildNode() == child) { return createWrap(mySettings.METHOD_PARAMETERS_LPAREN_ON_NEXT_LINE); } if (childType == RPAREN) { return createWrap(mySettings.METHOD_PARAMETERS_RPAREN_ON_NEXT_LINE); } return Wrap.createWrap(WrappingUtil.getWrapType(mySettings.METHOD_PARAMETERS_WRAP), true); } } if (elementType == INITIALIZERS) { if (childType != COLON && isNotFirstInitializer(child)) { return Wrap.createWrap(WrapType.ALWAYS, true); } if (childType == COLON && !DartSpacingProcessor.hasMultipleInitializers(child)) { return Wrap.createWrap(WrapType.NORMAL, true); } } // Lists in schematic s-expr notation: // (LIST_LITERAL_EXPRESSION '[ (EXPRESSION_LIST expr ', expr) ']) if (elementType == EXPRESSION_LIST) { Wrap wrap; // First, do persistent object management. if (myNode.getFirstChildNode() == child) { wrap = Wrap.createWrap(WrapType.CHOP_DOWN_IF_LONG, true); if (myNode.getLastChildNode() != child) { myNode.putUserData(DART_EXPRESSION_LIST_WRAP_KEY, wrap); } } else { wrap = myNode.getUserData(DART_EXPRESSION_LIST_WRAP_KEY); } // Second, decide what object to return. if (childType == MULTI_LINE_COMMENT || childType == CONST) { return Wrap.createWrap(WrapType.NONE, false); } return wrap != null ? wrap : Wrap.createWrap(WrapType.NORMAL, true); } else if (elementType == LIST_LITERAL_EXPRESSION && childType == RBRACKET) { ASTNode exprList = FormatterUtil.getPreviousNonWhitespaceSibling(child); Wrap wrap = null; if (exprList != null && exprList.getElementType() == EXPRESSION_LIST) { wrap = exprList.getUserData(DART_EXPRESSION_LIST_WRAP_KEY); exprList.putUserData(DART_EXPRESSION_LIST_WRAP_KEY, null); } return wrap != null ? wrap : Wrap.createWrap(WrapType.NORMAL, true); } // Maps in schematic s-expr notation: // (MAP_LITERAL_EXPRESSION '{ (MAP_LITERAL_ENTRY expr ': expr) ', (MAP_LITERAL_ENTRY expr ': expr) '}) if (elementType == MAP_LITERAL_EXPRESSION) { // First, do persistent object management. Wrap wrap = sharedWrap(child, DART_EXPRESSION_LIST_WRAP_KEY); // Second, decide what object to return. if (childType == LBRACE || childType == LBRACKET) { return Wrap.createWrap(WrapType.NONE, false); } if (childType == MULTI_LINE_COMMENT || childType == CONST) { return Wrap.createWrap(WrapType.NONE, false); } return wrap != null ? wrap : Wrap.createWrap(WrapType.NORMAL, true); } // // Wrap after arrows. // if (elementType == FUNCTION_BODY) { if (FormatterUtil.isPrecededBy(child, EXPRESSION_BODY_DEF)) { return createWrap(true); } } if (childType == CALL_EXPRESSION) { if (FormatterUtil.isPrecededBy(child, EXPRESSION_BODY_DEF)) { return createWrap(true); } if (mySettings.CALL_PARAMETERS_WRAP != CommonCodeStyleSettings.DO_NOT_WRAP) { if (childType == RPAREN) { return createWrap(mySettings.CALL_PARAMETERS_RPAREN_ON_NEXT_LINE); } } } // // If // if (elementType == IF_STATEMENT) { if (childType == ELSE) { return createWrap(mySettings.ELSE_ON_NEW_LINE); } else if (!BLOCKS.contains(childType) && child == child.getTreeParent().getLastChildNode()) { return createWrap(true); } } // //Binary expressions // if (BINARY_EXPRESSIONS.contains(elementType) && mySettings.BINARY_OPERATION_WRAP != CommonCodeStyleSettings.DO_NOT_WRAP) { if ((mySettings.BINARY_OPERATION_SIGN_ON_NEXT_LINE && BINARY_OPERATORS.contains(childType)) || (!mySettings.BINARY_OPERATION_SIGN_ON_NEXT_LINE && isRightOperand(child))) { return Wrap.createWrap(WrappingUtil.getWrapType(mySettings.BINARY_OPERATION_WRAP), true); } } // // Assignment // if (elementType == ASSIGN_EXPRESSION && mySettings.ASSIGNMENT_WRAP != CommonCodeStyleSettings.DO_NOT_WRAP) { if (childType != ASSIGNMENT_OPERATOR) { if (FormatterUtil.isPrecededBy(child, ASSIGNMENT_OPERATOR) && mySettings.PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE) { return Wrap.createWrap(WrapType.NONE, true); } return Wrap.createWrap(WrappingUtil.getWrapType(mySettings.ASSIGNMENT_WRAP), true); } else if (mySettings.PLACE_ASSIGNMENT_SIGN_ON_NEXT_LINE) { return Wrap.createWrap(WrapType.NORMAL, true); } } // // Ternary expressions // if (elementType == TERNARY_EXPRESSION) { if (myNode.getFirstChildNode() != child) { if (mySettings.TERNARY_OPERATION_SIGNS_ON_NEXT_LINE) { if (childType == QUEST) { final Wrap wrap = Wrap.createWrap(WrappingUtil.getWrapType(mySettings.TERNARY_OPERATION_WRAP), true); myNode.putUserData(DART_TERNARY_EXPRESSION_WRAP_KEY, wrap); return wrap; } if (childType == COLON) { final Wrap wrap = myNode.getUserData(DART_TERNARY_EXPRESSION_WRAP_KEY); myNode.putUserData(DART_TERNARY_EXPRESSION_WRAP_KEY, null); return wrap != null ? wrap : Wrap.createWrap(WrappingUtil.getWrapType(mySettings.TERNARY_OPERATION_WRAP), true); } } else if (childType != QUEST && childType != COLON) { return Wrap.createWrap(WrappingUtil.getWrapType(mySettings.TERNARY_OPERATION_WRAP), true); } } return Wrap.createWrap(WrapType.NONE, true); } if (childType == HIDE_COMBINATOR || childType == SHOW_COMBINATOR) { return createWrap(true); } if (childType == VAR_DECLARATION_LIST && elementType != FOR_LOOP_PARTS) { if (varDeclListContainsVarInit(child)) { return Wrap.createWrap(WrapType.ALWAYS, true); } else { return Wrap.createWrap(WrapType.CHOP_DOWN_IF_LONG, true); } } if (childType == VAR_DECLARATION_LIST_PART) { ASTNode parent = getParent(); if (parent != null && parent.getElementType() == FOR_LOOP_PARTS) { return Wrap.createWrap(WrapType.NORMAL, true); } else { if (varDeclListContainsVarInit(myNode)) { return Wrap.createWrap(WrapType.ALWAYS, true); } else { return Wrap.createWrap(WrapType.CHOP_DOWN_IF_LONG, true); } } } if (elementType == CLASS_DEFINITION) { if (childType == SUPERCLASS || childType == INTERFACES || childType == MIXINS) { return Wrap.createWrap(WrapType.CHOP_DOWN_IF_LONG, true); } } if (elementType == MIXIN_APPLICATION && childType == MIXINS) { return Wrap.createWrap(WrapType.CHOP_DOWN_IF_LONG, true); } if (elementType == ENUM_DEFINITION) { if (childType == ENUM_CONSTANT_DECLARATION) { return Wrap.createWrap(WrapType.CHOP_DOWN_IF_LONG, true); } } if (elementType == TYPE_LIST) { if (childType == TYPE) { Wrap wrap = sharedWrap(child, DART_TYPE_LIST_WRAP_KEY); if (childType == MULTI_LINE_COMMENT) { return Wrap.createWrap(WrapType.NONE, false); } return wrap == null ? Wrap.createWrap(WrapType.NORMAL, true) : wrap; } } if (elementType == REFERENCE_EXPRESSION && (childType == DOT || childType == QUEST_DOT)) { return Wrap.createWrap(WrapType.NORMAL, true); // NORMAL,CHOP_DOWN_IF_LONG } return defaultWrap; } private boolean isRightOperand(ASTNode child) { return myNode.getLastChildNode() == child; } private ASTNode getParent() { return myNode.getTreeParent(); } private static Wrap createWrap(boolean isNormal) { return Wrap.createWrap(isNormal ? WrapType.NORMAL : WrapType.NONE, true); } //private static Wrap createChildWrap(ASTNode child, int parentWrap, boolean newLineAfterLBrace, boolean newLineBeforeRBrace) { // IElementType childType = child.getElementType(); // if (childType != LPAREN && childType != RPAREN) { // if (FormatterUtil.isPrecededBy(child, LBRACKET)) { // if (newLineAfterLBrace) { // return Wrap.createChildWrap(Wrap.createWrap(parentWrap, true), WrapType.ALWAYS, true); // } // else { // return Wrap.createWrap(WrapType.NONE, true); // } // } // return Wrap.createWrap(WrappingUtil.getWrapType(parentWrap), true); // } // if (childType == RBRACKET && newLineBeforeRBrace) { // return Wrap.createWrap(WrapType.ALWAYS, true); // } // return Wrap.createWrap(WrapType.NONE, true); //} private static boolean varDeclListContainsVarInit(ASTNode decl) { if (decl.findChildByType(VAR_INIT) != null) return true; ASTNode child = decl.getFirstChildNode(); while (child != null) { if (child.findChildByType(VAR_INIT) != null) return true; child = child.getTreeNext(); } return false; } private static boolean isNotFirstInitializer(ASTNode child) { ASTNode prev = child; boolean isFirst = false; while ((prev = prev.getTreePrev()) != null) { if (prev.getElementType() == COLON) { return isFirst; } if (prev.getElementType() != WHITE_SPACE && !COMMENTS.contains(prev.getElementType())) { isFirst = true; } } return isFirst; } private Wrap sharedWrap(ASTNode child, Key<Wrap> key) { Wrap wrap; if (myNode.getFirstChildNode() == child) { wrap = Wrap.createWrap(WrapType.CHOP_DOWN_IF_LONG, true); if (myNode.getLastChildNode() != child) { myNode.putUserData(key, wrap); } } else { wrap = myNode.getUserData(key); if (myNode.getLastChildNode() == child) { myNode.putUserData(key, null); } } return wrap; } }