Package com.dmarcotte.handlebars.format

Source Code of com.dmarcotte.handlebars.format.HbFormattingModelBuilder$HandlebarsBlock

package com.dmarcotte.handlebars.format;

import com.dmarcotte.handlebars.config.HbConfig;
import com.dmarcotte.handlebars.parsing.HbTokenTypes;
import com.dmarcotte.handlebars.psi.HbPsiUtil;
import com.intellij.formatting.*;
import com.intellij.formatting.templateLanguages.*;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiErrorElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.formatter.DocumentBasedFormattingModel;
import com.intellij.psi.formatter.FormattingDocumentModelImpl;
import com.intellij.psi.formatter.xml.HtmlPolicy;
import com.intellij.psi.formatter.xml.SyntheticBlock;
import com.intellij.psi.templateLanguages.SimpleTemplateLanguageFormattingModelBuilder;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.xml.XmlTag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;

/**
* Template aware formatter which provides formatting for Handlebars/Mustache syntax and delegates formatting
* for the templated language to that languages formatter
*/
public class HbFormattingModelBuilder extends TemplateLanguageFormattingModelBuilder {
  @Override
  public TemplateLanguageBlock createTemplateLanguageBlock(@NotNull ASTNode node,
                                                           @Nullable Wrap wrap,
                                                           @Nullable Alignment alignment,
                                                           @Nullable List<DataLanguageBlockWrapper> foreignChildren,
                                                           @NotNull CodeStyleSettings codeStyleSettings) {
    final FormattingDocumentModelImpl documentModel = FormattingDocumentModelImpl.createOn(node.getPsi().getContainingFile());
    return new HandlebarsBlock(this, codeStyleSettings, node, foreignChildren, new HtmlPolicy(codeStyleSettings, documentModel));
  }

  /**
   * We have to override {@link com.intellij.formatting.templateLanguages.TemplateLanguageFormattingModelBuilder#createModel}
   * since after we delegate to some templated languages, those languages (xml/html for sure, potentially others)
   * delegate right back to us to format the HbTokenTypes.OUTER_ELEMENT_TYPE token we tell them to ignore,
   * causing an stack-overflowing loop of polite format-delegation.
   */
  @NotNull
  public FormattingModel createModel(PsiElement element, CodeStyleSettings settings) {

    if (!HbConfig.isFormattingEnabled()) {
      // formatting is disabled, return the no-op formatter (note that this still delegates formatting
      // to the templated language, which lets the users manage that separately)
      return new SimpleTemplateLanguageFormattingModelBuilder().createModel(element, settings);
    }

    final PsiFile file = element.getContainingFile();
    Block rootBlock;

    ASTNode node = element.getNode();

    if (node.getElementType() == HbTokenTypes.OUTER_ELEMENT_TYPE) {
      // If we're looking at a HbTokenTypes.OUTER_ELEMENT_TYPE element, then we've been invoked by our templated
      // language.  Make a dummy block to allow that formatter to continue
      return new SimpleTemplateLanguageFormattingModelBuilder().createModel(element, settings);
    }
    else {
      rootBlock = getRootBlock(file, file.getViewProvider(), settings);
    }
    return new DocumentBasedFormattingModel(rootBlock, element.getProject(), settings, file.getFileType(), file);
  }

  /**
   * Do format my model!
   *
   * @return false all the time to tell the {@link com.intellij.formatting.templateLanguages.TemplateLanguageFormattingModelBuilder}
   *         to not-not format our model (i.e. yes please!  Format away!)
   */
  @Override
  public boolean dontFormatMyModel() {
    return false;
  }

  private static class HandlebarsBlock extends TemplateLanguageBlock {

    private HtmlPolicy myHtmlPolicy;

    HandlebarsBlock(@NotNull TemplateLanguageBlockFactory blockFactory, @NotNull CodeStyleSettings settings,
                    @NotNull ASTNode node, @Nullable List<DataLanguageBlockWrapper> foreignChildren, HtmlPolicy htmlPolicy) {
      super(blockFactory, settings, node, foreignChildren);
      myHtmlPolicy = htmlPolicy;
    }

