From 1532a1053a30eb2998af262e0cb979e71a4ed16d Mon Sep 17 00:00:00 2001 From: Sergey Ignatov Date: Sat, 19 Apr 2014 22:38:20 +0400 Subject: [PATCH 1/2] generated formatter initial --- .../messages/attributeDescriptions/after.html | 5 + .../attributeDescriptions/alignment.html | 5 + .../attributeDescriptions/around.html | 5 + .../attributeDescriptions/before.html | 5 + .../continuationIndent.html | 5 + .../formattingBuilderClass.html | 5 + .../attributeDescriptions/noneIndent.html | 5 + .../attributeDescriptions/normalIndent.html | 5 + .../org/intellij/grammar/KnownAttribute.java | 12 + .../grammar/generator/ParserGenerator.java | 236 +++++++++++++++++- .../generator/Self.FORMATTER.expected.java | 126 ++++++++++ testData/generator/Self.bnf | 16 ++ .../intellij/grammar/BnfGeneratorTest.java | 9 +- 13 files changed, 437 insertions(+), 2 deletions(-) create mode 100644 support/messages/attributeDescriptions/after.html create mode 100644 support/messages/attributeDescriptions/alignment.html create mode 100644 support/messages/attributeDescriptions/around.html create mode 100644 support/messages/attributeDescriptions/before.html create mode 100644 support/messages/attributeDescriptions/continuationIndent.html create mode 100644 support/messages/attributeDescriptions/formattingBuilderClass.html create mode 100644 support/messages/attributeDescriptions/noneIndent.html create mode 100644 support/messages/attributeDescriptions/normalIndent.html create mode 100644 testData/generator/Self.FORMATTER.expected.java diff --git a/support/messages/attributeDescriptions/after.html b/support/messages/attributeDescriptions/after.html new file mode 100644 index 00000000..1f2b7349 --- /dev/null +++ b/support/messages/attributeDescriptions/after.html @@ -0,0 +1,5 @@ + + +Represents after rule in SpacingBuilder. + + diff --git a/support/messages/attributeDescriptions/alignment.html b/support/messages/attributeDescriptions/alignment.html new file mode 100644 index 00000000..dd5c4fe6 --- /dev/null +++ b/support/messages/attributeDescriptions/alignment.html @@ -0,0 +1,5 @@ + + +Allows to control formatter alignment for children nodes. + + diff --git a/support/messages/attributeDescriptions/around.html b/support/messages/attributeDescriptions/around.html new file mode 100644 index 00000000..720e7739 --- /dev/null +++ b/support/messages/attributeDescriptions/around.html @@ -0,0 +1,5 @@ + + +Represents around rule in SpacingBuilder. + + diff --git a/support/messages/attributeDescriptions/before.html b/support/messages/attributeDescriptions/before.html new file mode 100644 index 00000000..cc1b8bf1 --- /dev/null +++ b/support/messages/attributeDescriptions/before.html @@ -0,0 +1,5 @@ + + +Represents before rule in SpacingBuilder. + + diff --git a/support/messages/attributeDescriptions/continuationIndent.html b/support/messages/attributeDescriptions/continuationIndent.html new file mode 100644 index 00000000..33c6d3e2 --- /dev/null +++ b/support/messages/attributeDescriptions/continuationIndent.html @@ -0,0 +1,5 @@ + + +Allows to control formatter continuation indent for children nodes. + + diff --git a/support/messages/attributeDescriptions/formattingBuilderClass.html b/support/messages/attributeDescriptions/formattingBuilderClass.html new file mode 100644 index 00000000..42c8357e --- /dev/null +++ b/support/messages/attributeDescriptions/formattingBuilderClass.html @@ -0,0 +1,5 @@ + + +FormattingModelBuilder class qualified name. + + diff --git a/support/messages/attributeDescriptions/noneIndent.html b/support/messages/attributeDescriptions/noneIndent.html new file mode 100644 index 00000000..28091df9 --- /dev/null +++ b/support/messages/attributeDescriptions/noneIndent.html @@ -0,0 +1,5 @@ + + +Allows to control formatter none indent for children nodes. + + diff --git a/support/messages/attributeDescriptions/normalIndent.html b/support/messages/attributeDescriptions/normalIndent.html new file mode 100644 index 00000000..e6445782 --- /dev/null +++ b/support/messages/attributeDescriptions/normalIndent.html @@ -0,0 +1,5 @@ + + +Allows to control formatter normal indent for children nodes. + + diff --git a/support/org/intellij/grammar/KnownAttribute.java b/support/org/intellij/grammar/KnownAttribute.java index 5144e66d..250fb28a 100644 --- a/support/org/intellij/grammar/KnownAttribute.java +++ b/support/org/intellij/grammar/KnownAttribute.java @@ -71,6 +71,18 @@ public class KnownAttribute { public static final KnownAttribute RECOVER_WHILE = create(false, String.class, "recoverWhile", null); public static final KnownAttribute NAME = create(false, String.class, "name", null); + public static final KnownAttribute FORMATTING_BUILDER_CLASS = create(true, String.class, "formattingBuilderClass", null); + + public static final KnownAttribute AROUND = create(false, String.class, "around", null); + public static final KnownAttribute BEFORE = create(false, String.class, "before", null); + public static final KnownAttribute AFTER = create(false, String.class, "after", null); + + public static final KnownAttribute CONTINUATION_INDENT = create(false, String.class, "continuationIndent", null); + public static final KnownAttribute NONE_INDENT = create(false, String.class, "noneIndent", null); + public static final KnownAttribute NORMAL_INDENT = create(false, String.class, "normalIndent", null); + + public static final KnownAttribute ALIGNMENT = create(false, String.class, "alignment", null); + public static final KnownAttribute RIGHT_ASSOCIATIVE = create(false, Boolean.class, "rightAssociative", false); public static final KnownAttribute CONSUME_TOKEN_METHOD = create(false, String.class, "consumeTokenMethod", "consumeToken"); diff --git a/support/org/intellij/grammar/generator/ParserGenerator.java b/support/org/intellij/grammar/generator/ParserGenerator.java index 49006552..2c2827ae 100755 --- a/support/org/intellij/grammar/generator/ParserGenerator.java +++ b/support/org/intellij/grammar/generator/ParserGenerator.java @@ -115,7 +115,7 @@ private void openOutput(File file) throws FileNotFoundException { String grammarName = FileUtil.getNameWithoutExtension(myFile.getName()); String fileName = FileUtil.getNameWithoutExtension(file); if (myUnitTestMode) { - String name = grammarName + (fileName.startsWith(grammarName) || fileName.endsWith("Parser") ? "" : ".PSI") + ".java"; + String name = grammarName + (fileName.endsWith("FormattingModelBuilder") ? ".FORMATTER" : (fileName.startsWith(grammarName) || fileName.endsWith("Parser") ? "" : ".PSI")) + ".java"; myOut = new PrintWriter(new FileOutputStream(new File(myOutputPath, name), true)); out("// ---- " + file.getName() + " -----------------"); } @@ -236,6 +236,10 @@ public void generate() throws IOException { } } } + String formattingBuilderClass = getRootAttribute(myFile, KnownAttribute.FORMATTING_BUILDER_CLASS); + if (formattingBuilderClass != null) { + generateFormatter(); + } } private void generateVisitor(String psiClass, Map sortedRules) { @@ -1249,6 +1253,236 @@ private void generateElementTypesHolder(String className, Map s out("}"); } + private void generateFormatter() throws IOException { + String formattingBuilderClass = getRootAttribute(myFile, KnownAttribute.FORMATTING_BUILDER_CLASS); + if (formattingBuilderClass == null) return; + final String formatterPackage = StringUtil.getPackageName(formattingBuilderClass); + String shortName = StringUtil.getShortName(formattingBuilderClass); + String elementTypeHolder = getRootAttribute(myFile, KnownAttribute.ELEMENT_TYPE_HOLDER_CLASS); + String[] split = shortName.split("(?<=[a-z])(?=[A-Z])"); // split by camel humps + String blockClass = (split.length > 0 ? split[0] : "My") + "FormattingBlock"; + + File formattingFile = new File(myOutputPath, formattingBuilderClass.replace('.', File.separatorChar) + ".java"); + openOutput(formattingFile); + try { + List imports = ContainerUtil.list( + "com.intellij.formatting.*", + "com.intellij.psi.formatter.common.AbstractBlock", + "com.intellij.lang.ASTNode", + "com.intellij.openapi.util.TextRange", + "com.intellij.psi.PsiElement", + "com.intellij.psi.PsiFile", + "com.intellij.psi.codeStyle.CodeStyleSettings", + "org.jetbrains.annotations.NotNull", + "org.jetbrains.annotations.Nullable", + "com.intellij.psi.formatter.FormatterUtil", + "com.intellij.psi.tree.IElementType", + + "java.util.List", + "java.util.ArrayList" + ); + + out("package " + formatterPackage + ";"); + newLine(); + for (String i : imports) { + out("import " + i + ";"); + } + out("import static " + elementTypeHolder + ".*;"); + + newLine(); + + out("public class " + shortName + " implements FormattingModelBuilder {"); + + out("@NotNull"); + out("@Override"); + out("public FormattingModel createModel(PsiElement element, CodeStyleSettings settings) {"); + out(blockClass + " block = new " + blockClass + "(element.getNode(), null, null, settings);"); + out("return FormattingModelProvider.createFormattingModelForPsiFile(element.getContainingFile(), block, settings);"); + out("}"); + newLine(); + + out("@Override"); + out("public TextRange getRangeAffectingIndent(PsiFile psiFile, int i, ASTNode astNode) {"); + out("return null;"); + out("}"); + newLine(); + + out("public static class " + blockClass + " extends AbstractBlock {"); + out("private final CodeStyleSettings mySettings;"); + out("public " + blockClass + "(@NotNull ASTNode node, @Nullable Wrap wrap, @Nullable Alignment alignment, CodeStyleSettings settings) {"); + out("super(node, wrap, alignment);"); + out("mySettings = settings;"); + out("}"); + newLine(); + + out("@Override"); + out("protected List buildChildren() {"); + out("if (isLeaf()) { return EMPTY; }"); + out("ArrayList children = new ArrayList();"); + out("Alignment baseAlignment = Alignment.createAlignment();"); + out("for (ASTNode childNode = getNode().getFirstChildNode(); childNode != null; childNode = childNode.getTreeNext()) {"); + out("if (FormatterUtil.containsWhiteSpacesOnly(childNode)) continue;"); + out("Block childBlock = new " + blockClass + "(childNode, createChildWrap(childNode), createChildAlignment(baseAlignment, childNode), mySettings);"); + out("children.add(childBlock);"); + out("}"); + out("return children;"); + out("}"); + newLine(); + + out("public Wrap createChildWrap(ASTNode child) {"); + out("return null;"); + out("}"); + newLine(); + + out("public Alignment createChildAlignment(Alignment baseAlignment, ASTNode child) {"); + out("IElementType elementType = child.getElementType();"); + out("ASTNode parent = child.getTreeParent();"); + out("IElementType parentType = parent != null ? parent.getElementType() : null;"); + + for (BnfRule rule : myFile.getRules()) { + if (Rule.isPrivate(rule)) continue; + String elementType = getElementType(rule); + if (elementType.isEmpty()) continue; + + String value = getAttribute(rule, KnownAttribute.ALIGNMENT); + if (value != null) { + boolean all = value.equals(".*"); + if (all) { + out("if (parentType == " + elementType + ") {"); + out("return baseAlignment" + ";"); + out("}"); + } + else if (value.startsWith("except:")) { + value = value.replaceFirst("except:", ""); + List elementTypes = findAllElementTypes(value); + String join = elementTypes.size() > 0 ? (" && elementType != " + StringUtil.join(elementTypes, " && elementType != ")) : ""; + out("if (parentType == " + elementType + join + ") {"); + out("return baseAlignment" + ";"); + out("}"); + } + else { + for (String type : findAllElementTypes(value)) { + out("if (parentType == " + elementType + " && elementType == " + type + ") {"); + out("return baseAlignment" + ";"); + out("}"); + } + } + } + } + out("return null;"); + out("}"); + newLine(); + + out("@Override"); + out("public Indent getIndent() {"); + out("ASTNode node = getNode();"); + out("IElementType elementType = node.getElementType();"); + out("ASTNode parent = node.getTreeParent();"); + out("IElementType parentType = parent != null ? parent.getElementType() : null;"); + + //noinspection unchecked + List> indentAttributes = ContainerUtil.list(KnownAttribute.CONTINUATION_INDENT, KnownAttribute.NONE_INDENT, KnownAttribute.NORMAL_INDENT); + + // todo: where is tokens? + for (BnfRule rule : myFile.getRules()) { + if (Rule.isPrivate(rule)) continue; + String elementType = getElementType(rule); + if (elementType.isEmpty()) continue; + + for (KnownAttribute attribute : indentAttributes) { + String value = getAttribute(rule, attribute); + if (value != null) { + boolean all = value.equals(".*"); + if (all) { + out("if (parentType == " + elementType + ") {"); + out("return Indent.get" + StringUtil.capitalize(attribute.getName()) + "();"); + out("}"); + } + else { + for (String type : findAllElementTypes(value)) { + out("if (parentType == " + elementType + " && elementType == " + type + ") {"); + out("return Indent.get" + StringUtil.capitalize(attribute.getName()) + "();"); + out("}"); + } + } + } + } + } + + out("return Indent.getNoneIndent();"); + out("}"); + newLine(); + + out("@Nullable"); + out("@Override"); + out("public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {"); + out("SpacingBuilder spacingBuilder = new SpacingBuilder(mySettings)"); + + //noinspection unchecked + List> spacingAttributes = ContainerUtil.list(KnownAttribute.AROUND, KnownAttribute.BEFORE, KnownAttribute.AFTER); + + for (Map.Entry entry : mySimpleTokens.entrySet()) { + String entryValue = entry.getValue(); + if (entryValue == null) { + continue; + } + String elementType = getRootAttribute(myFile, KnownAttribute.ELEMENT_TYPE_PREFIX) + entryValue.toUpperCase(); + for (KnownAttribute attribute : spacingAttributes) { + String value = getRootAttribute(myFile, attribute, entry.getKey()); + if (value != null) { + out("." + attribute +"(" + elementType + ")." + value); + } + } + } + + for (BnfRule rule : myFile.getRules()) { + String elementType = getElementType(rule); + if (elementType.isEmpty()) continue; + for (KnownAttribute attribute : spacingAttributes) { + String value = getAttribute(rule, attribute); + if (value != null) { + out("." + attribute +"(" + elementType + ")." + value); + } + } + } + + out(";"); + out("return spacingBuilder.getSpacing(this, child1, child2);"); + out("}"); + newLine(); + + out("@Override"); + out("public boolean isLeaf() {"); + out("return false;"); + out("}"); + + out("}"); + out("}"); + } + finally { + closeOutput(); + } + } + + private List findAllElementTypes(String value) { + List elementTypes = new ArrayList(); + for (BnfRule childRule : myFile.getRules()) { + if (childRule.getName().matches(value)) { + String childElementType = getElementType(childRule); + if (childElementType == null) continue; + elementTypes.add(childElementType); + } + } + String tokenPrefix = getRootAttribute(myFile, KnownAttribute.ELEMENT_TYPE_PREFIX); + for (Map.Entry entry : mySimpleTokens.entrySet()) { + String childElementType = entry.getValue(); + if (entry.getKey().matches(value) && childElementType != null) { + childElementType = tokenPrefix + childElementType.toUpperCase(); + elementTypes.add(childElementType); + } + } + return elementTypes; + } /*PSI******************************************************************/ private void generatePsiIntf(BnfRule rule, diff --git a/testData/generator/Self.FORMATTER.expected.java b/testData/generator/Self.FORMATTER.expected.java new file mode 100644 index 00000000..bcdc1e4f --- /dev/null +++ b/testData/generator/Self.FORMATTER.expected.java @@ -0,0 +1,126 @@ +// ---- BnfFormattingModelBuilder.java ----------------- +package org.intellij.grammar; + +import com.intellij.formatting.*; +import com.intellij.psi.formatter.common.AbstractBlock; +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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import com.intellij.psi.formatter.FormatterUtil; +import com.intellij.psi.tree.IElementType; +import java.util.List; +import java.util.ArrayList; +import static org.intellij.grammar.psi.BnfTypes.*; + +public class BnfFormattingModelBuilder implements FormattingModelBuilder { + @NotNull + @Override + public FormattingModel createModel(PsiElement element, CodeStyleSettings settings) { + BnfFormattingBlock block = new BnfFormattingBlock(element.getNode(), null, null, settings); + return FormattingModelProvider.createFormattingModelForPsiFile(element.getContainingFile(), block, settings); + } + + @Override + public TextRange getRangeAffectingIndent(PsiFile psiFile, int i, ASTNode astNode) { + return null; + } + + public static class BnfFormattingBlock extends AbstractBlock { + private final CodeStyleSettings mySettings; + public BnfFormattingBlock(@NotNull ASTNode node, @Nullable Wrap wrap, @Nullable Alignment alignment, CodeStyleSettings settings) { + super(node, wrap, alignment); + mySettings = settings; + } + + @Override + protected List buildChildren() { + if (isLeaf()) { return EMPTY; } + ArrayList children = new ArrayList(); + Alignment baseAlignment = Alignment.createAlignment(); + for (ASTNode childNode = getNode().getFirstChildNode(); childNode != null; childNode = childNode.getTreeNext()) { + if (FormatterUtil.containsWhiteSpacesOnly(childNode)) continue; + Block childBlock = new BnfFormattingBlock(childNode, createChildWrap(childNode), createChildAlignment(baseAlignment, childNode), mySettings); + children.add(childBlock); + } + return children; + } + + public Wrap createChildWrap(ASTNode child) { + return null; + } + + public Alignment createChildAlignment(Alignment baseAlignment, ASTNode child) { + IElementType elementType = child.getElementType(); + ASTNode parent = child.getTreeParent(); + IElementType parentType = parent != null ? parent.getElementType() : null; + if (parentType == BNF_ATTRS && elementType != BNF_RIGHT_BRACE && elementType != BNF_LEFT_BRACE) { + return baseAlignment; + } + if (parentType == BNF_CHOICE) { + return baseAlignment; + } + if (parentType == BNF_PAREN_EXPRESSION && elementType != BNF_RIGHT_BRACE && elementType != BNF_LEFT_BRACE) { + return baseAlignment; + } + return null; + } + + @Override + public Indent getIndent() { + ASTNode node = getNode(); + IElementType elementType = node.getElementType(); + ASTNode parent = node.getTreeParent(); + IElementType parentType = parent != null ? parent.getElementType() : null; + if (parentType == BNF_ATTRS && elementType == BNF_ATTR) { + return Indent.getNormalIndent(); + } + if (parentType == BNF_CHOICE) { + return Indent.getContinuationIndent(); + } + if (parentType == BNF_PAREN_EXPRESSION) { + return Indent.getContinuationIndent(); + } + return Indent.getNoneIndent(); + } + + @Nullable + @Override + public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) { + SpacingBuilder spacingBuilder = new SpacingBuilder(mySettings) + .around(BNF_OP_EQ).none() + .before(BNF_SEMICOLON).none() + .after(BNF_LEFT_PAREN).none() + .before(BNF_RIGHT_PAREN).none() + .before(BNF_RIGHT_BRACE).none() + .after(BNF_LEFT_BRACE).none() + .after(BNF_LEFT_BRACKET).none() + .around(BNF_OP_OR).spaces(1) + .before(BNF_OP_ZEROMORE).none() + .before(BNF_OP_ONEMORE).none() + .around(BNF_OP_IS).spaces(1) + .around(BNF_RULE).none() + .around(BNF_EXPRESSION).spaces(1) + .after(BNF_MODIFIER).spaces(1) + .before(BNF_ATTRS).spaces(1) + .after(BNF_ATTR).spaces(1) + .after(BNF_PREDICATE_SIGN).none() + .after(BNF_QUANTIFIED).spaces(1) + .before(BNF_QUANTIFIER).none() + .around(BNF_REFERENCE_OR_TOKEN).spaces(1) + .around(BNF_LITERAL_EXPRESSION).spaces(1) + .around(BNF_STRING_LITERAL_EXPRESSION).spaces(1) + .around(BNF_PAREN_EXPRESSION).spaces(1) + ; + return spacingBuilder.getSpacing(this, child1, child2); + } + + @Override + public boolean isLeaf() { + return false; + } + } +} \ No newline at end of file diff --git a/testData/generator/Self.bnf b/testData/generator/Self.bnf index 3d7d658e..eeb29e5c 100644 --- a/testData/generator/Self.bnf +++ b/testData/generator/Self.bnf @@ -3,6 +3,7 @@ extendedPin=false name(".*")="" stubParserClass="org.intellij.grammar.parser.GeneratedParserUtilBase" + formattingBuilderClass="org.intellij.grammar.BnfFormattingModelBuilder" implements="org.intellij.grammar.psi.BnfCompositeElement" extends="org.intellij.grammar.psi.impl.BnfCompositeElementImpl" @@ -40,6 +41,21 @@ implements("rule|attr")="org.intellij.grammar.psi.BnfNamedElement" extends("rule|attr")="org.intellij.grammar.psi.impl.BnfNamedElementImpl" + around("::=|\|")="spaces(1)" + around("=|rule")="none()" + before("\*|\+|\)|\;|\}")="none()" + after("\(|\[|\{|predicate_sign")="none()" + after("quantified|attr|modifier")="spaces(1)" + before("quantifier")="none()" + before("attrs")="spaces(1)" + around(".*expression|reference_or_token")="spaces(1)" + + normalIndent("attrs")="attr" + continuationIndent("paren_expression")=".*" + continuationIndent("choice")=".*" + + alignment("choice")=".*" + alignment("attrs|paren_expression")="except:\{|\}" } grammar ::= (attrs | rule) * diff --git a/tests/org/intellij/grammar/BnfGeneratorTest.java b/tests/org/intellij/grammar/BnfGeneratorTest.java index cb7cfb80..53a9663e 100644 --- a/tests/org/intellij/grammar/BnfGeneratorTest.java +++ b/tests/org/intellij/grammar/BnfGeneratorTest.java @@ -24,7 +24,7 @@ public BnfGeneratorTest() { } public void testBnfGrammar() throws Exception { doGenTest(true); } - public void testSelf() throws Exception { doGenTest(true); } + public void testSelf() throws Exception { doGenTest(true, true); } public void testSmall() throws Exception { doGenTest(false); } public void testAutopin() throws Exception { doGenTest(false); } public void testExternalRules() throws Exception { doGenTest(false); } @@ -51,6 +51,10 @@ protected String loadFile(@NonNls String name) throws IOException { } public void doGenTest(final boolean generatePsi) throws Exception { + doGenTest(generatePsi, false); + } + + public void doGenTest(final boolean generatePsi, boolean generateFormatter) throws Exception { final String name = getTestName(false); String text = loadFile(name + "." + myFileExt); myFile = createPsiFile(name, text.replaceAll("generatePsi=[^\n]*", "generatePsi=" + generatePsi)); @@ -59,6 +63,9 @@ public void doGenTest(final boolean generatePsi) throws Exception { if (generatePsi) { filesToCheck.add(new File(myFullDataPath, name + ".PSI.java")); } + if (generateFormatter) { + filesToCheck.add(new File(myFullDataPath, name + ".FORMATTER.java")); + } for (File file : filesToCheck) { if (file.exists()) { assertTrue(file.delete()); From f059faf78d75686dde661d0cc78a3f74da426cba Mon Sep 17 00:00:00 2001 From: Sergey Ignatov Date: Sat, 19 Apr 2014 23:00:01 +0400 Subject: [PATCH 2/2] apply generated formatter --- .../formatter/BnfFormattingModelBuilder.java | 129 ++++++++++++++++++ grammars/Grammar.bnf | 17 +++ support/META-INF/plugin.xml | 2 + 3 files changed, 148 insertions(+) create mode 100644 gen/org/intellij/grammar/formatter/BnfFormattingModelBuilder.java diff --git a/gen/org/intellij/grammar/formatter/BnfFormattingModelBuilder.java b/gen/org/intellij/grammar/formatter/BnfFormattingModelBuilder.java new file mode 100644 index 00000000..6c06f545 --- /dev/null +++ b/gen/org/intellij/grammar/formatter/BnfFormattingModelBuilder.java @@ -0,0 +1,129 @@ +package org.intellij.grammar.formatter; + +import com.intellij.formatting.*; +import com.intellij.psi.formatter.common.AbstractBlock; +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 org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import com.intellij.psi.formatter.FormatterUtil; +import com.intellij.psi.tree.IElementType; +import java.util.List; +import java.util.ArrayList; +import static org.intellij.grammar.psi.BnfTypes.*; + +public class BnfFormattingModelBuilder implements FormattingModelBuilder { + @NotNull + @Override + public FormattingModel createModel(PsiElement element, CodeStyleSettings settings) { + BnfFormattingBlock block = new BnfFormattingBlock(element.getNode(), null, null, settings); + return FormattingModelProvider.createFormattingModelForPsiFile(element.getContainingFile(), block, settings); + } + + @Override + public TextRange getRangeAffectingIndent(PsiFile psiFile, int i, ASTNode astNode) { + return null; + } + + public static class BnfFormattingBlock extends AbstractBlock { + private final CodeStyleSettings mySettings; + public BnfFormattingBlock(@NotNull ASTNode node, @Nullable Wrap wrap, @Nullable Alignment alignment, CodeStyleSettings settings) { + super(node, wrap, alignment); + mySettings = settings; + } + + @Override + protected List buildChildren() { + if (isLeaf()) { return EMPTY; } + ArrayList children = new ArrayList(); + Alignment baseAlignment = Alignment.createAlignment(); + for (ASTNode childNode = getNode().getFirstChildNode(); childNode != null; childNode = childNode.getTreeNext()) { + if (FormatterUtil.containsWhiteSpacesOnly(childNode)) continue; + Block childBlock = new BnfFormattingBlock(childNode, createChildWrap(childNode), createChildAlignment(baseAlignment, childNode), mySettings); + children.add(childBlock); + } + return children; + } + + public Wrap createChildWrap(ASTNode child) { + return null; + } + + public Alignment createChildAlignment(Alignment baseAlignment, ASTNode child) { + IElementType elementType = child.getElementType(); + ASTNode parent = child.getTreeParent(); + IElementType parentType = parent != null ? parent.getElementType() : null; + if (parentType == BNF_ATTRS && elementType != BNF_LEFT_BRACE && elementType != BNF_RIGHT_BRACE) { + return baseAlignment; + } + if (parentType == BNF_CHOICE) { + return baseAlignment; + } + if (parentType == BNF_PAREN_EXPRESSION && elementType != BNF_LEFT_BRACE && elementType != BNF_RIGHT_BRACE) { + return baseAlignment; + } + return null; + } + + @Override + public Indent getIndent() { + ASTNode node = getNode(); + IElementType elementType = node.getElementType(); + ASTNode parent = node.getTreeParent(); + IElementType parentType = parent != null ? parent.getElementType() : null; + if (parentType == BNF_ATTRS && elementType == BNF_ATTR) { + return Indent.getNormalIndent(); + } + if (parentType == BNF_CHOICE) { + return Indent.getContinuationIndent(); + } + if (parentType == BNF_PAREN_EXPRESSION) { + return Indent.getContinuationIndent(); + } + return Indent.getNoneIndent(); + } + + @Nullable + @Override + public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) { + SpacingBuilder spacingBuilder = new SpacingBuilder(mySettings) + .around(BNF_OP_EQ).none() + .around(BNF_OP_IS).spaces(1) + .around(BNF_OP_OR).spaces(1) + .before(BNF_OP_ONEMORE).none() + .before(BNF_OP_ZEROMORE).none() + .before(BNF_SEMICOLON).none() + .after(BNF_LEFT_BRACE).none() + .before(BNF_RIGHT_BRACE).none() + .after(BNF_LEFT_BRACKET).none() + .after(BNF_LEFT_PAREN).none() + .before(BNF_RIGHT_PAREN).none() + .after(BNF_EXTERNAL_START).none() + .before(BNF_EXTERNAL_END).none() + .around(BNF_RULE).none() + .after(BNF_MODIFIER).spaces(1) + .before(BNF_ATTRS).spaces(1) + .after(BNF_ATTR).spaces(1) + .around(BNF_EXPRESSION).spaces(1) + .after(BNF_QUANTIFIED).spaces(1) + .before(BNF_QUANTIFIER).none() + .after(BNF_PREDICATE_SIGN).none() + .around(BNF_EXTERNAL_EXPRESSION).spaces(1) + .around(BNF_REFERENCE_OR_TOKEN).spaces(1) + .around(BNF_LITERAL_EXPRESSION).spaces(1) + .around(BNF_STRING_LITERAL_EXPRESSION).spaces(1) + .around(BNF_PAREN_EXPRESSION).spaces(1) + .around(BNF_PAREN_OPT_EXPRESSION).spaces(1) + ; + return spacingBuilder.getSpacing(this, child1, child2); + } + + @Override + public boolean isLeaf() { + return false; + } + } +} diff --git a/grammars/Grammar.bnf b/grammars/Grammar.bnf index 4108c8b1..c4145141 100755 --- a/grammars/Grammar.bnf +++ b/grammars/Grammar.bnf @@ -42,6 +42,23 @@ line_comment="regexp://.*" block_comment="regexp:/\*(.|\n)*\*/" ] + + formattingBuilderClass="org.intellij.grammar.formatter.BnfFormattingModelBuilder" + around("::=|\|")="spaces(1)" + around("=|rule")="none()" + before("\*|\+|\)|\;|\}|>>")="none()" + after("\(|\[|\{|predicate_sign|<<")="none()" + after("quantified|attr|modifier")="spaces(1)" + before("quantifier")="none()" + before("attrs")="spaces(1)" + around(".*expression|reference_or_token")="spaces(1)" + + normalIndent("attrs")="attr" + continuationIndent("paren_expression")=".*" + continuationIndent("choice")=".*" + + alignment("choice")=".*" + alignment("attrs|paren_expression")="except:\{|\}" implements("rule|attr")="org.intellij.grammar.psi.BnfNamedElement" extends("rule|attr")="org.intellij.grammar.psi.impl.BnfNamedElementImpl" diff --git a/support/META-INF/plugin.xml b/support/META-INF/plugin.xml index 9ce56cbc..0364ed5c 100755 --- a/support/META-INF/plugin.xml +++ b/support/META-INF/plugin.xml @@ -81,6 +81,8 @@ + +