diff --git a/README.md b/README.md index 41e32fa..5adc12e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## Google Protocol Buffers support for JetBrains IDEs +## Protobuf Support for JetBrains IDEs [Protobuf Support Plugin](https://plugins.jetbrains.com/plugin/8277) for IntelliJ IDEA & other JetBrains products. diff --git a/build.gradle b/build.gradle index d7ded81..88ad590 100644 --- a/build.gradle +++ b/build.gradle @@ -7,11 +7,10 @@ buildscript { plugins { id "org.jetbrains.intellij" version "0.0.43" - id "com.jfrog.bintray" version "1.6" } -group = 'org.antlr' -description = 'Support for using ANTLR-generated parsers/lexers in jetbrains IDE plug-ins.' +group = 'io.protostuff' +description = 'Protobuf Plugin for JetBrains IDEs' repositories { jcenter() @@ -21,13 +20,11 @@ repositories { } } -apply plugin: 'antlr' - dependencies { compile 'org.antlr:antlr4-runtime:4.5.1' compile 'org.antlr:antlr4-jetbrains-adapter:1.0.0' - antlr 'org.antlr:antlr4:4.5' - compile 'io.protostuff:protostuff-parser:2.0.0-alpha12' + compile 'com.google.guava:guava:19.0' + compile 'io.protostuff:protostuff-parser:2.0.0-alpha17-SNAPSHOT' } apply plugin: 'idea' @@ -36,16 +33,17 @@ idea { jdkName = javaVersion languageLevel = javaVersion } - module { - generatedSourceDirs += file('gen') - } } apply plugin: 'org.jetbrains.intellij' intellij { version = ideaVersion updateSinceUntilBuild = false - + // TODO: we do not need dependency on this plugin in runtime + // TODO: should be removed, but then tests are failing with + // TODO: ERROR: java.lang.ClassNotFoundException: com.intellij.lang.properties.PropertiesFileTypeFactory + plugins 'properties' +// downloadSources = false publish { username = project.hasProperty('jetbrainsUser') \ ? project.property('jetbrainsUser') \ diff --git a/gradle.properties b/gradle.properties index b138b3d..8b41ae5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # Available idea versions: # https://www.jetbrains.com/intellij-repository/releases # https://www.jetbrains.com/intellij-repository/snapshots -version=0.2.0 +version=0.3.0-SNAPSHOT ideaVersion=145.258.11 # https://intellij-support.jetbrains.com/hc/en-us/articles/206544879-Selecting-the-JDK-version-the-IDE-will-run-under # Java 8 is required to run IntelliJ IDEA starting from version 16 diff --git a/src/main/java/io/protostuff/jetbrains/plugin/ProtoParserDefinition.java b/src/main/java/io/protostuff/jetbrains/plugin/ProtoParserDefinition.java index e78c8b6..9b1d7b3 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/ProtoParserDefinition.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/ProtoParserDefinition.java @@ -46,6 +46,7 @@ public class ProtoParserDefinition implements ParserDefinition { public static final TokenIElementType RSQUARE; public static final TokenIElementType LT; public static final TokenIElementType GT; + public static final TokenIElementType ASSIGN; // Rules public static final IElementType R_TYPE_REFERENCE; @@ -53,20 +54,21 @@ public class ProtoParserDefinition implements ParserDefinition { public static final IElementType R_FIELD_MODIFIER; private static final IFileElementType FILE; private static final TokenSet COMMENTS; - private static final TokenSet WHITESPACE; - private static final TokenSet STRING; + public static final TokenSet WHITESPACE; + private static final TokenSet STRING; + private static List tokenTypes; + private static List ruleTypes; static { PSIElementTypeFactory.defineLanguageIElementTypes(ProtoLanguage.INSTANCE, ProtoParser.tokenNames, ProtoParser.ruleNames); - List tokenTypes = - PSIElementTypeFactory.getTokenIElementTypes(ProtoLanguage.INSTANCE); + tokenTypes = PSIElementTypeFactory.getTokenIElementTypes(ProtoLanguage.INSTANCE); ID = tokenTypes.get(ProtoLexer.NAME); FILE = new IFileElementType(ProtoLanguage.INSTANCE); COMMENTS = PSIElementTypeFactory.createTokenSet(ProtoLanguage.INSTANCE, COMMENT, LINE_COMMENT); - WHITESPACE = PSIElementTypeFactory.createTokenSet(ProtoLanguage.INSTANCE, WS); + WHITESPACE = PSIElementTypeFactory.createTokenSet(ProtoLanguage.INSTANCE, WS, NL); STRING = PSIElementTypeFactory.createTokenSet(ProtoLanguage.INSTANCE, STRING_VALUE); @@ -111,7 +113,7 @@ public class ProtoParserDefinition implements ParserDefinition { ProtoLexer.BYTES ); - List ruleTypes = PSIElementTypeFactory.getRuleIElementTypes(ProtoLanguage.INSTANCE); + ruleTypes = PSIElementTypeFactory.getRuleIElementTypes(ProtoLanguage.INSTANCE); R_TYPE_REFERENCE = ruleTypes.get(ProtoParser.RULE_typeReference); R_NAME = ruleTypes.get(ProtoParser.RULE_name); @@ -123,10 +125,19 @@ public class ProtoParserDefinition implements ParserDefinition { RPAREN = tokenTypes.get(ProtoLexer.RPAREN); LSQUARE = tokenTypes.get(ProtoLexer.LSQUARE); RSQUARE = tokenTypes.get(ProtoLexer.RSQUARE); + ASSIGN = tokenTypes.get(ProtoLexer.ASSIGN); LT = tokenTypes.get(ProtoLexer.LT); GT = tokenTypes.get(ProtoLexer.GT); } + public static TokenIElementType token(int token) { + return tokenTypes.get(token); + } + + public static RuleIElementType rule(int rule) { + return ruleTypes.get(rule); + } + @NotNull @Override public Lexer createLexer(Project project) { @@ -229,14 +240,6 @@ public PsiElement createElement(ASTNode node) { return new RpcMethodTypeNode(node); case ProtoParser.RULE_proto: return new ProtoRootNode(node); - case ProtoParser.RULE_statement: - return new ProtoRootStatementNode(node); - case ProtoParser.RULE_messageBlockEntry: - return new MessageBlockEntryNode(node); - case ProtoParser.RULE_enumBlockEntry: - return new EnumBlockEntryNode(node); - case ProtoParser.RULE_serviceBlockEntry: - return new ServiceBlockEntryNode(node); default: return new ANTLRPsiNode(node); } diff --git a/src/main/java/io/protostuff/jetbrains/plugin/formatter/BlockFactory.java b/src/main/java/io/protostuff/jetbrains/plugin/formatter/BlockFactory.java new file mode 100644 index 0000000..5256d2e --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/formatter/BlockFactory.java @@ -0,0 +1,95 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.intellij.formatting.Alignment; +import com.intellij.formatting.Block; +import com.intellij.formatting.Indent; +import com.intellij.lang.ASTNode; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; +import java.util.Map; + +import static io.protostuff.compiler.parser.ProtoParser.*; +import static io.protostuff.jetbrains.plugin.ProtoParserDefinition.rule; + +/** + * @author Kostiantyn Shchepanovskyi + */ +class BlockFactory { + + static final Map registry = new HashMap<>(); + + static { + Factory FAIL_ROOT_NODE = (node, alignment, indent, settings) -> { + throw new IllegalStateException("Root node cannot be handled here"); + }; + register(rule(RULE_proto), FAIL_ROOT_NODE); + register(rule(RULE_packageName), LeafBlock::new); + register(rule(RULE_rpcType), LeafBlock::new); + register(rule(RULE_name), LeafBlock::new); + register(rule(RULE_mapKey), LeafBlock::new); + register(rule(RULE_mapValue), LeafBlock::new); + register(rule(RULE_tag), LeafBlock::new); + register(rule(RULE_fieldName), LeafBlock::new); + register(rule(RULE_textFormatOptionName), LeafBlock::new); + register(rule(RULE_textFormatOptionValue), LeafBlock::new); + register(rule(RULE_optionName), LeafBlock::new); + register(rule(RULE_optionValue), LeafBlock::new); + register(rule(RULE_fieldModifier), LeafBlock::new); + register(rule(RULE_typeReference), LeafBlock::new); + register(rule(RULE_ranges), StatementBlock::new); + register(rule(RULE_range), StatementBlock::new); + register(rule(RULE_reserved), StatementBlock::new); + register(rule(RULE_fieldNames), StatementBlock::new); + register(rule(RULE_fieldOptions), StatementBlock::new); + register(rule(RULE_map), StatementBlock::new); + register(rule(RULE_syntax), StatementBlock::new); + register(rule(RULE_packageStatement), StatementBlock::new); + register(rule(RULE_importStatement), StatementBlock::new); + register(rule(RULE_optionEntry), StatementBlock::new); + register(rule(RULE_option), StatementBlock::new); + register(rule(RULE_messageBlock), ParentBlock::new); + register(rule(RULE_textFormat), ParentBlock::new); + register(rule(RULE_textFormatEntry), ParentBlock::new); + register(rule(RULE_field), StatementBlock::new); + register(rule(RULE_enumBlock), ParentBlock::new); + register(rule(RULE_enumConstant), StatementBlock::new); + register(rule(RULE_serviceBlock), ParentBlock::new); + register(rule(RULE_rpcMethod), ParentBlock::new); + register(rule(RULE_extendBlock), ParentBlock::new); + register(rule(RULE_extendBlockEntry), StatementBlock::new); + register(rule(RULE_oneof), ParentBlock::new); + register(rule(RULE_oneofField), StatementBlock::new); + register(rule(RULE_oneofGroup), ParentBlock::new); + register(rule(RULE_groupBlock), ParentBlock::new); + register(rule(RULE_extensions), StatementBlock::new); + } + + private static void register(IElementType elementType, Factory factory) { + if (registry.containsKey(elementType)) { + throw new IllegalStateException("Already registered: " + elementType); + } + registry.put(elementType, factory); + } + + static Block createBlock(ASTNode node, Alignment alignment, Indent indent, CodeStyleSettings settings) { + Factory factory = registry.get(node.getElementType()); + if (factory == null) { + // If element type is unknown it is best to keep existing formatting + return createLeaf(node, alignment, indent, settings); + } + return factory.create(node, alignment, indent, settings); + } + + @NotNull + private static LeafBlock createLeaf(ASTNode node, Alignment alignment, Indent indent, CodeStyleSettings settings) { + return new LeafBlock(node, alignment, indent, settings); + } + + interface Factory { + Block create(ASTNode node, Alignment alignment, Indent indent, CodeStyleSettings settings); + } + +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/formatter/FormattingModelBuilder.java b/src/main/java/io/protostuff/jetbrains/plugin/formatter/FormattingModelBuilder.java new file mode 100644 index 0000000..3e5300e --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/formatter/FormattingModelBuilder.java @@ -0,0 +1,56 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.intellij.formatting.*; +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.codeStyle.CommonCodeStyleSettings; +import io.protostuff.jetbrains.plugin.ProtoLanguage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import static io.protostuff.jetbrains.plugin.formatter.StatementBlock.*; +/** + * @author Kostiantyn Shchepanovskyi + */ +public class FormattingModelBuilder implements com.intellij.formatting.FormattingModelBuilder { + + @NotNull + @Override + public FormattingModel createModel(PsiElement element, CodeStyleSettings settings) { + PsiFile containingFile = element.getContainingFile().getViewProvider().getPsi(ProtoLanguage.INSTANCE); + ASTNode fileNode = containingFile.getNode(); + Wrap wrap = Wrap.createWrap(WrapType.NONE, false); + Alignment alignment = Alignment.createAlignment(); + ProtoFileBlock block = new ProtoFileBlock(fileNode, wrap, alignment, settings); + return FormattingModelProvider.createFormattingModelForPsiFile(containingFile, block, settings); + } + + @Nullable + @Override + public TextRange getRangeAffectingIndent(PsiFile file, int offset, ASTNode elementAtOffset) { + return null; + } + + public static SpacingBuilder createSpacingBuilder(CodeStyleSettings settings) { + CommonCodeStyleSettings protoSettings = settings.getCommonSettings(ProtoLanguage.INSTANCE); + return new SpacingBuilder(settings, ProtoLanguage.INSTANCE) + .around(ASSIGN).spaceIf(protoSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS) + .before(SEMICOLON).spaceIf(protoSettings.SPACE_BEFORE_SEMICOLON) + .after(LINE_COMMENT).spacing(0, 0, 1, true, 2) + .after(LCURLY).spacing(0, 0, 1, true, 2) + .before(RCURLY).spacing(0, 0, 1, true, 2) + .after(LPAREN).spacing(0, 0, 0, false, 0) + .before(RPAREN).spacing(0, 0, 0, false, 0) + .after(LSQUARE).spacing(0, 0, 0, false, 0) + .before(RSQUARE).spacing(0, 0, 0, false, 0) + .before(LT).spacing(0, 0, 0, false, 0) + .after(LT).spacing(0, 0, 0, false, 0) + .before(GT).spacing(0, 0, 0, false, 0) + .before(COMMA).spacing(0, 0, 0, false, 0) + .before(SEMICOLON).spacing(0, 0, 0, false, 0) + .after(COMMA).spacing(1, 1, 0, false, 0); + + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/formatter/LeafBlock.java b/src/main/java/io/protostuff/jetbrains/plugin/formatter/LeafBlock.java new file mode 100644 index 0000000..25bb38c --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/formatter/LeafBlock.java @@ -0,0 +1,76 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.intellij.formatting.*; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.List; + +class LeafBlock implements ASTBlock { + private final ASTNode node; + private final Alignment alignment; + private final Indent myIndent; + + LeafBlock(ASTNode node, Alignment alignment, Indent indent, CodeStyleSettings settings) { + this.node = node; + this.alignment = alignment; + myIndent = indent; + } + + @Override + public ASTNode getNode() { + return node; + } + + @Override + @NotNull + public TextRange getTextRange() { + return node.getTextRange(); + } + + @Override + @NotNull + public List getSubBlocks() { + return Collections.emptyList(); + } + + @Override + public Wrap getWrap() { + return null; + } + + @Override + public Indent getIndent() { + return myIndent; + } + + @Override + public Alignment getAlignment() { + return alignment; + } + + @Override + public Spacing getSpacing(Block child1, @NotNull Block child2) { + return null; + } + + @Override + @NotNull + public ChildAttributes getChildAttributes(final int newChildIndex) { + return new ChildAttributes(getIndent(), null); + } + + @Override + public boolean isIncomplete() { + return false; + } + + @Override + public boolean isLeaf() { + return true; + } + +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/formatter/ParentBlock.java b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ParentBlock.java new file mode 100644 index 0000000..e05f3ee --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ParentBlock.java @@ -0,0 +1,113 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.intellij.formatting.*; +import com.intellij.lang.ASTNode; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.formatter.FormatterUtil; +import com.intellij.psi.tree.IElementType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Abstract block for all constructs that have children in curly braces. + * + * @author Kostiantyn Shchepanovskyi + */ +class ParentBlock extends StatementBlock { + + private final Set headerBlocks = new HashSet<>(); + private Alignment childAlignment; + + ParentBlock(@NotNull ASTNode node, @Nullable Alignment alignment, + Indent indent, CodeStyleSettings settings) { + super(node, alignment, indent, settings); + childAlignment = Alignment.createAlignment(); + } + + @Override + protected List buildChildren() { + ASTNode child = getNode().getFirstChildNode(); + State state = State.BEFORE_LEFT_CURLY_BRACE; + List result = new ArrayList<>(); + while (child != null) { + if (!FormatterUtil.containsWhiteSpacesOnly(child)) { + IElementType elementType = child.getElementType(); + if (LCURLY.equals(elementType)) { + state = State.AFTER_LEFT_CURLY_BRACE; + result.add(BlockFactory.createBlock(child, myAlignment, Indent.getNoneIndent(), settings)); + } else if (RCURLY.equals(elementType)) { + result.add(BlockFactory.createBlock(child, myAlignment, Indent.getNoneIndent(), settings)); + state = State.AFTER_RIGHT_CURLY_BRACE; + } else { + switch (state) { + case BEFORE_LEFT_CURLY_BRACE: + Block block = BlockFactory.createBlock(child, myAlignment, Indent.getNoneIndent(), settings); + headerBlocks.add(block); + result.add(block); + break; + case AFTER_LEFT_CURLY_BRACE: + result.add(BlockFactory.createBlock(child, childAlignment, Indent.getNormalIndent(true), settings)); + break; + case AFTER_RIGHT_CURLY_BRACE: + result.add(BlockFactory.createBlock(child, myAlignment, Indent.getNoneIndent(), settings)); + break; + default: + throw new IllegalStateException(state.toString()); + } + } + } + child = child.getTreeNext(); + } + return result; + } + + @Nullable + @Override + public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) { + if (child2 instanceof ASTBlock) { + ASTBlock block = (ASTBlock) child2; + // Do not move semicolon after '}' to new line. + IElementType elementType = block.getNode().getElementType(); + if (SEMICOLON.equals(elementType)) { + return NONE; + } + // Do not move trailing comments to new line. + if (LINE_COMMENT.equals(elementType) + || COMMENT.equals(elementType)) { + return SPACE_OR_NEW_LINE; + } + } + if (headerBlocks.contains(child1)) { + return super.getSpacing(child1, child2); + } + return NEW_LINE; + } + + @NotNull + @Override + public ChildAttributes getChildAttributes(int newChildIndex) { + return new ChildAttributes(getChildIndent(), childAlignment); + } + + @Nullable + @Override + protected Indent getChildIndent() { + return Indent.getNormalIndent(); + } + + @Override + public boolean isLeaf() { + return false; + } + + private enum State { + BEFORE_LEFT_CURLY_BRACE, + AFTER_LEFT_CURLY_BRACE, + AFTER_RIGHT_CURLY_BRACE + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoCodeStyleSettings.java b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoCodeStyleSettings.java new file mode 100644 index 0000000..dfa9f27 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoCodeStyleSettings.java @@ -0,0 +1,12 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CustomCodeStyleSettings; + +public class ProtoCodeStyleSettings extends CustomCodeStyleSettings { + public ProtoCodeStyleSettings(CodeStyleSettings settings) { + super("ProtoCodeStyleSettings", settings); + } + + +} \ No newline at end of file diff --git a/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoCodeStyleSettingsProvider.java b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoCodeStyleSettingsProvider.java new file mode 100644 index 0000000..a00ee84 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoCodeStyleSettingsProvider.java @@ -0,0 +1,61 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.intellij.application.options.CodeStyleAbstractConfigurable; +import com.intellij.application.options.CodeStyleAbstractPanel; +import com.intellij.application.options.TabbedLanguageCodeStylePanel; +import com.intellij.openapi.options.Configurable; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsProvider; +import com.intellij.psi.codeStyle.CustomCodeStyleSettings; +import io.protostuff.jetbrains.plugin.ProtoLanguage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class ProtoCodeStyleSettingsProvider extends CodeStyleSettingsProvider { + + @Override + public CustomCodeStyleSettings createCustomSettings(CodeStyleSettings settings) { + return new ProtoCodeStyleSettings(settings); + } + + @Nullable + @Override + public String getConfigurableDisplayName() { + return "Protobuf"; + } + + @NotNull + @Override + public Configurable createSettingsPage(CodeStyleSettings settings, CodeStyleSettings originalSettings) { + return new CodeStyleAbstractConfigurable(settings, originalSettings, "Protobuf") { + @Override + protected CodeStyleAbstractPanel createPanel(CodeStyleSettings settings) { + return new ProtoCodeStyleMainPanel(getCurrentSettings(), settings); + } + + @Nullable + @Override + public String getHelpTopic() { + return null; + } + }; + } + + + private static class ProtoCodeStyleMainPanel extends TabbedLanguageCodeStylePanel { + public ProtoCodeStyleMainPanel(CodeStyleSettings currentSettings, CodeStyleSettings settings) { + super(ProtoLanguage.INSTANCE, currentSettings, settings); + } + + @Override + protected void initTabs(CodeStyleSettings settings) { + addIndentOptionsTab(settings); +// addSpacesTab(settings); +// addBlankLinesTab(settings); +// addWrappingAndBracesTab(settings); + } + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoFileBlock.java b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoFileBlock.java new file mode 100644 index 0000000..79e97f3 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoFileBlock.java @@ -0,0 +1,80 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.intellij.formatting.*; +import com.intellij.lang.ASTNode; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.formatter.FormatterUtil; +import com.intellij.psi.formatter.common.AbstractBlock; +import com.intellij.psi.tree.IElementType; +import io.protostuff.compiler.parser.ProtoParser; +import io.protostuff.jetbrains.plugin.ProtoParserDefinition; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +import static io.protostuff.jetbrains.plugin.formatter.BlockFactory.createBlock; + +/** + * @author Kostiantyn Shchepanovskyi + */ +class ProtoFileBlock extends AbstractBlock { + + private final CodeStyleSettings settings; + ProtoFileBlock(@NotNull ASTNode node, @Nullable Wrap wrap, @Nullable Alignment alignment, CodeStyleSettings settings) { + super(node, wrap, alignment); + this.settings = settings; + + } + + @Override + protected List buildChildren() { + List blocks = new ArrayList<>(); + ASTNode child = myNode.getFirstChildNode(); + while (child != null) { + if (!FormatterUtil.containsWhiteSpacesOnly(child)) { + IElementType elementType = child.getElementType(); + if (ProtoParserDefinition.rule(ProtoParser.RULE_proto).equals(elementType)) { + appendProtoBlocks(child, blocks); + } else { + // Comments are not part of root rule, we have to append them separately + blocks.add(new LeafBlock(child, Alignment.createAlignment(), Indent.getNoneIndent(), settings)); + } + } + child = child.getTreeNext(); + } + return blocks; + } + + private void appendProtoBlocks(ASTNode protoRootNode, List blocks) { + ASTNode child = protoRootNode.getFirstChildNode(); + Alignment alignment = Alignment.createAlignment(); + while (child != null) { + if (!FormatterUtil.containsWhiteSpacesOnly(child)) { + Block block = createBlock(child, alignment, Indent.getNoneIndent(), settings); + blocks.add(block); + } + child = child.getTreeNext(); + } + } + + @Nullable + @Override + public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) { + if (child1 == null) { + return StatementBlock.NONE; + } + return StatementBlock.NEW_LINE; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public Indent getIndent() { + return Indent.getAbsoluteNoneIndent(); + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoLanguageCodeStyleSettingsProvider.java b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoLanguageCodeStyleSettingsProvider.java new file mode 100644 index 0000000..69cf887 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/formatter/ProtoLanguageCodeStyleSettingsProvider.java @@ -0,0 +1,76 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.google.common.base.Joiner; +import com.intellij.application.options.IndentOptionsEditor; +import com.intellij.application.options.SmartIndentOptionsEditor; +import com.intellij.lang.Language; +import com.intellij.psi.codeStyle.CodeStyleSettingsCustomizable; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider; +import io.protostuff.jetbrains.plugin.ProtoLanguage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static com.intellij.psi.codeStyle.CommonCodeStyleSettings.END_OF_LINE; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class ProtoLanguageCodeStyleSettingsProvider extends LanguageCodeStyleSettingsProvider { + + @NotNull + @Override + public Language getLanguage() { + return ProtoLanguage.INSTANCE; + } + + + @Override + public void customizeSettings(@NotNull CodeStyleSettingsCustomizable consumer, @NotNull SettingsType settingsType) { + switch (settingsType) { +// case SPACING_SETTINGS: +// consumer.showStandardOptions("SPACE_AROUND_ASSIGNMENT_OPERATORS"); +// consumer.showStandardOptions("SPACE_SPACE_BEFORE_SEMICOLON"); +// consumer.renameStandardOption("SPACE_AROUND_ASSIGNMENT_OPERATORS", "Space around assignment operator"); +// break; +// case WRAPPING_AND_BRACES_SETTINGS: +// consumer.showStandardOptions("BRACE_STYLE"); +// break; +// case BLANK_LINES_SETTINGS: +// consumer.showStandardOptions("KEEP_BLANK_LINES_IN_CODE"); +// break; + default: + break; + } + } + + @Nullable + @Override + public IndentOptionsEditor getIndentOptionsEditor() { + return new SmartIndentOptionsEditor(); + } + + + @Nullable + @Override + public CommonCodeStyleSettings getDefaultCommonSettings() { + CommonCodeStyleSettings settings = new CommonCodeStyleSettings(ProtoLanguage.INSTANCE); + settings.initIndentOptions(); + // TODO: we should define our own settings + settings.SPACE_AROUND_ASSIGNMENT_OPERATORS = true; + settings.SPACE_BEFORE_SEMICOLON = false; + settings.BRACE_STYLE = END_OF_LINE; + return settings; + } + + @Override + public String getCodeSample(@NotNull SettingsType settingsType) { + return Joiner.on('\n').join( + "message Test {", + " optional int32 foo = 1;", + " optional string bar = 2;", + "}" + ); + } + +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/formatter/StatementBlock.java b/src/main/java/io/protostuff/jetbrains/plugin/formatter/StatementBlock.java new file mode 100644 index 0000000..4985427 --- /dev/null +++ b/src/main/java/io/protostuff/jetbrains/plugin/formatter/StatementBlock.java @@ -0,0 +1,88 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.intellij.formatting.*; +import com.intellij.lang.ASTNode; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.formatter.FormatterUtil; +import com.intellij.psi.formatter.common.AbstractBlock; +import com.intellij.psi.tree.IElementType; +import io.protostuff.compiler.parser.ProtoLexer; +import io.protostuff.jetbrains.plugin.ProtoLanguage; +import io.protostuff.jetbrains.plugin.ProtoParserDefinition; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Abstract block for all constructs that have children in curly braces. + * + * @author Kostiantyn Shchepanovskyi + */ +@SuppressWarnings("WeakerAccess") +class StatementBlock extends AbstractBlock { + + public static final Spacing NEW_LINE = Spacing.createSpacing(0, 0, 1, true, 2); + public static final Spacing SPACE = Spacing.createSpacing(1, 1, 0, false, 0); + public static final Spacing SPACE_OR_NEW_LINE = Spacing.createSpacing(1, 1, 0, true, 1); + public static final Spacing NONE = Spacing.createSpacing(0, 0, 0, false, 0); + + public static final IElementType SEMICOLON = ProtoParserDefinition.token(ProtoLexer.SEMICOLON); + public static final IElementType LCURLY = ProtoParserDefinition.token(ProtoLexer.LCURLY); + public static final IElementType RCURLY = ProtoParserDefinition.token(ProtoLexer.RCURLY); + public static final IElementType LPAREN = ProtoParserDefinition.token(ProtoLexer.LPAREN); + public static final IElementType RPAREN = ProtoParserDefinition.token(ProtoLexer.RPAREN); + public static final IElementType LSQUARE = ProtoParserDefinition.token(ProtoLexer.LSQUARE); + public static final IElementType RSQUARE = ProtoParserDefinition.token(ProtoLexer.RSQUARE); + public static final IElementType LT = ProtoParserDefinition.token(ProtoLexer.LT); + public static final IElementType GT = ProtoParserDefinition.token(ProtoLexer.GT); + public static final IElementType LINE_COMMENT = ProtoParserDefinition.token(ProtoLexer.LINE_COMMENT); + public static final IElementType COMMENT = ProtoParserDefinition.token(ProtoLexer.COMMENT); + public static final IElementType COMMA = ProtoParserDefinition.token(ProtoLexer.COMMA); + public static final IElementType ASSIGN = ProtoParserDefinition.token(ProtoLexer.ASSIGN); + + private final Indent indent; + protected final CodeStyleSettings settings; + private final SpacingBuilder spacingBuilder; + protected StatementBlock(@NotNull ASTNode node, @Nullable Alignment alignment, Indent indent, CodeStyleSettings settings) { + super(node, null, alignment); + this.indent = indent; + this.settings = settings; + spacingBuilder = FormattingModelBuilder.createSpacingBuilder(settings); + } + + @Override + protected List buildChildren() { + ASTNode child = getNode().getFirstChildNode(); + List result = new ArrayList<>(); + while (child != null) { + if (!FormatterUtil.containsWhiteSpacesOnly(child)) { + Block block = BlockFactory.createBlock(child, Alignment.createAlignment(), Indent.getNoneIndent(), settings); + result.add(block); + } + child = child.getTreeNext(); + } + return result; + } + + @Nullable + @Override + public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) { + Spacing spacing = spacingBuilder.getSpacing(this, child1, child2); + if (spacing == null) { + return SPACE; + } + return spacing; + } + + @Override + public boolean isLeaf() { + return false; + } + + @Override + public Indent getIndent() { + return indent; + } +} diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoPsiFileRoot.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoPsiFileRoot.java index 69c9ec7..caac3ec 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoPsiFileRoot.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoPsiFileRoot.java @@ -2,6 +2,7 @@ import com.intellij.extapi.psi.PsiFileBase; import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.FileViewProvider; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; @@ -30,7 +31,8 @@ public FileType getFileType() { @Override public String toString() { - return "Google Protocol Buffers File"; + final VirtualFile virtualFile = getVirtualFile(); + return "ProtobufFile: " + (virtualFile != null ? virtualFile.getName() : ""); } @Override diff --git a/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoRootStatementNode.java b/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoRootStatementNode.java deleted file mode 100644 index 93ed111..0000000 --- a/src/main/java/io/protostuff/jetbrains/plugin/psi/ProtoRootStatementNode.java +++ /dev/null @@ -1,16 +0,0 @@ -package io.protostuff.jetbrains.plugin.psi; - -import com.intellij.lang.ASTNode; -import org.antlr.jetbrains.adapter.psi.ANTLRPsiNode; -import org.jetbrains.annotations.NotNull; - -/** - * @author Kostiantyn Shchepanovskyi - */ -public class ProtoRootStatementNode extends ANTLRPsiNode implements KeywordsContainer { - - public ProtoRootStatementNode(@NotNull ASTNode node) { - super(node); - } - -} \ No newline at end of file diff --git a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/RootTreeElement.java b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/RootTreeElement.java index e07f840..139dd51 100644 --- a/src/main/java/io/protostuff/jetbrains/plugin/view/structure/RootTreeElement.java +++ b/src/main/java/io/protostuff/jetbrains/plugin/view/structure/RootTreeElement.java @@ -30,22 +30,18 @@ public ItemPresentation getPresentation() { @Override public TreeElement[] getChildren() { List treeElements = new ArrayList<>(); - for (PsiElement psiElement : element.getChildren()) { - if (psiElement instanceof ProtoRootStatementNode) { - // first and the only child - PsiElement node = psiElement.getFirstChild(); - if (node instanceof MessageNode) { - TreeElement element = new MessageTreeElement((MessageNode) node); - treeElements.add(element); - } - if (node instanceof EnumNode) { - TreeElement element = new EnumTreeElement((EnumNode) node); - treeElements.add(element); - } - if (node instanceof ServiceNode) { - TreeElement element = new ServiceTreeElement((ServiceNode) node); - treeElements.add(element); - } + for (PsiElement node : element.getChildren()) { + if (node instanceof MessageNode) { + TreeElement element = new MessageTreeElement((MessageNode) node); + treeElements.add(element); + } + if (node instanceof EnumNode) { + TreeElement element = new EnumTreeElement((EnumNode) node); + treeElements.add(element); + } + if (node instanceof ServiceNode) { + TreeElement element = new ServiceTreeElement((ServiceNode) node); + treeElements.add(element); } } return treeElements.toArray(new TreeElement[treeElements.size()]); diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 020f30f..995c04f 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -1,7 +1,7 @@ io.protostuff.protostuff-jetbrains-plugin Protobuf Support - 0.1.0 + 0.3.0 Kostiantyn Shchepanovskyi Features:
    -
  • Syntax highlighting and validation (full proto3 support).
  • -
  • Fonts & Colors configuration (Editor → Colors & Fonts → Protobuf).
  • -
  • Structure View. Activation keys: Alt+7 and Ctrl+F12 (popup).
  • +
  • Full Proto3 support.
  • +
  • Syntax highlighting and validation.
  • +
  • Fonts & Colors configuration.
  • +
  • Structure View.
  • Brace matching.
  • -
  • Line and block commenting using Ctrl+/ and Ctrl+Shift+/.
  • +
  • Line and block commenting.
  • +
  • Code formatting.

]]>
+ v0.3.0 + (2016-04-24) +
    +
  • Add code formatting.
  • +
