Skip to content

Instantly share code, notes, and snippets.

@dmarcotte
Created November 8, 2012 16:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dmarcotte/4039840 to your computer and use it in GitHub Desktop.
Save dmarcotte/4039840 to your computer and use it in GitHub Desktop.
Example of a standalone IDEA formatter for a templating language
package com.dmarcotte.handlebars.format;
import com.dmarcotte.handlebars.HbLanguage;
import com.dmarcotte.handlebars.parsing.HbTokenTypes;
import com.intellij.formatting.Alignment;
import com.intellij.formatting.Block;
import com.intellij.formatting.ChildAttributes;
import com.intellij.formatting.FormattingModel;
import com.intellij.formatting.FormattingModelBuilder;
import com.intellij.formatting.FormattingModelProvider;
import com.intellij.formatting.Indent;
import com.intellij.formatting.Spacing;
import com.intellij.formatting.Wrap;
import com.intellij.lang.ASTNode;
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.formatter.common.AbstractBlock;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A formatter for Handlebars/Mustache files which ignores the templated content, only formatting the 'stache-y parts.
* This is what gets evolved into a full template-aware formatter
*/
public class HbStandaloneFormattingBuilder implements FormattingModelBuilder {
@NotNull
@Override
public FormattingModel createModel(PsiElement element, CodeStyleSettings settings) {
PsiFile containingFile = element.getContainingFile().getViewProvider().getPsi(HbLanguage.INSTANCE);
return FormattingModelProvider
.createFormattingModelForPsiFile(
containingFile,
new HandlebarsBlock(element.getNode(), settings),
settings);
}
@Override
public TextRange getRangeAffectingIndent(PsiFile file, int offset, ASTNode elementAtOffset) {
return null; // todo this doens't seem to be needed... must take care of a special case. Look into it.
}
private static class HandlebarsBlock extends AbstractBlock {
ASTNode myNode;
CodeStyleSettings myStyleSettings; // todo consult these settings for the user's preferred formatting
private HandlebarsBlock(ASTNode node, CodeStyleSettings styleSettings) {
super(node, null, null);
this.myNode = node;
this.myStyleSettings = styleSettings;
}
/**
* Instantiates blocks for each of this node's children.
*
* Seems it's fairly common to do indent and alignment calculation in this method and pass those values
* to the Block constructor (which makes sense, since your indent or alignment could very well be determined by
* your siblings as much as your parent).
*
* For this simple formatter, where we can fully determine indent for a given node, we simple iterate over
* the children creating our blocks without any other computations (note that we skip whitespace; the
* formatter behaves REALLY weird if you don't...)
*/
@Override
protected List<Block> buildChildren() {
if (isLeaf()) {
return Collections.emptyList();
}
ArrayList<Block> childBlocks = new ArrayList<Block>();
for (ASTNode childNode : myNode.getChildren(null)) {
if (childNode.getText().trim().length() == 0) {
continue;
}
childBlocks.add(new HandlebarsBlock(childNode, myStyleSettings));
}
return childBlocks;
}
/**
* This method is supposed to return the range covered by this Formatting Block. Since our blocks
* map so well to our PSI tree, there's no need to do anything special here.
*/
@NotNull
@Override
public TextRange getTextRange() {
return myNode.getTextRange();
}
@Override
public Wrap getWrap() {
return null; // todo implement some wrappin'
}
/**
* We indented the code in the following manner:
* * block expressions:
* {{#foo}}
* INDENTED_CONTENET
* {{/foo}}
* * inverse block expressions:
* {{^bar}}
* INDENTED_CONTENT
* {{/bar}}
* * conditional expressions use the "else" syntax:
* {{#if test}}
* INDENTED_CONTENT
* {{else}}
* INDENTED_CONTENT
* {{/if}}
* * conditional expressions use the "^" syntax:
*
* {{#if test}}
* INDENTED_CONTENT
* {{^}}
* INDENTED_CONTENT
* {{/if}}
*
* 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.
*/
@Override
public Indent getIndent() {
if (myNode.getText().trim().length() == 0) {
return null;
}
Indent indent;
// we indent if this element corresponds to a non-root "statements" expression
if (myNode.getElementType() == HbTokenTypes.STATEMENTS
&& myNode.getTreeParent().getElementType() != HbTokenTypes.FILE) {
indent = Indent.getNormalIndent(); // todo should we pass true for relativeToDirectParent?
} else {
indent = Indent.getNoneIndent();
}
return indent;
}
@Override
public Alignment getAlignment() {
return null; // todo implement some alignin'
}
@Override
public Spacing getSpacing(Block child1, Block child2) {
return null; // todo implement some spacin'
}
/**
* This method handles indent and alignment on Enter keypresses.
*
* This method is called on the parent block of the new block which is being created on
* keypress, and 'newChildIndex' seems to be the index where the new block will be inserted in
* the list of that parent's sub blocks
*
* For this naive implementation, it is sufficient to simply return a ChildAttributes which contains
* the parent block's indent.
* TODO if/when we implement alignment, update this method to do alignment properly
*/
@NotNull
@Override
public ChildAttributes getChildAttributes(int newChildIndex) {
return new ChildAttributes(getIndent(), null);
}
@Override
public boolean isIncomplete() {
return false; // todo dunno what this means...
}
@Override
public boolean isLeaf() {
return myNode.getFirstChildNode() == null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment