| /* |
| * 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.imports; |
| |
| import com.intellij.codeInsight.FileModificationService; |
| import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass; |
| import com.intellij.codeInsight.hint.HintManager; |
| import com.intellij.codeInsight.intention.HighPriorityAction; |
| import com.intellij.codeInspection.LocalQuickFix; |
| import com.intellij.codeInspection.ProblemDescriptor; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.ProjectFileIndex; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiFileSystemItem; |
| import com.intellij.psi.PsiReference; |
| import com.intellij.psi.util.QualifiedName; |
| import com.intellij.util.IncorrectOperationException; |
| import com.jetbrains.python.PyBundle; |
| import com.jetbrains.python.codeInsight.PyCodeInsightSettings; |
| import com.jetbrains.python.psi.PyElement; |
| import com.jetbrains.python.psi.PyFunction; |
| import com.jetbrains.python.psi.PyImportElement; |
| import com.jetbrains.python.psi.PyQualifiedExpression; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| /** |
| * The object contains a list of import candidates and serves only to show the initial hint; |
| * the actual work is done in ImportFromExistingAction.. |
| * |
| * @author dcheryasov |
| */ |
| public class AutoImportQuickFix implements LocalQuickFix, HighPriorityAction { |
| |
| private final PyElement myNode; |
| private final PsiReference myReference; |
| |
| private final List<ImportCandidateHolder> myImports; // from where and what to import |
| private final String myInitialName; |
| |
| boolean myUseQualifiedImport; |
| private boolean myExpended; |
| |
| /** |
| * Creates a new, empty fix object. |
| * @param node to which the fix applies. |
| * @param qualify if true, add an "import ..." statement and qualify the name; else use "from ... import name" |
| */ |
| public AutoImportQuickFix(PyElement node, PsiReference reference, boolean qualify) { |
| myNode = node; |
| myReference = reference; |
| myImports = new ArrayList<ImportCandidateHolder>(); |
| myInitialName = getNameToImport(); |
| myUseQualifiedImport = qualify; |
| myExpended = false; |
| } |
| |
| /** |
| * Adds another import source. |
| * @param importable an element that could be imported either from import element or from file. |
| * @param file the file which is the source of the importable |
| * @param importElement an existing import element that can be a source for the importable. |
| */ |
| public void addImport(@NotNull PsiElement importable, @NotNull PsiFile file, @Nullable PyImportElement importElement) { |
| myImports.add(new ImportCandidateHolder(importable, file, importElement, null)); |
| } |
| |
| /** |
| * Adds another import source. |
| * @param importable an element that could be imported either from import element or from file. |
| * @param file the file which is the source of the importable |
| * @param path import path for the file, as a qualified name (a.b.c) |
| */ |
| public void addImport(@NotNull PsiElement importable, @NotNull PsiFileSystemItem file, @Nullable QualifiedName path) { |
| myImports.add(new ImportCandidateHolder(importable, file, null, path)); |
| } |
| |
| @NotNull |
| public String getText() { |
| if (myUseQualifiedImport) return PyBundle.message("ACT.qualify.with.module"); |
| else if (myImports.size() == 1) { |
| return "Import '" + myImports.get(0).getPresentableText(getNameToImport()) + "'"; |
| } |
| else { |
| return PyBundle.message("ACT.NAME.use.import"); |
| } |
| } |
| |
| @NotNull |
| public String getName() { |
| return getText(); |
| } |
| |
| @NotNull |
| public String getFamilyName() { |
| return PyBundle.message("ACT.FAMILY.import"); |
| } |
| |
| public boolean showHint(Editor editor) { |
| if (!PyCodeInsightSettings.getInstance().SHOW_IMPORT_POPUP) { |
| return false; |
| } |
| if (myNode == null || !myNode.isValid() || myNode.getName() == null || myImports.size() <= 0) { |
| return false; // TODO: also return false if an on-the-fly unambiguous fix is possible? |
| } |
| if (ImportFromExistingAction.isResolved(myReference)) { |
| return false; |
| } |
| if (HintManager.getInstance().hasShownHintsThatWillHideByOtherHint(true)) { |
| return false; |
| } |
| if ((myNode instanceof PyQualifiedExpression) && ((((PyQualifiedExpression)myNode).isQualified()))) return false; // we cannot be qualified |
| String name = getNameToImport(); |
| if (!name.equals(myInitialName)) { |
| return false; |
| } |
| final String message = ShowAutoImportPass.getMessage( |
| myImports.size() > 1, |
| ImportCandidateHolder.getQualifiedName(name, myImports.get(0).getPath(), myImports.get(0).getImportElement()) |
| ); |
| final ImportFromExistingAction action = new ImportFromExistingAction(myNode, myImports, name, myUseQualifiedImport, false); |
| action.onDone(new Runnable() { |
| public void run() { |
| myExpended = true; |
| } |
| }); |
| HintManager.getInstance().showQuestionHint( |
| editor, message, |
| myNode.getTextOffset(), |
| myNode.getTextRange().getEndOffset(), action); |
| return true; |
| } |
| |
| public boolean isAvailable() { |
| return !myExpended && myNode != null && myNode.isValid() && myImports.size() > 0; |
| } |
| |
| public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { |
| invoke(descriptor.getPsiElement().getContainingFile()); |
| myExpended = true; |
| } |
| |
| public void invoke(PsiFile file) throws IncorrectOperationException { |
| // make sure file is committed, writable, etc |
| if (!myReference.getElement().isValid()) return; |
| if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; |
| if (ImportFromExistingAction.isResolved(myReference)) return; |
| // act |
| ImportFromExistingAction action = createAction(); |
| action.execute(); // assume that action runs in WriteAction on its own behalf |
| myExpended = true; |
| } |
| |
| @NotNull |
| protected ImportFromExistingAction createAction() { |
| return new ImportFromExistingAction(myNode, myImports, getNameToImport(), myUseQualifiedImport, false); |
| } |
| |
| public void sortCandidates() { |
| Collections.sort(myImports); |
| } |
| |
| public int getCandidatesCount() { |
| return myImports.size(); |
| } |
| |
| public boolean hasOnlyFunctions() { |
| for (ImportCandidateHolder holder : myImports) { |
| if (!(holder.getImportable() instanceof PyFunction)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public String getNameToImport() { |
| final String text = myReference.getElement().getText(); |
| return myReference.getRangeInElement().substring(text); // text of the part we're working with |
| } |
| |
| public boolean hasProjectImports() { |
| ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(myNode.getProject()); |
| for (ImportCandidateHolder anImport : myImports) { |
| VirtualFile file = anImport.getFile().getVirtualFile(); |
| if (file != null && fileIndex.isInContent(file)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @NotNull |
| public AutoImportQuickFix forLocalImport() { |
| return new AutoImportQuickFix(myNode, myReference, myUseQualifiedImport) { |
| @NotNull |
| @Override |
| public String getName() { |
| return super.getName() + " locally"; |
| } |
| |
| @NotNull |
| @Override |
| public String getFamilyName() { |
| return "import locally"; |
| } |
| |
| @NotNull |
| @Override |
| protected ImportFromExistingAction createAction() { |
| return new ImportFromExistingAction(myNode, myImports, getNameToImport(), myUseQualifiedImport, true); |
| } |
| }; |
| } |
| } |