From 45b7314fe181506648496dcb8c74a674222616c0 Mon Sep 17 00:00:00 2001 From: Denys Dushyn Date: Sat, 18 Nov 2023 11:59:50 +0100 Subject: [PATCH] Add completion contributor for import statements. Signed-off-by: Denys Dushyn --- .../intellij/plugin/ANTLRv4ASTFactory.java | 1 + .../ANTLRv4CompletionContributor.java | 28 ++++++++ .../ANTLRv4ImportCompletionProvider.java | 47 +++++++++++++ .../plugin/psi/DelegateGrammarNode.java | 22 ++++++ src/main/resources/META-INF/plugin.xml | 2 + .../ANTLRv4CompletionContributorTest.java | 67 +++++++++++++++++++ .../import_parser_rules/CommonLexerRules.g4 | 0 .../completion/import_parser_rules/Grammar.g4 | 0 .../import_parser_rules/GrammarExpr.g4 | 0 9 files changed, 167 insertions(+) create mode 100644 src/main/java/org/antlr/intellij/plugin/completion/ANTLRv4CompletionContributor.java create mode 100644 src/main/java/org/antlr/intellij/plugin/completion/ANTLRv4ImportCompletionProvider.java create mode 100644 src/main/java/org/antlr/intellij/plugin/psi/DelegateGrammarNode.java create mode 100644 src/test/java/org/antlr/intellij/plugin/completion/ANTLRv4CompletionContributorTest.java create mode 100644 src/test/resources/completion/import_parser_rules/CommonLexerRules.g4 create mode 100644 src/test/resources/completion/import_parser_rules/Grammar.g4 create mode 100644 src/test/resources/completion/import_parser_rules/GrammarExpr.g4 diff --git a/src/main/java/org/antlr/intellij/plugin/ANTLRv4ASTFactory.java b/src/main/java/org/antlr/intellij/plugin/ANTLRv4ASTFactory.java index 600f9a90..35667393 100644 --- a/src/main/java/org/antlr/intellij/plugin/ANTLRv4ASTFactory.java +++ b/src/main/java/org/antlr/intellij/plugin/ANTLRv4ASTFactory.java @@ -29,6 +29,7 @@ public class ANTLRv4ASTFactory extends ASTFactory { ruleElementTypeToPsiFactory.put(ANTLRv4TokenTypes.RULE_ELEMENT_TYPES.get(ANTLRv4Parser.RULE_modeSpec), ModeSpecNode.Factory.INSTANCE); ruleElementTypeToPsiFactory.put(ANTLRv4TokenTypes.RULE_ELEMENT_TYPES.get(ANTLRv4Parser.RULE_action), AtAction.Factory.INSTANCE); ruleElementTypeToPsiFactory.put(ANTLRv4TokenTypes.RULE_ELEMENT_TYPES.get(ANTLRv4Parser.RULE_identifier), TokenSpecNode.Factory.INSTANCE); + ruleElementTypeToPsiFactory.put(ANTLRv4TokenTypes.RULE_ELEMENT_TYPES.get(ANTLRv4Parser.RULE_delegateGrammar), DelegateGrammarNode.Factory.INSTANCE); } /** Create a FileElement for root or a parse tree CompositeElement (not diff --git a/src/main/java/org/antlr/intellij/plugin/completion/ANTLRv4CompletionContributor.java b/src/main/java/org/antlr/intellij/plugin/completion/ANTLRv4CompletionContributor.java new file mode 100644 index 00000000..377f36a7 --- /dev/null +++ b/src/main/java/org/antlr/intellij/plugin/completion/ANTLRv4CompletionContributor.java @@ -0,0 +1,28 @@ +package org.antlr.intellij.plugin.completion; + +import com.intellij.codeInsight.completion.CompletionContributor; +import com.intellij.codeInsight.completion.CompletionType; +import com.intellij.patterns.PlatformPatterns; +import com.intellij.patterns.PsiElementPattern; +import org.antlr.intellij.plugin.psi.DelegateGrammarNode; +import org.antlr.intellij.plugin.psi.LexerRuleRefNode; +import org.antlr.intellij.plugin.psi.ParserRuleRefNode; + +public class ANTLRv4CompletionContributor extends CompletionContributor { + // a user can type in an upper case letter + private final static PsiElementPattern.Capture LEXER_RULE_GRAMMAR_IMPORT_PATTERN = + PlatformPatterns + .psiElement(LexerRuleRefNode.class) + .withSuperParent(2, DelegateGrammarNode.class); + + // a user can type in a lower case letter + private final static PsiElementPattern.Capture PARSE_RULE_GRAMMAR_IMPORT_PATTERN = + PlatformPatterns + .psiElement(ParserRuleRefNode.class) + .withSuperParent(2, DelegateGrammarNode.class); + + public ANTLRv4CompletionContributor() { + extend(CompletionType.BASIC, LEXER_RULE_GRAMMAR_IMPORT_PATTERN, new ANTLRv4ImportCompletionProvider()); + extend(CompletionType.BASIC, PARSE_RULE_GRAMMAR_IMPORT_PATTERN, new ANTLRv4ImportCompletionProvider()); + } +} diff --git a/src/main/java/org/antlr/intellij/plugin/completion/ANTLRv4ImportCompletionProvider.java b/src/main/java/org/antlr/intellij/plugin/completion/ANTLRv4ImportCompletionProvider.java new file mode 100644 index 00000000..a0b83479 --- /dev/null +++ b/src/main/java/org/antlr/intellij/plugin/completion/ANTLRv4ImportCompletionProvider.java @@ -0,0 +1,47 @@ +package org.antlr.intellij.plugin.completion; + +import com.intellij.codeInsight.completion.CompletionParameters; +import com.intellij.codeInsight.completion.CompletionProvider; +import com.intellij.codeInsight.completion.CompletionResultSet; +import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.psi.PsiDirectory; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ProcessingContext; +import org.antlr.intellij.plugin.ANTLRv4FileRoot; +import org.antlr.intellij.plugin.psi.DelegateGrammarNode; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Objects; + +public class ANTLRv4ImportCompletionProvider extends CompletionProvider { + @Override + protected void addCompletions(@NotNull CompletionParameters completionParameters, @NotNull ProcessingContext processingContext, @NotNull CompletionResultSet completionResultSet) { + PsiElement position = completionParameters.getPosition(); + DelegateGrammarNode delegateGrammarNode = PsiTreeUtil.getParentOfType(position, DelegateGrammarNode.class); + if (delegateGrammarNode == null) return; + + PsiFile containingFile = completionParameters.getOriginalFile(); + PsiDirectory containingDirectory = containingFile.getContainingDirectory(); + if (containingDirectory == null) return; // scratch file case + + CompletionResultSet resultSet = completionResultSet.caseInsensitive(); + Arrays + .stream(containingDirectory.getChildren()) + .map(psiElement -> { + if (psiElement instanceof ANTLRv4FileRoot) { + return (ANTLRv4FileRoot) psiElement; + } else { + return null; + } + }) + .filter(Objects::nonNull) + .map(PsiFileImpl::getName) + .filter(name -> !name.equals(containingFile.getName())) + .map(name -> name.replace(".g4", "")) + .forEach(name -> resultSet.addElement(LookupElementBuilder.create(name))); + } +} diff --git a/src/main/java/org/antlr/intellij/plugin/psi/DelegateGrammarNode.java b/src/main/java/org/antlr/intellij/plugin/psi/DelegateGrammarNode.java new file mode 100644 index 00000000..f29ef0a4 --- /dev/null +++ b/src/main/java/org/antlr/intellij/plugin/psi/DelegateGrammarNode.java @@ -0,0 +1,22 @@ +package org.antlr.intellij.plugin.psi; + +import com.intellij.extapi.psi.ASTWrapperPsiElement; +import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; +import org.antlr.intellij.adaptor.parser.PsiElementFactory; +import org.jetbrains.annotations.NotNull; + +public class DelegateGrammarNode extends ASTWrapperPsiElement { + public DelegateGrammarNode(@NotNull ASTNode node) { + super(node); + } + + public static class Factory implements PsiElementFactory { + public static Factory INSTANCE = new Factory(); + + @Override + public PsiElement createElement(ASTNode node) { + return new DelegateGrammarNode(node); + } + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f00cc69e..d50f8d51 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -173,5 +173,7 @@ For really big files and slow grammars, there is an appreciable delay when displ instance="org.antlr.intellij.plugin.configdialogs.ANTLRv4ProjectSettings"/> + diff --git a/src/test/java/org/antlr/intellij/plugin/completion/ANTLRv4CompletionContributorTest.java b/src/test/java/org/antlr/intellij/plugin/completion/ANTLRv4CompletionContributorTest.java new file mode 100644 index 00000000..a5072c79 --- /dev/null +++ b/src/test/java/org/antlr/intellij/plugin/completion/ANTLRv4CompletionContributorTest.java @@ -0,0 +1,67 @@ +package org.antlr.intellij.plugin.completion; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.testFramework.fixtures.BasePlatformTestCase; +import org.antlr.intellij.plugin.ANTLRv4FileType; +import org.antlr.intellij.plugin.TestUtils; + +import java.util.Arrays; +import java.util.List; + +public class ANTLRv4CompletionContributorTest extends BasePlatformTestCase { + public void test_import_completion() { + myFixture.copyDirectoryToProject("/import_parser_rules", ""); + myFixture.configureByText( + ANTLRv4FileType.INSTANCE, + """ + parser grammar ANTLRv4Parser; + import + """); + List completions = Arrays.stream(myFixture.completeBasic()).map(LookupElement::getLookupString).toList(); + assertNotNull(completions); + assertSize(3, completions); + assertTrue(completions.contains("Grammar")); + assertTrue(completions.contains("GrammarExpr")); + assertTrue(completions.contains("CommonLexerRules")); + + } + + public void test_import_completion_is_case_insensitive_lower_case() { + myFixture.copyDirectoryToProject("/import_parser_rules", ""); + myFixture.configureByText( + ANTLRv4FileType.INSTANCE, + """ + parser grammar ANTLRv4Parser; + import g + """); + List completions = Arrays.stream(myFixture.completeBasic()).map(LookupElement::getLookupString).toList(); + assertNotNull(completions); + assertSize(2, completions); + assertTrue(completions.contains("Grammar")); + assertTrue(completions.contains("GrammarExpr")); + } + + public void test_import_completion_is_case_insensitive_upper_case() { + myFixture.copyDirectoryToProject("/import_parser_rules", ""); + myFixture.configureByText( + ANTLRv4FileType.INSTANCE, + """ + parser grammar ANTLRv4Parser; + import G + """); + List completions = Arrays.stream(myFixture.completeBasic()).map(LookupElement::getLookupString).toList(); + assertNotNull(completions); + assertSize(2, completions); + assertTrue(completions.contains("Grammar")); + assertTrue(completions.contains("GrammarExpr")); + } + + protected String getTestDataPath() { + return "src/test/resources/completion"; + } + + @Override + protected void tearDown() throws Exception { + TestUtils.tearDownIgnoringObjectNotDisposedException(() -> super.tearDown()); + } +} diff --git a/src/test/resources/completion/import_parser_rules/CommonLexerRules.g4 b/src/test/resources/completion/import_parser_rules/CommonLexerRules.g4 new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/completion/import_parser_rules/Grammar.g4 b/src/test/resources/completion/import_parser_rules/Grammar.g4 new file mode 100644 index 00000000..e69de29b diff --git a/src/test/resources/completion/import_parser_rules/GrammarExpr.g4 b/src/test/resources/completion/import_parser_rules/GrammarExpr.g4 new file mode 100644 index 00000000..e69de29b