blob: 621adf8cda3ef6bf23717ae33581fb31eaa4ed8a [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* 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 com.jetbrains.python.codeInsight.intentions;
import com.intellij.codeInsight.intention.IntentionAction;
import com.intellij.lang.ASTNode;
import com.intellij.lang.injection.InjectedLanguageManager;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.InputValidator;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiLanguageInjectionHost;
import com.intellij.psi.PsiReference;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static com.jetbrains.python.codeInsight.intentions.DeclarationConflictChecker.findDefinitions;
import static com.jetbrains.python.codeInsight.intentions.DeclarationConflictChecker.showConflicts;
import static com.jetbrains.python.psi.PyUtil.sure;
/**
* Adds an alias to "import foo" or "from foo import bar" import elements, or removes it if it's already present.
* User: dcheryasov
* Date: Oct 9, 2009 6:07:19 PM
*/
public class ImportToggleAliasIntention implements IntentionAction {
private static class IntentionState {
private PyImportElement myImportElement;
private PyFromImportStatement myFromImportStatement;
private PyImportStatement myImportStatement;
private String myAlias;
private static IntentionState fromContext(Editor editor, PsiFile file) {
IntentionState state = new IntentionState();
state.myImportElement = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyImportElement.class);
if (state.myImportElement != null && state.myImportElement.isValid()) {
PyTargetExpression target = state.myImportElement.getAsNameElement();
if (target != null && target.isValid()) state.myAlias = target.getName();
else state.myAlias = null;
state.myFromImportStatement = PsiTreeUtil.getParentOfType(state.myImportElement, PyFromImportStatement.class);
state.myImportStatement = PsiTreeUtil.getParentOfType(state.myImportElement, PyImportStatement.class);
}
return state;
}
public boolean isAvailable() {
if (myFromImportStatement != null) {
if (!myFromImportStatement.isValid() || myFromImportStatement.isFromFuture()) {
return false;
}
}
else {
if (myImportStatement == null || !myImportStatement.isValid()) {
return false;
}
}
final PyReferenceExpression referenceExpression = myImportElement.getImportReferenceExpression();
if (referenceExpression == null || referenceExpression.getReference().resolve() == null) {
return false;
}
return true;
}
public String getText() {
String add_name = "Add alias";
if (myImportElement != null) {
PyReferenceExpression refex = myImportElement.getImportReferenceExpression();
if (refex != null) {
add_name = PyBundle.message("INTN.add.alias.for.import.$0", refex.getText());
}
}
return myAlias == null? add_name : PyBundle.message("INTN.remove.alias.for.import.$0", myAlias);
}
}
private String myLastText;
@NotNull
public String getText() {
return myLastText;
}
@NotNull
public String getFamilyName() {
return PyBundle.message("INTN.Family.toggle.import.alias");
}
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
if (!(file instanceof PyFile)) {
return false;
}
IntentionState state = IntentionState.fromContext(editor, file);
myLastText = state.getText();
return state.isAvailable();
}
public void invoke(@NotNull final Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
// sanity check: isAvailable must have set it.
final IntentionState state = IntentionState.fromContext(editor, file);
//
final String target_name; // we set in in the source
final String remove_name; // we replace it in the source
PyReferenceExpression reference = sure(state.myImportElement.getImportReferenceExpression());
// search for references to us with the right name
try {
String imported_name = PyPsiUtils.toPath(reference);
if (state.myAlias != null) {
// have to remove alias, rename everything to original
target_name = imported_name;
remove_name = state.myAlias;
}
else {
// ask for and add alias
Application application = ApplicationManager.getApplication();
if (application != null && !application.isUnitTestMode()) {
String alias = Messages.showInputDialog(project, PyBundle.message("INTN.alias.for.$0.dialog.title", imported_name),
"Add Alias", Messages.getQuestionIcon(), "", new InputValidator() {
@Override
public boolean checkInput(String inputString) {
return PyNames.isIdentifier(inputString);
}
@Override
public boolean canClose(String inputString) {
return PyNames.isIdentifier(inputString);
}
});
if (alias == null) {
return;
}
target_name = alias;
}
else { // test mode
target_name = "alias";
}
remove_name = imported_name;
}
final PsiElement referee = reference.getReference().resolve();
if (referee != null && imported_name != null) {
final Collection<PsiReference> references = new ArrayList<PsiReference>();
final ScopeOwner scope = PsiTreeUtil.getParentOfType(state.myImportElement, ScopeOwner.class);
PsiTreeUtil.processElements(scope, new PsiElementProcessor() {
public boolean execute(@NotNull PsiElement element) {
getReferences(element);
if (element instanceof PyStringLiteralExpression) {
final PsiLanguageInjectionHost host = (PsiLanguageInjectionHost)element;
final List<Pair<PsiElement,TextRange>> files =
InjectedLanguageManager.getInstance(project).getInjectedPsiFiles(host);
if (files != null) {
for (Pair<PsiElement, TextRange> pair : files) {
final PsiElement first = pair.getFirst();
if (first instanceof ScopeOwner) {
final ScopeOwner scopeOwner = (ScopeOwner)first;
PsiTreeUtil.processElements(scopeOwner, new PsiElementProcessor() {
public boolean execute(@NotNull PsiElement element) {
getReferences(element);
return true;
}
});
}
}
}
}
return true;
}
private void getReferences(PsiElement element) {
if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType(element,
PyImportElement.class) == null) {
PyReferenceExpression ref = (PyReferenceExpression)element;
if (remove_name.equals(PyPsiUtils.toPath(ref))) { // filter out other names that might resolve to our target
PsiElement resolved = ref.getReference().resolve();
if (resolved == referee) references.add(ref.getReference());
}
}
}
});
// no references here is OK by us.
if (showConflicts(project, findDefinitions(target_name, references, null), target_name, null)) {
return; // got conflicts
}
// alter the import element
PyElementGenerator generator = PyElementGenerator.getInstance(project);
final LanguageLevel languageLevel = LanguageLevel.forElement(state.myImportElement);
if (state.myAlias != null) {
// remove alias
ASTNode node = sure(state.myImportElement.getNode());
ASTNode parent = sure(node.getTreeParent());
node = sure(node.getFirstChildNode()); // this is the reference
node = sure(node.getTreeNext()); // things past the reference: space, 'as', and alias
parent.removeRange(node, null);
}
else {
// add alias
ASTNode my_ielt_node = sure(state.myImportElement.getNode());
PyImportElement fountain = generator.createFromText(languageLevel, PyImportElement.class, "import foo as "+target_name, new int[]{0,2});
ASTNode graft_node = sure(fountain.getNode()); // at import elt
graft_node = sure(graft_node.getFirstChildNode()); // at ref
graft_node = sure(graft_node.getTreeNext()); // space
my_ielt_node.addChild((ASTNode)graft_node.clone());
graft_node = sure(graft_node.getTreeNext()); // 'as'
my_ielt_node.addChild((ASTNode)graft_node.clone());
graft_node = sure(graft_node.getTreeNext()); // space
my_ielt_node.addChild((ASTNode)graft_node.clone());
graft_node = sure(graft_node.getTreeNext()); // alias
my_ielt_node.addChild((ASTNode)graft_node.clone());
}
// alter references
for (PsiReference ref : references) {
ASTNode ref_name_node = sure(sure(ref.getElement()).getNode());
ASTNode parent = sure(ref_name_node.getTreeParent());
ASTNode new_name_node = generator.createExpressionFromText(languageLevel, target_name).getNode();
assert new_name_node != null;
parent.replaceChild(ref_name_node, new_name_node);
}
}
}
catch (IncorrectOperationException ignored) {
PyUtil.showBalloon(project, PyBundle.message("QFIX.action.failed"), MessageType.WARNING);
}
}
public boolean startInWriteAction() {
return true;
}
}