+ v0.2.0 (2016-04-16) @@ -89,6 +98,15 @@ + + + + + + diff --git a/src/main/resources/io/protostuff/protostuff-jetbrains-plugin/messages/ProtostuffBundle.properties b/src/main/resources/io/protostuff/protostuff-jetbrains-plugin/messages/ProtostuffBundle.properties index 22522b9..8e81ac5 100644 --- a/src/main/resources/io/protostuff/protostuff-jetbrains-plugin/messages/ProtostuffBundle.properties +++ b/src/main/resources/io/protostuff/protostuff-jetbrains-plugin/messages/ProtostuffBundle.properties @@ -1,9 +1,5 @@ #actions -filetype.description.proto=Google Protocol Buffers file -action.newfile.text=Proto File -action.newfile.description=Create a new Protocol Buffers File -action.newfile.dialog.title=Create a new Protocol Buffers File -action.newfile.dialog.promt=Enter a new Protocol Buffers file name +filetype.description.proto=Protobuf file action.structureview.show.fields=Show Fields file.structure.toggle.show.fields=Show Fields diff --git a/src/test/java/io/protostuff/jetbrains/plugin/formatter/BlockFactoryTest.java b/src/test/java/io/protostuff/jetbrains/plugin/formatter/BlockFactoryTest.java new file mode 100644 index 0000000..626054c --- /dev/null +++ b/src/test/java/io/protostuff/jetbrains/plugin/formatter/BlockFactoryTest.java @@ -0,0 +1,36 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import com.intellij.psi.tree.IElementType; +import io.protostuff.compiler.parser.ProtoParser; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class BlockFactoryTest { + + @Test + public void allParserRulesAreRegistered() throws Exception { + Map registry = BlockFactory.registry; + + Set allRules = ImmutableSet.copyOf(ProtoParser.ruleNames); + + Set registeredRules = new HashSet<>(); + for (IElementType type : registry.keySet()) { + registeredRules.add(type.toString()); + } + + Sets.SetView diff = Sets.difference(allRules, registeredRules); + if (!diff.isEmpty()) { + Assert.fail("Following rules are not registered: " + diff); + } + + } +} \ No newline at end of file diff --git a/src/test/java/io/protostuff/jetbrains/plugin/formatter/FormatterTest.java b/src/test/java/io/protostuff/jetbrains/plugin/formatter/FormatterTest.java new file mode 100644 index 0000000..4ce1102 --- /dev/null +++ b/src/test/java/io/protostuff/jetbrains/plugin/formatter/FormatterTest.java @@ -0,0 +1,62 @@ +package io.protostuff.jetbrains.plugin.formatter; + +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.vfs.newvfs.impl.VfsRootAccess; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +import io.protostuff.jetbrains.plugin.ProtoLanguage; + +import java.util.function.Consumer; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class FormatterTest extends LightCodeInsightFixtureTestCase { + + @Override + protected String getTestDataPath() { + return "src/test/resources/formatting"; + } + + private void run(String test, Consumer settings) { + myFixture.configureByFiles(test + "/Source.proto"); + CodeStyleSettings codeStyleSettings = CodeStyleSettingsManager.getSettings(getProject()); + CommonCodeStyleSettings protoSettings = codeStyleSettings.getCommonSettings(ProtoLanguage.INSTANCE); + settings.accept(protoSettings); + new WriteCommandAction.Simple(getProject()) { + @Override + protected void run() throws Throwable { + CodeStyleManager.getInstance(getProject()).reformat(myFixture.getFile()); + } + }.execute(); + myFixture.checkResultByFile(test + "/Expected.proto"); + } + + public void testDefaultFormatter() { + run("default", settings -> {}); + } + + public void testTab() { + run("use_tab", settings -> { + CommonCodeStyleSettings.IndentOptions indentOptions = settings.initIndentOptions(); + indentOptions.USE_TAB_CHARACTER = true; + }); + } + + public void testCustomIndent() { + run("custom_indent", settings -> { + CommonCodeStyleSettings.IndentOptions indentOptions = settings.initIndentOptions(); + indentOptions.INDENT_SIZE = 2; + }); + } + + public void testDisableSpaceAroundAssignmentOperator() { + run("disable_space_around_assignment_operator", settings -> { + settings.SPACE_AROUND_ASSIGNMENT_OPERATORS = false; + }); + } + +} diff --git a/src/test/java/io/protostuff/jetbrains/plugin/parsing/ParsingTest.java b/src/test/java/io/protostuff/jetbrains/plugin/parsing/ParsingTest.java new file mode 100644 index 0000000..d6fb1ca --- /dev/null +++ b/src/test/java/io/protostuff/jetbrains/plugin/parsing/ParsingTest.java @@ -0,0 +1,46 @@ +package io.protostuff.jetbrains.plugin.parsing; + +import com.intellij.testFramework.ParsingTestCase; +import io.protostuff.jetbrains.plugin.ProtoParserDefinition; +import org.junit.Test; + +/** + * @author Kostiantyn Shchepanovskyi + */ +public class ParsingTest extends ParsingTestCase { + + public ParsingTest() { + super("parsing", "proto", new ProtoParserDefinition()); + } + + public void testMessage() { + doTest(true); + } + + public void testEnum() { + doTest(true); + } + + public void testService() { + doTest(true); + } + + public void testHeaderAndFooterComments() { + doTest(true); + } + + @Override + protected String getTestDataPath() { + return "src/test/resources"; + } + + @Override + protected boolean skipSpaces() { + return false; + } + + @Override + protected boolean includeRanges() { + return true; + } +} diff --git a/src/test/resources/formatting/custom_indent/Expected.proto b/src/test/resources/formatting/custom_indent/Expected.proto new file mode 100644 index 0000000..2ae1fae --- /dev/null +++ b/src/test/resources/formatting/custom_indent/Expected.proto @@ -0,0 +1,5 @@ +syntax = "proto2"; + +message TestMessage { + optional int32 x = 1; +} diff --git a/src/test/resources/formatting/custom_indent/Source.proto b/src/test/resources/formatting/custom_indent/Source.proto new file mode 100644 index 0000000..eda0c3e --- /dev/null +++ b/src/test/resources/formatting/custom_indent/Source.proto @@ -0,0 +1,5 @@ +syntax = "proto2"; + +message TestMessage{ + optional int32 x = 1; +} diff --git a/src/test/resources/formatting/default/Expected.proto b/src/test/resources/formatting/default/Expected.proto new file mode 100644 index 0000000..8f2ee54 --- /dev/null +++ b/src/test/resources/formatting/default/Expected.proto @@ -0,0 +1,14 @@ +syntax = "proto2"; + +message TestMessage { + optional int32 x = 1; +} + +enum TestEnum { + A = 1 [deprecated = true]; + B = 2; +} + +service TestService { + rpc Hello (HelloRequest) returns (HelloResponse); +} diff --git a/src/test/resources/formatting/default/Source.proto b/src/test/resources/formatting/default/Source.proto new file mode 100644 index 0000000..33a8cb8 --- /dev/null +++ b/src/test/resources/formatting/default/Source.proto @@ -0,0 +1,7 @@ +syntax = "proto2"; + +message TestMessage{optional int32 x = 1;} + +enum TestEnum{A=1[deprecated=true];B=2;} + +service TestService{rpc Hello(HelloRequest)returns(HelloResponse);} diff --git a/src/test/resources/formatting/disable_space_around_assignment_operator/Expected.proto b/src/test/resources/formatting/disable_space_around_assignment_operator/Expected.proto new file mode 100644 index 0000000..4e6502f --- /dev/null +++ b/src/test/resources/formatting/disable_space_around_assignment_operator/Expected.proto @@ -0,0 +1,5 @@ +syntax="proto2"; + +message TestMessage { + optional int32 x=1; +} diff --git a/src/test/resources/formatting/disable_space_around_assignment_operator/Source.proto b/src/test/resources/formatting/disable_space_around_assignment_operator/Source.proto new file mode 100644 index 0000000..eda0c3e --- /dev/null +++ b/src/test/resources/formatting/disable_space_around_assignment_operator/Source.proto @@ -0,0 +1,5 @@ +syntax = "proto2"; + +message TestMessage{ + optional int32 x = 1; +} diff --git a/src/test/resources/formatting/use_tab/Expected.proto b/src/test/resources/formatting/use_tab/Expected.proto new file mode 100644 index 0000000..a2283ce --- /dev/null +++ b/src/test/resources/formatting/use_tab/Expected.proto @@ -0,0 +1,5 @@ +syntax = "proto2"; + +message TestMessage { + optional int32 x = 1; +} diff --git a/src/test/resources/formatting/use_tab/Source.proto b/src/test/resources/formatting/use_tab/Source.proto new file mode 100644 index 0000000..eda0c3e --- /dev/null +++ b/src/test/resources/formatting/use_tab/Source.proto @@ -0,0 +1,5 @@ +syntax = "proto2"; + +message TestMessage{ + optional int32 x = 1; +} diff --git a/src/test/resources/parsing/Enum.proto b/src/test/resources/parsing/Enum.proto new file mode 100644 index 0000000..58a14af --- /dev/null +++ b/src/test/resources/parsing/Enum.proto @@ -0,0 +1,5 @@ +enum TestEnum { + ZERO = 0; + FIRST = 1; + FIFTH = 5; +} diff --git a/src/test/resources/parsing/Enum.txt b/src/test/resources/parsing/Enum.txt new file mode 100644 index 0000000..3f436d2 --- /dev/null +++ b/src/test/resources/parsing/Enum.txt @@ -0,0 +1,41 @@ +ProtobufFile: Enum.proto(0,61) + ProtoRootNode(proto)(0,61) + EnumNode(enumBlock)(0,61) + PsiElement('enum')('enum')(0,4) + PsiWhiteSpace(' ')(4,5) + ANTLRPsiNode(name)(5,13) + PsiElement(NAME)('TestEnum')(5,13) + PsiWhiteSpace(' ')(13,14) + PsiElement('{')('{')(14,15) + PsiWhiteSpace('\n')(15,16) + PsiWhiteSpace(' ')(16,20) + EnumConstantNode(enumConstant)(20,29) + ANTLRPsiNode(name)(20,24) + PsiElement(NAME)('ZERO')(20,24) + PsiWhiteSpace(' ')(24,25) + PsiElement('=')('=')(25,26) + PsiWhiteSpace(' ')(26,27) + PsiElement(INTEGER_VALUE)('0')(27,28) + PsiElement(';')(';')(28,29) + PsiWhiteSpace('\n')(29,30) + PsiWhiteSpace(' ')(30,34) + EnumConstantNode(enumConstant)(34,44) + ANTLRPsiNode(name)(34,39) + PsiElement(NAME)('FIRST')(34,39) + PsiWhiteSpace(' ')(39,40) + PsiElement('=')('=')(40,41) + PsiWhiteSpace(' ')(41,42) + PsiElement(INTEGER_VALUE)('1')(42,43) + PsiElement(';')(';')(43,44) + PsiWhiteSpace('\n')(44,45) + PsiWhiteSpace(' ')(45,49) + EnumConstantNode(enumConstant)(49,59) + ANTLRPsiNode(name)(49,54) + PsiElement(NAME)('FIFTH')(49,54) + PsiWhiteSpace(' ')(54,55) + PsiElement('=')('=')(55,56) + PsiWhiteSpace(' ')(56,57) + PsiElement(INTEGER_VALUE)('5')(57,58) + PsiElement(';')(';')(58,59) + PsiWhiteSpace('\n')(59,60) + PsiElement('}')('}')(60,61) diff --git a/src/test/resources/parsing/HeaderAndFooterComments.proto b/src/test/resources/parsing/HeaderAndFooterComments.proto new file mode 100644 index 0000000..3f92f7d --- /dev/null +++ b/src/test/resources/parsing/HeaderAndFooterComments.proto @@ -0,0 +1,3 @@ +// Header comment does not belong to 'proto' rule +syntax = "proto2"; +// Footer comment does not belong to 'proto' rule diff --git a/src/test/resources/parsing/HeaderAndFooterComments.txt b/src/test/resources/parsing/HeaderAndFooterComments.txt new file mode 100644 index 0000000..52bea16 --- /dev/null +++ b/src/test/resources/parsing/HeaderAndFooterComments.txt @@ -0,0 +1,14 @@ +ProtobufFile: HeaderAndFooterComments.proto(0,118) + PsiComment(LINE_COMMENT)('// Header comment does not belong to 'proto' rule')(0,49) + PsiWhiteSpace('\n')(49,50) + ProtoRootNode(proto)(50,68) + SyntaxNode(syntax)(50,68) + PsiElement('syntax')('syntax')(50,56) + PsiWhiteSpace(' ')(56,57) + PsiElement('=')('=')(57,58) + PsiWhiteSpace(' ')(58,59) + PsiElement(STRING_VALUE)('"proto2"')(59,67) + PsiElement(';')(';')(67,68) + PsiWhiteSpace('\n')(68,69) + PsiComment(LINE_COMMENT)('// Footer comment does not belong to 'proto' rule')(69,118) + diff --git a/src/test/resources/parsing/Message.proto b/src/test/resources/parsing/Message.proto new file mode 100644 index 0000000..743d880 --- /dev/null +++ b/src/test/resources/parsing/Message.proto @@ -0,0 +1,4 @@ +message TestMessage { + optional int32 x = 1; + optional int32 y = 2; +} diff --git a/src/test/resources/parsing/Message.txt b/src/test/resources/parsing/Message.txt new file mode 100644 index 0000000..dfafbd5 --- /dev/null +++ b/src/test/resources/parsing/Message.txt @@ -0,0 +1,43 @@ +ProtobufFile: Message.proto(0,75) + ProtoRootNode(proto)(0,75) + MessageNode(messageBlock)(0,75) + PsiElement('message')('message')(0,7) + PsiWhiteSpace(' ')(7,8) + ANTLRPsiNode(name)(8,19) + PsiElement(NAME)('TestMessage')(8,19) + PsiWhiteSpace(' ')(19,20) + PsiElement('{')('{')(20,21) + PsiWhiteSpace('\n')(21,22) + PsiWhiteSpace(' ')(22,26) + FieldNode(field)(26,47) + ANTLRPsiNode(fieldModifier)(26,34) + PsiElement('optional')('optional')(26,34) + PsiWhiteSpace(' ')(34,35) + TypeReferenceNode(typeReference)(35,40) + PsiElement('int32')('int32')(35,40) + PsiWhiteSpace(' ')(40,41) + ANTLRPsiNode(name)(41,42) + PsiElement(NAME)('x')(41,42) + PsiWhiteSpace(' ')(42,43) + PsiElement('=')('=')(43,44) + PsiWhiteSpace(' ')(44,45) + PsiElement(INTEGER_VALUE)('1')(45,46) + PsiElement(';')(';')(46,47) + PsiWhiteSpace('\n')(47,48) + PsiWhiteSpace(' ')(48,52) + FieldNode(field)(52,73) + ANTLRPsiNode(fieldModifier)(52,60) + PsiElement('optional')('optional')(52,60) + PsiWhiteSpace(' ')(60,61) + TypeReferenceNode(typeReference)(61,66) + PsiElement('int32')('int32')(61,66) + PsiWhiteSpace(' ')(66,67) + ANTLRPsiNode(name)(67,68) + PsiElement(NAME)('y')(67,68) + PsiWhiteSpace(' ')(68,69) + PsiElement('=')('=')(69,70) + PsiWhiteSpace(' ')(70,71) + PsiElement(INTEGER_VALUE)('2')(71,72) + PsiElement(';')(';')(72,73) + PsiWhiteSpace('\n')(73,74) + PsiElement('}')('}')(74,75) diff --git a/src/test/resources/parsing/Service.proto b/src/test/resources/parsing/Service.proto new file mode 100644 index 0000000..814d237 --- /dev/null +++ b/src/test/resources/parsing/Service.proto @@ -0,0 +1,3 @@ +service TestService { + rpc Hello (HelloRequest) returns (HelloResponse); +} diff --git a/src/test/resources/parsing/Service.txt b/src/test/resources/parsing/Service.txt new file mode 100644 index 0000000..5f6fc1e --- /dev/null +++ b/src/test/resources/parsing/Service.txt @@ -0,0 +1,35 @@ +ProtobufFile: Service.proto(0,77) + ProtoRootNode(proto)(0,77) + ServiceNode(serviceBlock)(0,77) + PsiElement('service')('service')(0,7) + PsiWhiteSpace(' ')(7,8) + ANTLRPsiNode(name)(8,19) + PsiElement(NAME)('TestService')(8,19) + PsiWhiteSpace(' ')(19,20) + PsiElement('{')('{')(20,21) + PsiWhiteSpace('\n')(21,22) + PsiWhiteSpace(' ')(22,26) + RpcMethodNode(rpcMethod)(26,75) + PsiElement('rpc')('rpc')(26,29) + PsiWhiteSpace(' ')(29,30) + ANTLRPsiNode(name)(30,35) + PsiElement(NAME)('Hello')(30,35) + PsiWhiteSpace(' ')(35,36) + PsiElement('(')('(')(36,37) + RpcMethodTypeNode(rpcType)(37,49) + TypeReferenceNode(typeReference)(37,49) + ANTLRPsiNode(name)(37,49) + PsiElement(NAME)('HelloRequest')(37,49) + PsiElement(')')(')')(49,50) + PsiWhiteSpace(' ')(50,51) + PsiElement('returns')('returns')(51,58) + PsiWhiteSpace(' ')(58,59) + PsiElement('(')('(')(59,60) + RpcMethodTypeNode(rpcType)(60,73) + TypeReferenceNode(typeReference)(60,73) + ANTLRPsiNode(name)(60,73) + PsiElement(NAME)('HelloResponse')(60,73) + PsiElement(')')(')')(73,74) + PsiElement(';')(';')(74,75) + PsiWhiteSpace('\n')(75,76) + PsiElement('}')('}')(76,77)