Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save ignatov/4241249 to your computer and use it in GitHub Desktop.
Save ignatov/4241249 to your computer and use it in GitHub Desktop.
better rule introduction
Index: support/org/intellij/grammar/refactor/BnfIntroduceRuleHandler.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP
<+>/*\n * Copyright 2011-2011 Gregory Shrago\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.intellij.grammar.refactor;\n\nimport com.intellij.openapi.actionSystem.DataContext;\nimport com.intellij.openapi.application.ApplicationManager;\nimport com.intellij.openapi.command.WriteCommandAction;\nimport com.intellij.openapi.editor.Editor;\nimport com.intellij.openapi.editor.SelectionModel;\nimport com.intellij.openapi.project.Project;\nimport com.intellij.openapi.util.Pass;\nimport com.intellij.openapi.util.TextRange;\nimport com.intellij.psi.*;\nimport com.intellij.psi.util.PsiTreeUtil;\nimport com.intellij.refactoring.RefactoringActionHandler;\nimport com.intellij.refactoring.introduce.inplace.OccurrencesChooser;\nimport gnu.trove.THashSet;\nimport org.intellij.grammar.generator.ParserGeneratorUtil;\nimport org.intellij.grammar.psi.*;\nimport org.intellij.grammar.psi.impl.BnfElementFactory;\nimport org.intellij.grammar.psi.impl.BnfFileImpl;\nimport org.intellij.grammar.psi.impl.GrammarUtil;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\n\nimport java.util.*;\n\n/**\n * Created by IntelliJ IDEA.\n * Date: 8/16/11\n * Time: 5:25 PM\n *\n * @author Vadim Romansky\n * @author gregsh\n */\npublic class BnfIntroduceRuleHandler implements RefactoringActionHandler {\n public static final String REFACTORING_NAME = \"Extract Rule\";\n\n @Override\n public void invoke(final @NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {\n // do not support this case\n }\n\n @Override\n public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, @Nullable DataContext dataContext) {\n if (!(file instanceof BnfFileImpl)) return;\n\n final BnfFileImpl bnfFile = (BnfFileImpl)file;\n final SelectionModel selectionModel = editor.getSelectionModel();\n int[] starts = selectionModel.getBlockSelectionStarts();\n int[] ends = selectionModel.getBlockSelectionEnds();\n if (starts.length == 0) return;\n\n int startOffset = starts[0];\n int endOffset = ends[ends.length-1];\n final BnfRule currentRule = PsiTreeUtil.getParentOfType(file.findElementAt(startOffset), BnfRule.class);\n if (currentRule == null) return;\n final BnfExpression parentExpression = findParentExpression(bnfFile, startOffset, endOffset-1);\n if (parentExpression == null) return;\n final List<BnfExpression> selectedExpression = findSelectedExpressionsInRange(parentExpression, new TextRange(startOffset, endOffset));\n if (selectedExpression.isEmpty()) return;\n final TextRange fixedRange = new TextRange(selectedExpression.get(0).getTextRange().getStartOffset(), selectedExpression.get(selectedExpression.size()-1).getTextRange().getEndOffset());\n final BnfRule ruleFromText = BnfElementFactory.createRuleFromText(file.getProject(), \"a ::= \" + fixedRange.substring(file.getText()));\n BnfExpressionOptimizer.optimize(ruleFromText.getExpression());\n\n final LinkedHashMap<OccurrencesChooser.ReplaceChoice, List<BnfExpression[]>> occurrencesMap = new LinkedHashMap<OccurrencesChooser.ReplaceChoice, List<BnfExpression[]>>();\n occurrencesMap.put(OccurrencesChooser.ReplaceChoice.NO, Collections.singletonList(selectedExpression.toArray(new BnfExpression[selectedExpression.size()])));\n occurrencesMap.put(OccurrencesChooser.ReplaceChoice.ALL, new ArrayList<BnfExpression[]>());\n file.acceptChildren(new PsiRecursiveElementWalkingVisitor() {\n @Override\n public void visitElement(PsiElement element) {\n if (element instanceof BnfExpression) {\n findOccurrences((BnfExpression)element, selectedExpression, occurrencesMap);\n }\n else if (element instanceof BnfAttrs) {\n return;\n }\n super.visitElement(element);\n }\n });\n if (occurrencesMap.get(OccurrencesChooser.ReplaceChoice.ALL).size() <= 1 && !ApplicationManager.getApplication().isUnitTestMode()) {\n occurrencesMap.remove(OccurrencesChooser.ReplaceChoice.ALL);\n }\n \n final Pass<OccurrencesChooser.ReplaceChoice> callback = new Pass<OccurrencesChooser.ReplaceChoice>() {\n @Override\n public void pass(final OccurrencesChooser.ReplaceChoice choice) {\n new WriteCommandAction.Simple(project, REFACTORING_NAME, file) {\n @Override\n public void run() {\n final PsiFile containingFile = currentRule.getContainingFile();\n String newRuleName = choseRuleName(containingFile);\n String newRuleText = \"private \" + newRuleName + \" ::= \" + ruleFromText.getExpression().getText();\n BnfRule addedRule = addNextRule(project, currentRule, newRuleText);\n if (choice == OccurrencesChooser.ReplaceChoice.ALL) {\n List<BnfExpression[]> exprToReplace = occurrencesMap.get(OccurrencesChooser.ReplaceChoice.ALL);\n replaceUsages(project, exprToReplace, addedRule.getId());\n }\n else {\n List<BnfExpression[]> exprToReplace = occurrencesMap.get(OccurrencesChooser.ReplaceChoice.NO);\n replaceUsages(project, exprToReplace, addedRule.getId());\n }\n final BnfIntroduceRulePopup popup = new BnfIntroduceRulePopup(project, editor, addedRule, addedRule.getExpression());\n\n editor.getCaretModel().moveToOffset(addedRule.getTextOffset());\n PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());\n popup.performInplaceRefactoring(null);\n }\n }.execute();\n }\n };\n if(ApplicationManager.getApplication().isUnitTestMode()){\n callback.pass(OccurrencesChooser.ReplaceChoice.ALL);\n } else {\n new OccurrencesChooser<BnfExpression[]>(editor){\n @Override\n protected TextRange getOccurrenceRange(BnfExpression[] occurrence) {\n return new TextRange(occurrence[0].getTextRange().getStartOffset(), occurrence[occurrence.length-1].getTextRange().getEndOffset());\n }\n }.showChooser(callback, occurrencesMap);\n }\n }\n\n public static BnfRule addNextRule(Project project, BnfRule currentRule, String newRuleText) {\n BnfRule addedRule = (BnfRule)currentRule.getParent().addAfter(BnfElementFactory.createRuleFromText(project, newRuleText), currentRule);\n currentRule.getParent().addBefore(BnfElementFactory.createLeafFromText(project, \"\\n\"), addedRule);\n if (endsWithSemicolon(currentRule)) {\n addedRule.addBefore(BnfElementFactory.createLeafFromText(project, \";\"), null);\n if (currentRule.getNextSibling() instanceof PsiWhiteSpace) {\n currentRule.getParent().addAfter(BnfElementFactory.createLeafFromText(project, \"\\n\"), addedRule);\n }\n }\n return addedRule;\n }\n\n public static boolean endsWithSemicolon(BnfRule rule) {\n return rule.getLastChild().getNode().getElementType() == BnfTypes.BNF_SEMICOLON;\n }\n\n private static List<BnfExpression> findSelectedExpressionsInRange(BnfExpression parentExpression, TextRange range) {\n if (parentExpression.getTextRange().equals(range)) {\n if (parentExpression instanceof BnfSequence) return ((BnfSequence)parentExpression).getExpressionList();\n if (parentExpression instanceof BnfChoice) return ((BnfChoice)parentExpression).getExpressionList();\n return Collections.singletonList(parentExpression);\n }\n LinkedList<BnfExpression> list = new LinkedList<BnfExpression>();\n for (PsiElement c = parentExpression.getFirstChild(); c != null; c = c.getNextSibling()) {\n if (c instanceof PsiWhiteSpace) continue;\n if (c.getTextRange().intersectsStrict(range)) {\n if (c instanceof BnfExpression) {\n list.add((BnfExpression)c);\n }\n else if (c == parentExpression.getFirstChild() || c == parentExpression.getLastChild()) {\n return Collections.singletonList(parentExpression);\n }\n }\n }\n return list;\n }\n\n private static void replaceUsages(Project project, List<BnfExpression[]> exprToReplace, PsiElement id) {\n for (BnfExpression[] expression : exprToReplace) {\n replaceExpression(project, expression, id);\n }\n }\n\n private static void replaceExpression(Project project, BnfExpression[] oldExpression, PsiElement id) {\n PsiElement parent = oldExpression[0].getParent();\n parent.addBefore(BnfElementFactory.createRuleFromText(project, \"a::=\"+id.getText()).getExpression(), oldExpression[0]);\n parent.deleteChildRange(oldExpression[0], oldExpression[oldExpression.length - 1]);\n //BnfExpressionOptimizer.optimize(parent);\n }\n\n private static void findOccurrences(BnfExpression expression,\n List<BnfExpression> selectedExpressions,\n Map<OccurrencesChooser.ReplaceChoice, List<BnfExpression[]>> occurrencesMap) {\n if (selectedExpressions.size() == 1) {\n if (GrammarUtil.equalsElement(expression, selectedExpressions.get(0))) {\n addOccurrence(OccurrencesChooser.ReplaceChoice.ALL, occurrencesMap, expression);\n }\n }\n else if (!GrammarUtil.isOneTokenExpression(expression)) {\n final PsiElement selectedParent = selectedExpressions.get(0).getParent();\n if (ParserGeneratorUtil.getEffectiveType(expression) != ParserGeneratorUtil.getEffectiveType(selectedParent)) return; \n int pos = 0;\n BnfExpression[] result = new BnfExpression[selectedExpressions.size()];\n for (PsiElement c = expression.getFirstChild(), s = null; c != null; c = c.getNextSibling()) {\n if (!(c instanceof BnfExpression)) continue;\n if (GrammarUtil.equalsElement((BnfExpression)c, selectedExpressions.get(pos))) {\n if (pos == 0) s = c;\n result[pos] = (BnfExpression)c;\n if (++ pos == result.length) {\n addOccurrence(OccurrencesChooser.ReplaceChoice.ALL, occurrencesMap, result.clone());\n pos = 0;\n }\n }\n else if (s != null) {\n c = s;\n pos = 0;\n s = null;\n }\n }\n }\n }\n\n private static void addOccurrence(OccurrencesChooser.ReplaceChoice choice,\n Map<OccurrencesChooser.ReplaceChoice, List<BnfExpression[]>> occurrencesMap,\n BnfExpression... expressions) {\n List<BnfExpression[]> list = occurrencesMap.get(choice);\n if (list == null) occurrencesMap.put(choice, list = new LinkedList<BnfExpression[]>());\n list.add(expressions);\n }\n\n private static String choseRuleName(PsiFile containingFile) {\n final Set<String> existingNames = new THashSet<String>();\n containingFile.accept(new PsiRecursiveElementWalkingVisitor() {\n @Override\n public void visitElement(PsiElement element) {\n if (element instanceof BnfAttrs) return;\n if (element instanceof BnfReferenceOrToken) {\n existingNames.add(((BnfReferenceOrToken)element).getId().getText());\n }\n else if (element instanceof BnfRule) {\n existingNames.add(((BnfRule)element).getId().getText());\n }\n super.visitElement(element);\n }\n });\n String name = \"rule\";\n for (int i = 1; existingNames.contains(name); i++) {\n name = \"rule\" + i;\n }\n return name;\n }\n\n @Nullable\n private static BnfExpression findParentExpression(PsiFile file, int startOffset, int endOffset) {\n final PsiElement startElement = file.findElementAt(startOffset);\n final PsiElement endElement = file.findElementAt(endOffset);\n if (startElement == null || endElement == null) return null;\n final PsiElement commonParent = PsiTreeUtil.findCommonParent(startElement, endElement);\n return PsiTreeUtil.getParentOfType(commonParent, BnfExpression.class, false);\n }\n}\n
===================================================================
--- support/org/intellij/grammar/refactor/BnfIntroduceRuleHandler.java (revision 9f189511ee6b5de517389db375e181abf9118800)
+++ support/org/intellij/grammar/refactor/BnfIntroduceRuleHandler.java (revision )
@@ -24,6 +24,7 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pass;
import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringActionHandler;
@@ -103,7 +104,10 @@
@Override
public void run() {
final PsiFile containingFile = currentRule.getContainingFile();
- String newRuleName = choseRuleName(containingFile);
+ InitializerTextBuilder builder = new InitializerTextBuilder();
+ ruleFromText.getExpression().accept(builder);
+ String newRuleName = ApplicationManager.getApplication().isUnitTestMode() ? "rule" : builder.result();
+ newRuleName = choseRuleName(containingFile, newRuleName);
String newRuleText = "private " + newRuleName + " ::= " + ruleFromText.getExpression().getText();
BnfRule addedRule = addNextRule(project, currentRule, newRuleText);
if (choice == OccurrencesChooser.ReplaceChoice.ALL) {
@@ -225,7 +229,7 @@
list.add(expressions);
}
- private static String choseRuleName(PsiFile containingFile) {
+ private static String choseRuleName(PsiFile containingFile, String newRuleName) {
final Set<String> existingNames = new THashSet<String>();
containingFile.accept(new PsiRecursiveElementWalkingVisitor() {
@Override
@@ -240,9 +244,9 @@
super.visitElement(element);
}
});
- String name = "rule";
+ String name = newRuleName;
for (int i = 1; existingNames.contains(name); i++) {
- name = "rule" + i;
+ name = newRuleName + i;
}
return name;
}
@@ -254,5 +258,49 @@
if (startElement == null || endElement == null) return null;
final PsiElement commonParent = PsiTreeUtil.findCommonParent(startElement, endElement);
return PsiTreeUtil.getParentOfType(commonParent, BnfExpression.class, false);
+ }
+
+ private static class InitializerTextBuilder extends PsiRecursiveElementVisitor {
+ private List<String> myResult = new ArrayList<String>();
+
+ @Override
+ public void visitWhiteSpace(@NotNull PsiWhiteSpace space) {
+// myResult.add(space.getText().replace('\n', ' '));
+ }
+
+ @Override
+ public void visitElement(@NotNull PsiElement element) {
+ if (element.getChildren().length == 0) {
+ String e = StringUtil.toLowerCase(element.getText().trim());
+ boolean emptyOrSpaces = StringUtil.isEmptyOrSpaces(e);
+ if (!emptyOrSpaces) {
+ myResult.add(e);
+ }
+ return;
+ }
+ super.visitElement(element);
+ }
+
+ @NotNull
+ public String result() {
+ int count = 10;
+ if (myResult.size() > count) {
+ myResult = myResult.subList(0, count - 1);
+ }
+ String join = StringUtil.join(myResult, "_");
+ String s = join
+ .replaceAll("\\|", "_or")
+ .replaceAll("\\?", "_opt")
+ .replaceAll(" ", "_")
+ .replaceAll("]", "_opt")
+ .replaceAll("_*\\*", "s")
+ .replaceAll("_*\\+", "s")
+ .replaceAll("[^\\w]+", "_")
+ .replaceAll("_+", "_");
+ while (s.startsWith("_")) {
+ s = s.replaceFirst("_", "");
+ }
+ return s.isEmpty() ? "PlaceHolder" : s;
+ }
- }
+ }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment