diff --git a/src/main/java/org/antlr/intellij/plugin/ANTLRv4FileRoot.java b/src/main/java/org/antlr/intellij/plugin/ANTLRv4FileRoot.java
index 0df39717..c5f76e76 100644
--- a/src/main/java/org/antlr/intellij/plugin/ANTLRv4FileRoot.java
+++ b/src/main/java/org/antlr/intellij/plugin/ANTLRv4FileRoot.java
@@ -3,6 +3,8 @@
import com.intellij.extapi.psi.PsiFileBase;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.psi.FileViewProvider;
+import com.intellij.psi.PsiElement;
+import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
public class ANTLRv4FileRoot extends PsiFileBase {
@@ -16,6 +18,16 @@ public FileType getFileType() {
return ANTLRv4FileType.INSTANCE;
}
+ @Override
+ public @NotNull String getName() {
+ return super.getName().replace(".g4", "");
+ }
+
+ @Override
+ public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
+ return super.setName(name.replace(".g4", "") + ".g4");
+ }
+
@Override
public String toString() {
return "ANTLR v4 grammar file";
diff --git a/src/main/java/org/antlr/intellij/plugin/psi/GrammarElementRef.java b/src/main/java/org/antlr/intellij/plugin/psi/GrammarElementRef.java
index bfeebd78..7749baf6 100644
--- a/src/main/java/org/antlr/intellij/plugin/psi/GrammarElementRef.java
+++ b/src/main/java/org/antlr/intellij/plugin/psi/GrammarElementRef.java
@@ -60,6 +60,10 @@ public PsiElement resolve() {
return importedFile;
}
+ if ( MyPsiUtils.isGrammarName(getElement())) {
+ return getElement().getContainingFile();
+ }
+
GrammarSpecNode grammar = PsiTreeUtil.getContextOfType(getElement(), GrammarSpecNode.class);
PsiElement specNode = MyPsiUtils.findSpecNode(grammar, ruleName);
diff --git a/src/main/java/org/antlr/intellij/plugin/psi/GrammarElementRefNode.java b/src/main/java/org/antlr/intellij/plugin/psi/GrammarElementRefNode.java
index ff35f853..1af2b0a1 100644
--- a/src/main/java/org/antlr/intellij/plugin/psi/GrammarElementRefNode.java
+++ b/src/main/java/org/antlr/intellij/plugin/psi/GrammarElementRefNode.java
@@ -4,7 +4,7 @@
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.tree.IElementType;
-import com.intellij.util.IncorrectOperationException;
+import org.antlr.intellij.plugin.ANTLRv4TokenTypes;
import org.jetbrains.annotations.NotNull;
/**
@@ -24,8 +24,13 @@ public String getName() {
}
@Override
- public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
- throw new IncorrectOperationException("Can't rename grammar reference identifier");
+ public PsiElement setName(@NotNull String newName) {
+ name = newName;
+ replace(MyPsiUtils.createLeafFromText(getProject(),
+ getContext(),
+ newName,
+ ANTLRv4TokenTypes.TOKEN_ELEMENT_TYPES.get(org.antlr.intellij.plugin.parser.ANTLRv4Lexer.TOKEN_REF)));
+ return this;
}
@Override
diff --git a/src/main/java/org/antlr/intellij/plugin/psi/MyPsiUtils.java b/src/main/java/org/antlr/intellij/plugin/psi/MyPsiUtils.java
index 1295b9cb..15c902f7 100644
--- a/src/main/java/org/antlr/intellij/plugin/psi/MyPsiUtils.java
+++ b/src/main/java/org/antlr/intellij/plugin/psi/MyPsiUtils.java
@@ -17,6 +17,9 @@
import org.antlr.intellij.plugin.ANTLRv4TokenTypes;
import org.antlr.intellij.plugin.parser.ANTLRv4Parser;
import org.jetbrains.annotations.Nullable;
+import static org.antlr.intellij.plugin.parser.ANTLRv4Parser.RULE_identifier;
+import static org.antlr.intellij.plugin.parser.ANTLRv4Parser.RULE_optionValue;
+import static org.antlr.intellij.plugin.parser.ANTLRv4Parser.RULE_grammarType;
import java.util.ArrayList;
import java.util.Arrays;
@@ -188,6 +191,37 @@ public static String findTokenVocabIfAny(ANTLRv4FileRoot file) {
return vocabName;
}
+ public static Boolean isGrammarName(PsiElement element) {
+
+ PsiElement parent = element.getParent();
+
+ if (parent == null || !isRuleType(parent, RULE_identifier)) return false;
+
+ if (parent.getParent() != null && isRuleType(parent.getParent(), RULE_optionValue)) {
+ return true;
+ }
+
+ if (parent.getPrevSibling() != null && parent.getPrevSibling().getPrevSibling() != null) {
+ return isRuleType(parent.getPrevSibling().getPrevSibling(), RULE_grammarType);
+ }
+
+ return false;
+
+ }
+
+ public static Boolean isGrammarNameVocabOption(PsiElement element) {
+ if (!isGrammarName(element)) return false;
+ PsiElement parent = element.getParent();
+ return (parent.getParent() != null && isRuleType(parent.getParent(), RULE_optionValue));
+ }
+
+ public static Boolean isRuleType(PsiElement element,int type){
+ return element
+ .getNode()
+ .getElementType()
+ .equals(ANTLRv4TokenTypes.getRuleElementType(type));
+ }
+
public static PsiElement findElement(PsiElement startNode, int offset) {
PsiElement p = startNode;
if ( p==null ) return null;
diff --git a/src/main/java/org/antlr/intellij/plugin/refactor/GrammarRenameInputValidator.java b/src/main/java/org/antlr/intellij/plugin/refactor/GrammarRenameInputValidator.java
new file mode 100644
index 00000000..c8fbb14a
--- /dev/null
+++ b/src/main/java/org/antlr/intellij/plugin/refactor/GrammarRenameInputValidator.java
@@ -0,0 +1,22 @@
+package org.antlr.intellij.plugin.refactor;
+
+import com.intellij.patterns.ElementPattern;
+import com.intellij.psi.PsiElement;
+import com.intellij.refactoring.rename.RenameInputValidator;
+import com.intellij.util.ProcessingContext;
+import org.antlr.intellij.plugin.ANTLRv4FileRoot;
+import org.jetbrains.annotations.NotNull;
+import static com.intellij.patterns.PlatformPatterns.psiElement;
+
+public class GrammarRenameInputValidator implements RenameInputValidator {
+
+ @Override
+ public @NotNull ElementPattern extends PsiElement> getPattern() {
+ return psiElement(ANTLRv4FileRoot.class);
+ }
+
+ @Override
+ public boolean isInputValid(@NotNull String newName, PsiElement element, ProcessingContext context) {
+ return !newName.contains(" ");
+ }
+}
diff --git a/src/main/java/org/antlr/intellij/plugin/refactor/RenameGrammarProcessor.java b/src/main/java/org/antlr/intellij/plugin/refactor/RenameGrammarProcessor.java
new file mode 100644
index 00000000..15423a5b
--- /dev/null
+++ b/src/main/java/org/antlr/intellij/plugin/refactor/RenameGrammarProcessor.java
@@ -0,0 +1,79 @@
+package org.antlr.intellij.plugin.refactor;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiNamedElement;
+import com.intellij.refactoring.listeners.RefactoringElementListener;
+import com.intellij.refactoring.rename.RenamePsiElementProcessor;
+import com.intellij.usageView.UsageInfo;
+import com.intellij.util.IncorrectOperationException;
+import org.antlr.intellij.plugin.ANTLRv4FileRoot;
+import org.antlr.intellij.plugin.psi.GrammarElementRefNode;
+import org.antlr.intellij.plugin.psi.MyPsiUtils;
+import org.antlr.intellij.plugin.resolve.TokenVocabResolver;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import java.io.IOException;
+
+public class RenameGrammarProcessor extends RenamePsiElementProcessor {
+
+ @Override
+ public boolean canProcessElement(@NotNull PsiElement psiElement) {
+ return (psiElement instanceof GrammarElementRefNode) ||
+ (psiElement instanceof ANTLRv4FileRoot);
+ }
+
+ @Override
+ public boolean isToSearchInComments(@NotNull PsiElement element) {
+ return false;
+ }
+
+ @Override
+ public boolean isToSearchForTextOccurrences(@NotNull PsiElement element) {
+ return false;
+ }
+
+ @Override
+ public void renameElement(@NotNull PsiElement element, @NotNull String newName, UsageInfo @NotNull [] usages, @Nullable RefactoringElementListener listener) throws IncorrectOperationException {
+
+ if (element instanceof ANTLRv4FileRoot) {
+ ANTLRv4FileRoot root = (ANTLRv4FileRoot) element;
+ String oldName = root.getName();
+ root.setName(newName);
+ ((PsiNamedElement) root.getChildren()[0].getChildren()[1].getFirstChild()).setName(newName);
+
+ // Rename the token-vocab file if found
+ VirtualFile tokenVocabFile = TokenVocabResolver.findRelativeTokenFile(oldName, root);
+ if (tokenVocabFile != null) {
+ try {
+ tokenVocabFile.rename(this, newName + ".tokens");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ } else if (element instanceof GrammarElementRefNode &&
+ MyPsiUtils.isGrammarName(element) &&
+ !MyPsiUtils.isGrammarNameVocabOption(element)) {
+
+ GrammarElementRefNode node = (GrammarElementRefNode) element;
+ PsiFile file = node.getContainingFile();
+ String fileName = file.getName();
+ int dotIndex = fileName.lastIndexOf('.');
+ file.setName(dotIndex >= 0 ? newName + "." + fileName.substring(dotIndex + 1) : newName);
+ node.replaceWithText(newName);
+
+ } else {
+ super.renameElement(element, newName, usages, listener);
+ }
+
+ for (UsageInfo usage : usages) {
+ if (MyPsiUtils.isGrammarNameVocabOption(usage.getElement())) {
+ super.renameElement(element, newName, usages, listener);
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/org/antlr/intellij/plugin/resolve/TokenVocabResolver.java b/src/main/java/org/antlr/intellij/plugin/resolve/TokenVocabResolver.java
index 9ccc3b96..c461b4d6 100644
--- a/src/main/java/org/antlr/intellij/plugin/resolve/TokenVocabResolver.java
+++ b/src/main/java/org/antlr/intellij/plugin/resolve/TokenVocabResolver.java
@@ -1,6 +1,8 @@
package org.antlr.intellij.plugin.resolve;
import com.intellij.lang.ASTNode;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
@@ -89,4 +91,9 @@ static PsiFile findRelativeFile(String baseName, PsiFile sibling) {
return null;
}
+
+ public static VirtualFile findRelativeTokenFile(String baseName, ANTLRv4FileRoot file) {
+ return VfsUtil.findRelativeFile(file.getVirtualFile().getParent(), baseName + ".tokens");
+ }
+
}
diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml
index 81dbfc26..a23d5f20 100644
--- a/src/main/resources/META-INF/plugin.xml
+++ b/src/main/resources/META-INF/plugin.xml
@@ -168,5 +168,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/TestUtils.java b/src/test/java/org/antlr/intellij/plugin/TestUtils.java
index 7298b8f7..bd16abcb 100644
--- a/src/test/java/org/antlr/intellij/plugin/TestUtils.java
+++ b/src/test/java/org/antlr/intellij/plugin/TestUtils.java
@@ -24,9 +24,8 @@ public static void tearDownIgnoringObjectNotDisposedException(ThrowableRunnable<
StringWriter stringWriter = new StringWriter();
e.printStackTrace(new PrintWriter(stringWriter));
String stack = stringWriter.toString();
- System.out.println(e.getStackTrace()[0].getClassName());
if ( stack.contains("ANTLRv4PluginController.createToolWindows") ||
-
+ stack.contains("GrammarRenameTest") ||
stack.contains("Issue559") || stack.contains("Issue540") || stack.contains("Issue569") ||
stack.contains("org.antlr.intellij.plugin.preview.InputPanel.createPreviewEditor") ) {
return;
diff --git a/src/test/java/org/antlr/intellij/plugin/psi/GrammarElementRefTest.java b/src/test/java/org/antlr/intellij/plugin/psi/GrammarElementRefTest.java
index 4c8f29f2..ef02407c 100644
--- a/src/test/java/org/antlr/intellij/plugin/psi/GrammarElementRefTest.java
+++ b/src/test/java/org/antlr/intellij/plugin/psi/GrammarElementRefTest.java
@@ -118,7 +118,7 @@ public void testReferencesFromParserToLexer() {
assertResolvedMatches(LexerRuleSpecNode.class, element -> {
assertEquals("TOKEN1", element.getName());
assertEquals(66, element.getTextOffset());
- assertEquals("FooLexer.g4", element.getContainingFile().getName());
+ assertEquals("FooLexer", element.getContainingFile().getName());
});
moveCaret(85);
@@ -131,7 +131,7 @@ public void testReferencesFromParserToLexer() {
assertResolvedMatches(TokenSpecNode.class, element -> {
assertEquals("STRING", element.getName());
assertEquals(34, element.getTextOffset());
- assertEquals("FooLexer.g4", element.getContainingFile().getName());
+ assertEquals("FooLexer", element.getContainingFile().getName());
});
}
@@ -139,21 +139,21 @@ public void testReferencesToTokenVocabFile() {
myFixture.configureByFiles("FooParser.g4", "FooLexer.g4");
moveCaret(55);
- assertResolvedMatches(ANTLRv4FileRoot.class, file -> assertEquals("FooLexer.g4", file.getName()));
+ assertResolvedMatches(ANTLRv4FileRoot.class, file -> assertEquals("FooLexer", file.getName()));
}
public void testReferencesToTokenVocabFileString() {
myFixture.configureByFiles("FooParser2.g4", "FooLexer.g4");
moveCaret(55);
- assertResolvedMatches(ANTLRv4FileRoot.class, file -> assertEquals("FooLexer.g4", file.getName()));
+ assertResolvedMatches(ANTLRv4FileRoot.class, file -> assertEquals("FooLexer", file.getName()));
}
public void testReferenceToImportedFile() {
myFixture.configureByFiles("importing.g4", "imported.g4");
moveCaret(35);
- assertResolvedMatches(ANTLRv4FileRoot.class, file -> assertEquals("imported.g4", file.getName()));
+ assertResolvedMatches(ANTLRv4FileRoot.class, file -> assertEquals("imported", file.getName()));
}
public void testReferenceToRuleInImportedFile() {
@@ -165,13 +165,13 @@ public void testReferenceToRuleInImportedFile() {
moveCaret(66);
assertResolvedMatches(LexerRuleSpecNode.class, node -> {
assertEquals("Bar", node.getName());
- assertEquals("imported2.g4", node.getContainingFile().getName());
+ assertEquals("imported2", node.getContainingFile().getName());
});
moveCaret(80);
assertResolvedMatches(LexerRuleSpecNode.class, node -> {
assertEquals("Baz", node.getName());
- assertEquals("imported3.g4", node.getContainingFile().getName());
+ assertEquals("imported3", node.getContainingFile().getName());
});
}
diff --git a/src/test/java/org/antlr/intellij/plugin/refactor/GrammarRenameTest.java b/src/test/java/org/antlr/intellij/plugin/refactor/GrammarRenameTest.java
new file mode 100644
index 00000000..c3f2ecd7
--- /dev/null
+++ b/src/test/java/org/antlr/intellij/plugin/refactor/GrammarRenameTest.java
@@ -0,0 +1,61 @@
+package org.antlr.intellij.plugin.refactor;
+
+import com.intellij.openapi.command.WriteCommandAction;
+import com.intellij.testFramework.fixtures.BasePlatformTestCase;
+import org.antlr.intellij.plugin.TestUtils;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Test;
+
+public class GrammarRenameTest extends BasePlatformTestCase {
+
+ @Test
+ public void test_validateInput() {
+ GrammarRenameInputValidator validator = new GrammarRenameInputValidator();
+ assertTrue(validator.isInputValid("foo", null, null));
+ assertFalse(validator.isInputValid("f oo", null, null));
+ }
+
+ @Test
+ public void test_renameFromLexerGrammar() {
+ testFilePaths("RenameLexer.g4", "RenameLexer.tokens", "RenameParser.g4");
+ }
+
+ @Test
+ public void test_renameGrammarFiles() {
+ testFilePaths("RenameParser.g4", "RenameLexer.tokens", "RenameLexer.g4");
+ }
+
+ @Test
+ public void test_renameFromFile() {
+ myFixture.configureByFiles("RenameLexer.g4", "RenameLexer.tokens", "RenameParser.g4");
+
+ // Rename the file
+ WriteCommandAction.runWriteCommandAction(getProject(), () ->
+ myFixture.renameElement(myFixture.getFile(), "RenameLexer2"));
+
+ assertNotNull(myFixture.findFileInTempDir("RenameLexer2.tokens"));
+ myFixture.checkResultByFile("RenameLexer2.g4", "RenameLexer-after.g4", false);
+ myFixture.checkResultByFile("RenameParser.g4", "RenameParser-after.g4", false);
+
+ }
+
+ private void testFilePaths(String @NotNull ... filePaths) {
+ myFixture.configureByFiles(filePaths);
+ myFixture.renameElementAtCaret("RenameLexer2");
+ assertNotNull(myFixture.findFileInTempDir("RenameLexer2.tokens"));
+ assertNotNull(myFixture.findFileInTempDir("RenameLexer2.g4"));
+ myFixture.checkResultByFile("RenameParser.g4", "RenameParser-after.g4", false);
+ myFixture.checkResultByFile("RenameLexer2.g4", "RenameLexer-after.g4", false);
+ }
+
+ @Override
+ protected String getTestDataPath() {
+ return "src/test/resources/rename";
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ TestUtils.tearDownIgnoringObjectNotDisposedException(() -> super.tearDown());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/resources/rename/RenameLexer-after.g4 b/src/test/resources/rename/RenameLexer-after.g4
new file mode 100644
index 00000000..4112026e
--- /dev/null
+++ b/src/test/resources/rename/RenameLexer-after.g4
@@ -0,0 +1,3 @@
+lexer grammar RenameLexer2;
+
+TOKEN1: 'TOKEN1';
diff --git a/src/test/resources/rename/RenameLexer.g4 b/src/test/resources/rename/RenameLexer.g4
new file mode 100644
index 00000000..ae0a98aa
--- /dev/null
+++ b/src/test/resources/rename/RenameLexer.g4
@@ -0,0 +1,3 @@
+lexer grammar RenameLexer;
+
+TOKEN1: 'TOKEN1';
diff --git a/src/test/resources/rename/RenameLexer.tokens b/src/test/resources/rename/RenameLexer.tokens
new file mode 100644
index 00000000..8c285dcf
--- /dev/null
+++ b/src/test/resources/rename/RenameLexer.tokens
@@ -0,0 +1,2 @@
+TOKEN1=2
+'TOKEN1'=2
diff --git a/src/test/resources/rename/RenameParser-after.g4 b/src/test/resources/rename/RenameParser-after.g4
new file mode 100644
index 00000000..3331470c
--- /dev/null
+++ b/src/test/resources/rename/RenameParser-after.g4
@@ -0,0 +1,7 @@
+parser grammar RenameParser;
+
+options {
+ tokenVocab=RenameLexer2;
+}
+
+myrule: TOKEN1;
diff --git a/src/test/resources/rename/RenameParser.g4 b/src/test/resources/rename/RenameParser.g4
new file mode 100644
index 00000000..bd95e7c3
--- /dev/null
+++ b/src/test/resources/rename/RenameParser.g4
@@ -0,0 +1,7 @@
+parser grammar RenameParser;
+
+options {
+ tokenVocab=RenameLexer;
+}
+
+myrule: TOKEN1;