Package org.intellij.grammar.refactor

Source Code of org.intellij.grammar.refactor.BnfIntroduceTokenHandler

/*
* Copyright 2011-2014 Gregory Shrago
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.intellij.grammar.refactor;

import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.template.*;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.codeInsight.template.impl.TextExpression;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.command.impl.FinishMarkAction;
import com.intellij.openapi.command.impl.StartMarkAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.ScrollType;
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.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.introduce.inplace.OccurrencesChooser;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.text.UniqueNameGenerator;
import org.intellij.grammar.KnownAttribute;
import org.intellij.grammar.generator.ParserGeneratorUtil;
import org.intellij.grammar.generator.RuleGraphHelper;
import org.intellij.grammar.psi.*;
import org.intellij.grammar.psi.impl.BnfElementFactory;
import org.intellij.grammar.psi.impl.GrammarUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

/**
* @author greg
*/
public class BnfIntroduceTokenHandler implements RefactoringActionHandler {
  public static final String REFACTORING_NAME = "Introduce Token";

  @Override
  public void invoke(final @NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
    // do not support this case
  }

  @Override
  public void invoke(@NotNull final Project project,
                     final Editor editor,
                     final PsiFile file,
                     @Nullable DataContext dataContext) {
    if (!(file instanceof BnfFile)) return;
    final BnfFile bnfFile = (BnfFile) file;

    final Map<String, String> tokenMap = RuleGraphHelper.getTokenMap(bnfFile);

    final String tokenText;
    final String tokenName;
    BnfExpression target = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), BnfReferenceOrToken.class, BnfStringLiteralExpression.class);
    if (target instanceof BnfReferenceOrToken) {
      if (bnfFile.getRule(target.getText()) != null) return;
      if (GrammarUtil.isExternalReference(target)) return;
      tokenName = target.getText();
      String existingKey = null;
      for (String key : tokenMap.keySet()) {
        if (tokenName.equals(tokenMap.get(key))) {
          existingKey = key;
          break;
        }
      }
      tokenText = existingKey;
    }
    else if (target instanceof BnfStringLiteralExpression) {
      if (PsiTreeUtil.getParentOfType(target, BnfAttrs.class) != null) return;
      tokenText = target.getText();
      tokenName = tokenMap.get(StringUtil.unquoteString(tokenText));
    }
    else return;

    final ArrayList<BnfExpression> allOccurrences = new ArrayList<BnfExpression>();
    final LinkedHashMap<OccurrencesChooser.ReplaceChoice, List<BnfExpression>> occurrencesMap = new LinkedHashMap<OccurrencesChooser.ReplaceChoice, List<BnfExpression>>();
    occurrencesMap.put(OccurrencesChooser.ReplaceChoice.NO, Collections.singletonList(target));
    occurrencesMap.put(OccurrencesChooser.ReplaceChoice.ALL, allOccurrences);

    GrammarUtil.visitRecursively(file, true, new BnfVisitor() {
      @Override
      public void visitStringLiteralExpression(@NotNull BnfStringLiteralExpression o) {
        if (tokenText != null && tokenText.equals(o.getText())) {
          allOccurrences.add(o);
        }
      }

      @Override
      public void visitReferenceOrToken(@NotNull BnfReferenceOrToken o) {
        if (GrammarUtil.isExternalReference(o)) return;
        if (tokenName != null && tokenName.equals(o.getText())) {
          allOccurrences.add(o);
        }
      }
    });

    if (occurrencesMap.get(OccurrencesChooser.ReplaceChoice.ALL).size() <= 1 && !ApplicationManager.getApplication().isUnitTestMode()) {
      occurrencesMap.remove(OccurrencesChooser.ReplaceChoice.ALL);
    }

    final Pass<OccurrencesChooser.ReplaceChoice> callback = new Pass<OccurrencesChooser.ReplaceChoice>() {
      @Override
      public void pass(final OccurrencesChooser.ReplaceChoice choice) {
        new WriteCommandAction(project, REFACTORING_NAME, file) {
          @Override
          protected void run(com.intellij.openapi.application.Result result) throws Throwable {
            buildTemplateAndRun(project, editor, bnfFile, occurrencesMap.get(choice), tokenName, tokenText, tokenMap);
          }
        }.execute();
      }
    };
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      callback.pass(OccurrencesChooser.ReplaceChoice.ALL);
    }
    else {
      new OccurrencesChooser<BnfExpression>(editor) {
        @Override
        protected TextRange getOccurrenceRange(BnfExpression occurrence) {
          return occurrence.getTextRange();
        }
      }.showChooser(callback, occurrencesMap);
    }
  }

  private void buildTemplateAndRun(final Project project,
                                   final Editor editor,
                                   BnfFile bnfFile, List<BnfExpression> occurrences,
                                   String tokenName,
                                   String tokenText,
                                   Map<String, String> tokenMap) throws StartMarkAction.AlreadyStartedException {
    final StartMarkAction startAction = StartMarkAction.start(editor, project, REFACTORING_NAME);
    BnfListEntry entry = addTokenDefinition(project, bnfFile, tokenName, tokenText, tokenMap);
    PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());

    TemplateBuilderImpl builder = new TemplateBuilderImpl(bnfFile);
    PsiElement tokenId = ObjectUtils.assertNotNull(entry.getId());
    PsiElement tokenValue = ObjectUtils.assertNotNull(entry.getLiteralExpression());
    if (tokenName == null) {
      builder.replaceElement(tokenId, "TokenName", new TextExpression(tokenId.getText()), true);
    }
    builder.replaceElement(tokenValue, "TokenText", new TextExpression(tokenValue.getText()), true);

    for (BnfExpression occurrence : occurrences) {
      builder.replaceElement(occurrence, "Other", new Expression() {

        @Nullable
        @Override
        public Result calculateResult(ExpressionContext context) {
          TemplateState state = TemplateManagerImpl.getTemplateState(context.getEditor());
          assert state != null;
          TextResult text = ObjectUtils.assertNotNull(state.getVariableValue("TokenText"));
          String curText = StringUtil.unquoteString(text.getText());
          if (ParserGeneratorUtil.isRegexpToken(curText)) {
            return state.getVariableValue("TokenName");
          }
          else {
            return new TextResult("'" + curText + "'");
          }
        }

        @Nullable
        @Override
        public Result calculateQuickResult(ExpressionContext context) {
          return calculateResult(context);
        }

        @Nullable
        @Override
        public LookupElement[] calculateLookupItems(ExpressionContext context) {
          return LookupElement.EMPTY_ARRAY;
        }
      }, false);
    }
    final RangeMarker caretMarker = editor.getDocument().createRangeMarker(0, editor.getCaretModel().getOffset());
    caretMarker.setGreedyToRight(true);
    editor.getCaretModel().moveToOffset(0);
    Template template = builder.buildInlineTemplate();
    template.setToShortenLongNames(false);
    template.setToReformat(false);
    TemplateManager.getInstance(project).startTemplate(editor, template, new TemplateEditingAdapter() {

      @Override
      public void templateFinished(Template template, boolean brokenOff) {
        handleTemplateFinished(project, editor, caretMarker, startAction);
      }

      @Override
      public void templateCancelled(Template template) {
        handleTemplateFinished(project, editor, caretMarker, startAction);
      }
    });
  }

  private void handleTemplateFinished(Project project,
                                      Editor editor,
                                      RangeMarker caretMarker,
                                      StartMarkAction startAction) {
    try {
      editor.getCaretModel().moveToOffset(caretMarker.getEndOffset());
      editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
    }
    finally {
      FinishMarkAction.finish(project, editor, startAction);
    }
  }

  private static BnfListEntry addTokenDefinition(Project project,
                                                 BnfFile bnfFile,
                                                 String tokenName,
                                                 String tokenText,
                                                 Map<String, String> tokenMap) {
    String fixedTokenName = new UniqueNameGenerator(tokenMap.values(), null).generateUniqueName(StringUtil.notNullize(tokenName, "token"));
    String newAttrText = "tokens = [\n    " + fixedTokenName + "=" + StringUtil.notNullize(tokenText, "\"\"") + "\n  ]";
    BnfAttr newAttr = BnfElementFactory.createAttributeFromText(project, newAttrText);
    BnfAttrs attrs = ContainerUtil.getFirstItem(bnfFile.getAttributes());
    BnfAttr tokensAttr = null;
    if (attrs == null) {
      attrs = (BnfAttrs) bnfFile.addAfter(newAttr.getParent(), null);
      tokensAttr = attrs.getAttrList().get(0);
      return ((BnfValueList) tokensAttr.getExpression()).getListEntryList().get(0);
    }
    else {
      for (BnfAttr attr : attrs.getAttrList()) {
        if (KnownAttribute.TOKENS.getName().equals(attr.getName())) {
          tokensAttr = attr;
        }
      }
      if (tokensAttr == null) {
        List<BnfAttr> attrList = attrs.getAttrList();
        PsiElement anchor = attrList.isEmpty() ? attrs.getFirstChild() : attrList.get(attrList.size() - 1);
        newAttr = (BnfAttr) attrs.addAfter(newAttr, anchor);
        attrs.addAfter(BnfElementFactory.createLeafFromText(project, "\n  "), anchor);
        return ((BnfValueList) newAttr.getExpression()).getListEntryList().get(0);
      }
      else {
        BnfExpression expression = tokensAttr.getExpression();
        List<BnfListEntry> entryList = expression instanceof BnfValueList ? ((BnfValueList) expression).getListEntryList() : null;
        if (entryList == null || entryList.isEmpty()) {
          expression.replace(newAttr.getParent());
          return ((BnfValueList) tokensAttr.getExpression()).getListEntryList().get(0);
        }
        else {
          for (BnfListEntry entry : entryList) {
            PsiElement id = entry.getId();
            if (id != null && id.getText().equals(tokenName)) {
              return entry;
            }
          }
          BnfListEntry newValue = ((BnfValueList) newAttr.getExpression()).getListEntryList().get(0);
          PsiElement anchor = entryList.get(entryList.size() - 1);
          newValue = (BnfListEntry) expression.addAfter(newValue, anchor);
          expression.addAfter(BnfElementFactory.createLeafFromText(project, "\n    "), anchor);
          return newValue;
        }
      }
    }
  }

}
TOP

Related Classes of org.intellij.grammar.refactor.BnfIntroduceTokenHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.