    /**
     * We indented the code in the following manner, playing nice with the formatting from the language
     * we're templating:
     * <pre>
     *   * Block expressions:
     *      {{#foo}}
     *          INDENTED_CONTENT
     *      {{/foo}}
     *   * Inverse block expressions:
     *      {{^bar}}
     *          INDENTED_CONTENT
     *      {{/bar}}
     *   * Conditional expressions using the "else" syntax:
     *      {{#if test}}
     *          INDENTED_CONTENT
     *      {{else}}
     *          INDENTED_CONTENT
     *      {{/if}}
     *   * Conditional expressions using the "^" syntax:
     *      {{#if test}}
     *          INDENTED_CONTENT
     *      {{^}}
     *          INDENTED_CONTENT
     *      {{/if}}
     * </pre>
     * <p/>
     * This naturally maps to any "statements" expression in the grammar which is not a child of the
     * root "program" element.  See {@link com.dmarcotte.handlebars.parsing.HbParsing#parseProgram} and
     * {@link com.dmarcotte.handlebars.parsing.HbParsing#parseStatement(com.intellij.lang.PsiBuilder)} for the
     * relevant parts of the parser.
     * <p/>
     * To understand the approach in this method, consider the following:
     * <pre>
     * {{#foo}}
     * BEGIN_STATEMENTS
     * TEMPLATE_STUFF
     * END_STATEMENTS
     * {{/foo}}
     * </pre>
     * <p/>
     * then formatting looks easy. Simply apply an indent (represented here by "[hb_indent]") to the STATEMENTS and call it a day:
     * <pre>
     * {{#foo}}
     * [hb_indent]BEGIN_STATEMENTS
     * [hb_indent]TEMPLATE_STUFF
     * [hb_indent]END_STATEMENTS
     * {{/foo}}
     * </pre>
     * <p/>
     * However, if we're contained in templated language block, it's going to provide some indents of its own
     * (call them "[tl_indent]") which quickly leads to undesirable double-indenting:
     * <p/>
     * <pre>
     * &lt;div>
     * [tl_indent]{{#foo}}
     *            [hb_indent]BEGIN_STATEMENTS
     *            [tl_indent][hb_indent]TEMPLATE_STUFF
     *            [hb_indent]END_STATEMENTS
     * [tl_indent]{{/foo}}
     * &lt;/div>
     * </pre>
     * So to behave correctly in both situations, we indent STATEMENTS from the "outside" anytime we're not wrapped
     * in a templated language block, and we indent STATEMENTS from the "inside" (i.e. apply an indent to each non-template
     * language STATEMENT inside the STATEMENTS) to interleave nicely with templated-language provided indents.
     */
    @Override
    public Indent getIndent() {
      // ignore whitespace
      if (myNode.getText().trim().length() == 0) {
        return Indent.getNoneIndent();
      }

      if (HbPsiUtil.isNonRootStatementsElement(myNode.getPsi())) {
        // we're computing the indent for a non-root STATEMENTS:
        //      if it's not contained in a foreign block, indent!
        DataLanguageBlockWrapper foreignBlockParent = getForeignBlockParent(false);
        if (foreignBlockParent == null) {
          return Indent.getNormalIndent();
        }

        // otherwise, only indent if our foreign parent isn't indenting us
        if (foreignBlockParent.getNode() instanceof XmlTag) {
          XmlTag xmlTag = (XmlTag) foreignBlockParent.getNode();
          if (!myHtmlPolicy.indentChildrenOf(xmlTag)) {
            // no indent from xml parent, add our own
            return Indent.getNormalIndent();
          }
        }

        return Indent.getNoneIndent();
      }

      if (myNode.getTreeParent() != null
          && HbPsiUtil.isNonRootStatementsElement(myNode.getTreeParent().getPsi())) {
        // we're computing the indent for a direct descendant of a non-root STATEMENTS:
        //      if its Block parent (i.e. not HB AST Tree parent) is a Handlebars block
        //      which has NOT been indented, then have the element provide the indent itself
        if (getParent() instanceof HandlebarsBlock
            && ((HandlebarsBlock)getParent()).getIndent() == Indent.getNoneIndent()) {
          return Indent.getNormalIndent();
        }
      }

      // any element that is the direct descendant of a foreign block gets an indent
      // (unless that foreign element has been configured to not indent its children)
      DataLanguageBlockWrapper foreignParent = getForeignBlockParent(true);
      if (foreignParent != null) {
        if (foreignParent.getNode() instanceof XmlTag
            && !myHtmlPolicy.indentChildrenOf((XmlTag) foreignParent.getNode())) {
          return Indent.getNoneIndent();
        }
        return Indent.getNormalIndent();
      }

      return Indent.getNoneIndent();
    }

