| /* |
| * 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.lang.injection.InjectedLanguageManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.projectRoots.Sdk; |
| import com.intellij.openapi.roots.ProjectRootManager; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.QualifiedName; |
| import com.intellij.util.IncorrectOperationException; |
| import com.jetbrains.python.codeInsight.PyCodeInsightSettings; |
| import com.jetbrains.python.documentation.DocStringUtil; |
| import com.jetbrains.python.psi.*; |
| import com.jetbrains.python.psi.resolve.QualifiedNameFinder; |
| import com.jetbrains.python.sdk.PythonSdkType; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.List; |
| |
| import static com.jetbrains.python.psi.PyUtil.sure; |
| |
| /** |
| * Does the actual job of adding an import statement into a file. |
| * User: dcheryasov |
| * Date: Apr 24, 2009 3:17:59 AM |
| */ |
| public class AddImportHelper { |
| private static final Logger LOG = Logger.getInstance("#" + AddImportHelper.class.getName()); |
| |
| private AddImportHelper() { |
| } |
| |
| public static void addLocalImportStatement(@NotNull PyElement element, @NotNull String name) { |
| final PyElementGenerator generator = PyElementGenerator.getInstance(element.getProject()); |
| final LanguageLevel languageLevel = LanguageLevel.forElement(element); |
| |
| final PsiElement anchor = getLocalInsertPosition(element); |
| final PsiElement parentElement = sure(anchor).getParent(); |
| if (parentElement != null) { |
| parentElement.addBefore(generator.createImportStatement(languageLevel, name, null), anchor); |
| } |
| } |
| |
| public static void addLocalFromImportStatement(@NotNull PyElement element, @NotNull String qualifier, @NotNull String name) { |
| final PyElementGenerator generator = PyElementGenerator.getInstance(element.getProject()); |
| final LanguageLevel languageLevel = LanguageLevel.forElement(element); |
| |
| final PsiElement anchor = getLocalInsertPosition(element); |
| final PsiElement parentElement = sure(anchor).getParent(); |
| if (parentElement != null) { |
| parentElement.addBefore(generator.createFromImportStatement(languageLevel, qualifier, name, null), anchor); |
| } |
| |
| } |
| |
| @Nullable |
| public static PsiElement getLocalInsertPosition(@NotNull PyElement anchor) { |
| return PsiTreeUtil.getParentOfType(anchor, PyStatement.class, false); |
| } |
| |
| public enum ImportPriority { |
| BUILTIN, THIRD_PARTY, PROJECT |
| } |
| |
| @Nullable |
| public static PsiElement getFileInsertPosition(final PsiFile file) { |
| return getInsertPosition(file, null, null); |
| } |
| |
| @Nullable |
| private static PsiElement getInsertPosition(final PsiFile file, @Nullable String nameToImport, @Nullable ImportPriority priority) { |
| PsiElement feeler = file.getFirstChild(); |
| if (feeler == null) return null; |
| // skip initial comments and whitespace and try to get just below the last import stmt |
| boolean skippedOverImports = false; |
| boolean skippedOverDoc = false; |
| PsiElement seeker = feeler; |
| final boolean isInjected = InjectedLanguageManager.getInstance(feeler.getProject()).isInjectedFragment(feeler.getContainingFile()); |
| do { |
| if (feeler instanceof PyImportStatementBase && !isInjected) { |
| if (nameToImport != null && priority != null && shouldInsertBefore(file, (PyImportStatementBase)feeler, nameToImport, priority)) { |
| break; |
| } |
| seeker = feeler; |
| feeler = feeler.getNextSibling(); |
| skippedOverImports = true; |
| } |
| else if (PyUtil.instanceOf(feeler, PsiWhiteSpace.class, PsiComment.class)) { |
| seeker = feeler; |
| feeler = feeler.getNextSibling(); |
| } |
| // maybe we arrived at the doc comment stmt; skip over it, too |
| else if (!skippedOverImports && !skippedOverDoc && file instanceof PyFile) { |
| PsiElement doc_elt = |
| DocStringUtil |
| .findDocStringExpression((PyElement)file); // this gives the literal; its parent is the expr seeker may have encountered |
| if (doc_elt != null && doc_elt.getParent() == feeler) { |
| feeler = feeler.getNextSibling(); |
| seeker = feeler; // skip over doc even if there's nothing below it |
| skippedOverDoc = true; |
| } |
| else { |
| break; // not a doc comment, stop on it |
| } |
| } |
| else { |
| break; // some other statement, stop |
| } |
| } |
| while (feeler != null); |
| return seeker; |
| } |
| |
| private static boolean shouldInsertBefore(PsiFile file, PyImportStatementBase relativeTo, String nameToImport, ImportPriority priority) { |
| QualifiedName relativeToName; |
| PsiElement source; |
| if (relativeTo instanceof PyFromImportStatement) { |
| final PyFromImportStatement fromImportStatement = (PyFromImportStatement)relativeTo; |
| if (fromImportStatement.isFromFuture()) { |
| return false; |
| } |
| relativeToName = fromImportStatement.getImportSourceQName(); |
| source = fromImportStatement.resolveImportSource(); |
| } |
| else { |
| final PyImportElement[] importElements = relativeTo.getImportElements(); |
| if (importElements.length == 0) { |
| return false; |
| } |
| relativeToName = importElements[0].getImportedQName(); |
| source = importElements[0].resolve(); |
| } |
| if (relativeToName == null) { |
| return false; |
| } |
| final PsiFileSystemItem containingFile; |
| if (source instanceof PsiDirectory) { |
| containingFile = (PsiDirectory)source; |
| } |
| else { |
| containingFile = source != null ? source.getContainingFile() : null; |
| } |
| ImportPriority relativeToPriority = source == null || containingFile == null |
| ? ImportPriority.BUILTIN |
| : getImportPriority(file, containingFile); |
| final int rc = priority.compareTo(relativeToPriority); |
| if (rc < 0) { |
| return true; |
| } |
| if (rc == 0) { |
| return nameToImport.compareTo(relativeToName.toString()) < 0; |
| } |
| return false; |
| } |
| |
| /** |
| * Adds an import statement, presumably below all other initial imports in the file. |
| * |
| * @param file where to operate |
| * @param name which to import (qualified is OK) |
| * @param asName optional name for 'as' clause |
| * @return whether import statement was actually added |
| */ |
| public static boolean addImportStatement(PsiFile file, String name, @Nullable String asName, ImportPriority priority) { |
| if (!(file instanceof PyFile)) { |
| return false; |
| } |
| final List<PyImportElement> existingImports = ((PyFile)file).getImportTargets(); |
| for (PyImportElement element : existingImports) { |
| final QualifiedName qName = element.getImportedQName(); |
| if (qName != null && name.equals(qName.toString())) { |
| if ((asName != null && asName.equals(element.getAsName())) || asName == null) { |
| return false; |
| } |
| } |
| } |
| |
| final PyElementGenerator generator = PyElementGenerator.getInstance(file.getProject()); |
| final LanguageLevel languageLevel = LanguageLevel.forElement(file); |
| final PyImportStatement importNodeToInsert = generator.createImportStatement(languageLevel, name, asName); |
| try { |
| file.addBefore(importNodeToInsert, getInsertPosition(file, name, priority)); |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| return true; |
| } |
| |
| /** |
| * Adds an "import ... from ..." statement below other top-level imports. |
| * |
| * @param file where to operate |
| * @param from name of the module |
| * @param name imported name |
| * @param asName optional name for 'as' clause |
| */ |
| public static void addImportFromStatement(PsiFile file, String from, String name, @Nullable String asName, ImportPriority priority) { |
| final PyElementGenerator generator = PyElementGenerator.getInstance(file.getProject()); |
| final LanguageLevel languageLevel = LanguageLevel.forElement(file); |
| final PyFromImportStatement nodeToInsert = generator.createFromImportStatement(languageLevel, from, name, asName); |
| try { |
| if (InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) { |
| final PsiElement element = file.addBefore(nodeToInsert, getInsertPosition(file, from, priority)); |
| PsiElement whitespace = element.getNextSibling(); |
| if (!(whitespace instanceof PsiWhiteSpace)) { |
| whitespace = PsiParserFacade.SERVICE.getInstance(file.getProject()).createWhiteSpaceFromText(" >>> "); |
| } |
| file.addBefore(whitespace, element); |
| } |
| else { |
| file.addBefore(nodeToInsert, getInsertPosition(file, from, priority)); |
| } |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| |
| public static boolean addImportFrom(PsiFile file, @Nullable PsiElement target, String path, final String name, |
| @Nullable String asName, ImportPriority priority) { |
| final List<PyFromImportStatement> existingImports = ((PyFile)file).getFromImports(); |
| for (PyFromImportStatement existingImport : existingImports) { |
| if (target != null && existingImport.getTextRange().getStartOffset() > target.getTextRange().getStartOffset()) { |
| continue; |
| } |
| if (existingImport.isStarImport()) { |
| continue; |
| } |
| final QualifiedName qName = existingImport.getImportSourceQName(); |
| if (qName != null && qName.toString().equals(path)) { |
| for (PyImportElement el : existingImport.getImportElements()) { |
| if (name.equals(el.getVisibleName())) { |
| return false; |
| } |
| } |
| final PyElementGenerator generator = PyElementGenerator.getInstance(file.getProject()); |
| final PyImportElement importElement = generator.createImportElement(LanguageLevel.forElement(file), name); |
| existingImport.add(importElement); |
| return true; |
| } |
| } |
| addImportFromStatement(file, path, name, asName, priority); |
| return true; |
| } |
| |
| public static void addImport(final PsiNamedElement target, final PsiFile file, final PyElement element) { |
| final boolean useQualified = !PyCodeInsightSettings.getInstance().PREFER_FROM_IMPORT; |
| final PsiFileSystemItem toImport = |
| target instanceof PsiFileSystemItem ? ((PsiFileSystemItem)target).getParent() : target.getContainingFile(); |
| final ImportPriority priority = getImportPriority(file, toImport); |
| final QualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(target, element); |
| if (qName == null) return; |
| String path = qName.toString(); |
| if (target instanceof PsiFileSystemItem && qName.getComponentCount() == 1) { |
| addImportStatement(file, path, null, priority); |
| } |
| else { |
| final QualifiedName toImportQName = QualifiedNameFinder.findCanonicalImportPath(toImport, element); |
| if (useQualified) { |
| addImportStatement(file, path, null, priority); |
| final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(file.getProject()); |
| final String targetName = PyUtil.getElementNameWithoutExtension(target); |
| element.replace(elementGenerator.createExpressionFromText(LanguageLevel.forElement(target), toImportQName + "." + targetName)); |
| } |
| else { |
| addImportFrom(file, null, toImportQName.toString(), target.getName(), null, priority); |
| } |
| } |
| } |
| |
| public static ImportPriority getImportPriority(PsiElement importLocation, @NotNull PsiFileSystemItem toImport) { |
| final VirtualFile vFile = toImport.getVirtualFile(); |
| if (vFile == null) { |
| return ImportPriority.PROJECT; |
| } |
| final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(toImport.getProject()); |
| if (projectRootManager.getFileIndex().isInContent(vFile)) { |
| return ImportPriority.PROJECT; |
| } |
| Module module = ModuleUtilCore.findModuleForPsiElement(importLocation); |
| Sdk pythonSdk = module != null ? PythonSdkType.findPythonSdk(module) : projectRootManager.getProjectSdk(); |
| |
| return PythonSdkType.isStdLib(vFile, pythonSdk) ? ImportPriority.BUILTIN : ImportPriority.THIRD_PARTY; |
| } |
| } |