    /**
     * TODO implement alignment for "stacked" mustache content.  i.e.:
     * {{foo bar="baz"
     * bat="bam"}} <- note the alignment here
     */
    @Override
    public Alignment getAlignment() {
      return null;
    }

    @Override
    protected IElementType getTemplateTextElementType() {
      // we ignore CONTENT tokens since they get formatted by the templated language
      return HbTokenTypes.CONTENT;
    }

    @Override
    public boolean isRequiredRange(TextRange range) {
      // seems our approach doesn't require us to insert any custom DataLanguageBlockFragmentWrapper blocks
      return false;
    }

    /**
     * TODO if/when we implement alignment, update this method to do alignment properly
     * <p/>
     * This method handles indent and alignment on Enter.
     */
    @NotNull
    @Override
    public ChildAttributes getChildAttributes(int newChildIndex) {
      /**
       * We indent if we're in a BLOCK_WRAPPER (note that this works nicely since Enter can only be invoked
       * INSIDE a block (i.e. after the open block 'stache).
       *
       * Also indent if we are wrapped in a block created by the templated language
       */
      if (myNode.getElementType() == HbTokenTypes.BLOCK_WRAPPER
          || (getParent() instanceof DataLanguageBlockWrapper
              // hack alert: the following check opportunistically fixes com.dmarcotte.handlebars.format.HbFormatOnEnterTest#testSimpleBlockInDiv8
              //      and com.dmarcotte.handlebars.format.HbFormatOnEnterTest#testSimpleBlockInDiv8
              //      but isn't really based on solid logic (why do these checks work?), so when there's inevitably a
              //      format-on-enter bug, this is the first bit of code to be suspicious of
              &&
              (myNode.getElementType() != HbTokenTypes.STATEMENTS || myNode.getTreeNext() instanceof PsiErrorElement))) {
        return new ChildAttributes(Indent.getNormalIndent(), null);
      }
      else {
        return new ChildAttributes(Indent.getNoneIndent(), null);
      }
    }

    /**
     * Returns this block's first "real" foreign block parent if it exists, and null otherwise.  (By "real" here, we mean that this method
     * skips SyntheticBlock blocks inserted by the template formatter)
     *
     * @param immediate Pass true to only check for an immediate foreign parent, false to look up the hierarchy.
     */
    private DataLanguageBlockWrapper getForeignBlockParent(boolean immediate) {
      DataLanguageBlockWrapper foreignBlockParent = null;
      BlockWithParent parent = getParent();

      while (parent != null) {
        if (parent instanceof DataLanguageBlockWrapper && !(((DataLanguageBlockWrapper)parent).getOriginal() instanceof SyntheticBlock)) {
          foreignBlockParent = (DataLanguageBlockWrapper) parent;
          break;
        } else if (immediate && parent instanceof HandlebarsBlock) {
          break;
        }
        parent = parent.getParent();
      }

      return foreignBlockParent;
    }
  }
}
TOP

Related Classes of com.dmarcotte.handlebars.format.HbFormattingModelBuilder$HandlebarsBlock

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.