/* * Copyright 2000-2014 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.psi.impl; import com.intellij.extapi.psi.PsiFileBase; import com.intellij.icons.AllIcons; import com.intellij.lang.ASTNode; import com.intellij.lang.Language; import com.intellij.navigation.ItemPresentation; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.stubs.StubElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.QualifiedName; import com.intellij.reference.SoftReference; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.indexing.IndexingDataKeys; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonFileType; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache; import com.jetbrains.python.documentation.DocStringUtil; import com.jetbrains.python.inspections.PythonVisitorFilter; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.QualifiedNameFinder; import com.jetbrains.python.psi.resolve.RatedResolveResult; import com.jetbrains.python.psi.resolve.ResolveImportUtil; import com.jetbrains.python.psi.resolve.VariantsProcessor; import com.jetbrains.python.psi.stubs.PyFileStub; import com.jetbrains.python.psi.types.PyModuleType; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.io.File; import java.util.*; public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { protected PyType myType; private final ThreadLocal> myFindExportedNameStack = new ArrayListThreadLocal(); //private volatile Boolean myAbsoluteImportEnabled; private final Map myFutureFeatures; private List myDunderAll; private boolean myDunderAllCalculated; private volatile SoftReference myExportedNameCache = new SoftReference(null); private final PsiModificationTracker myModificationTracker; private class ExportedNameCache { private final Map myLocalDeclarations = new HashMap(); private final MultiMap myLocalAmbiguousDeclarations = new MultiMap(); private final Map myExceptPartDeclarations = new HashMap(); private final MultiMap myExceptPartAmbiguousDeclarations = new MultiMap(); private final List myNameDefiners = new ArrayList(); private final List myNameDefinerNegativeCache = new ArrayList(); private long myNameDefinerOOCBModCount = -1; private final long myModificationStamp; private ExportedNameCache(long modificationStamp) { myModificationStamp = modificationStamp; final List children = PyPsiUtils.collectAllStubChildren(PyFileImpl.this, getStub()); final List exceptParts = new ArrayList(); for (PsiElement child : children) { if (child instanceof PyExceptPart) { exceptParts.add((PyExceptPart)child); } else { addDeclaration(child, myLocalDeclarations, myLocalAmbiguousDeclarations, myNameDefiners); } } if (!exceptParts.isEmpty()) { for (PyExceptPart part : exceptParts) { final List exceptChildren = PyPsiUtils.collectAllStubChildren(part, part.getStub()); for (PsiElement child : exceptChildren) { addDeclaration(child, myExceptPartDeclarations, myExceptPartAmbiguousDeclarations, myNameDefiners); } } } } private void addDeclaration(PsiElement child, Map localDeclarations, MultiMap ambiguousDeclarations, List nameDefiners) { if (child instanceof PsiNamedElement) { final String name = ((PsiNamedElement)child).getName(); localDeclarations.put(name, child); } else if (child instanceof PyFromImportStatement) { final PyFromImportStatement fromImportStatement = (PyFromImportStatement)child; if (fromImportStatement.isStarImport()) { nameDefiners.add(fromImportStatement); } else { for (PyImportElement importElement : fromImportStatement.getImportElements()) { addImportElementDeclaration(importElement, localDeclarations, ambiguousDeclarations); } } } else if (child instanceof PyImportStatement) { final PyImportStatement importStatement = (PyImportStatement)child; for (PyImportElement importElement : importStatement.getImportElements()) { addImportElementDeclaration(importElement, localDeclarations, ambiguousDeclarations); } } else if (child instanceof NameDefiner) { nameDefiners.add(child); } } private void addImportElementDeclaration(PyImportElement importElement, Map localDeclarations, MultiMap ambiguousDeclarations) { final String visibleName = importElement.getVisibleName(); if (visibleName != null) { if (ambiguousDeclarations.containsKey(visibleName)) { ambiguousDeclarations.putValue(visibleName, importElement); } else if (localDeclarations.containsKey(visibleName)) { final PsiElement oldElement = localDeclarations.get(visibleName); ambiguousDeclarations.putValue(visibleName, oldElement); ambiguousDeclarations.putValue(visibleName, importElement); } else { localDeclarations.put(visibleName, importElement); } } } @Nullable private PsiElement resolve(String name) { final PsiElement named = resolveNamed(name, myLocalDeclarations, myLocalAmbiguousDeclarations); if (named != null) { return named; } if (!myNameDefiners.isEmpty()) { final PsiElement result = findNameInNameDefiners(name); if (result != null) { return result; } } return resolveNamed(name, myExceptPartDeclarations, myExceptPartAmbiguousDeclarations); } @Nullable private PsiElement resolveNamed(String name, final Map declarations, final MultiMap ambiguousDeclarations) { if (ambiguousDeclarations.containsKey(name)) { final List localAmbiguous = new ArrayList(myLocalAmbiguousDeclarations.get(name)); for (int i = localAmbiguous.size() - 1; i >= 0; i--) { PsiElement ambiguous = localAmbiguous.get(i); final PsiElement result = resolveDeclaration(name, ambiguous, true); if (result != null) { return result; } } } else { final PsiElement result = declarations.get(name); if (result != null) { final boolean resolveImportElement = result instanceof PyImportElement && ((PyImportElement)result).getContainingImportStatement() instanceof PyFromImportStatement; return resolveDeclaration(name, result, resolveImportElement); } } return null; } @Nullable private PsiElement resolveDeclaration(String name, PsiElement result, boolean resolveImportElement) { if (result instanceof PyImportElement) { final PyImportElement importElement = (PyImportElement)result; return findNameInImportElement(name, importElement, resolveImportElement); } else if (result instanceof PyFromImportStatement) { return ((PyFromImportStatement)result).resolveImportSource(); } return result; } @Nullable private PsiElement findNameInNameDefiners(String name) { synchronized (myNameDefinerNegativeCache) { long oocbModCount = myModificationTracker.getOutOfCodeBlockModificationCount(); if (oocbModCount != myNameDefinerOOCBModCount) { myNameDefinerNegativeCache.clear(); myNameDefinerOOCBModCount = oocbModCount; } else { if (myNameDefinerNegativeCache.contains(name)) { return null; } } } for (PsiElement definer : myNameDefiners) { final PsiElement result; if (definer instanceof PyFromImportStatement) { result = findNameInStarImport(name, (PyFromImportStatement)definer); } else { result = ((NameDefiner)definer).getElementNamed(name); } if (result != null) { return result; } } synchronized (myNameDefinerNegativeCache) { myNameDefinerNegativeCache.add(name); } return null; } public long getModificationStamp() { return myModificationStamp; } } public PyFileImpl(FileViewProvider viewProvider) { this(viewProvider, PythonLanguage.getInstance()); } public PyFileImpl(FileViewProvider viewProvider, Language language) { super(viewProvider, language); myFutureFeatures = new HashMap(); myModificationTracker = PsiModificationTracker.SERVICE.getInstance(getProject()); } @Override @NotNull public FileType getFileType() { return PythonFileType.INSTANCE; } public String toString() { return "PyFile:" + getName(); } @Override public PyFunction findTopLevelFunction(String name) { return findByName(name, getTopLevelFunctions()); } @Override public PyClass findTopLevelClass(String name) { return findByName(name, getTopLevelClasses()); } @Override public PyTargetExpression findTopLevelAttribute(String name) { return findByName(name, getTopLevelAttributes()); } @Nullable private static T findByName(String name, List namedElements) { for (T namedElement : namedElements) { if (name.equals(namedElement.getName())) { return namedElement; } } return null; } @Override public LanguageLevel getLanguageLevel() { if (myOriginalFile != null) { return ((PyFileImpl)myOriginalFile).getLanguageLevel(); } VirtualFile virtualFile = getVirtualFile(); if (virtualFile == null) { virtualFile = getUserData(IndexingDataKeys.VIRTUAL_FILE); } if (virtualFile == null) { virtualFile = getViewProvider().getVirtualFile(); } return PyUtil.getLanguageLevelForVirtualFile(getProject(), virtualFile); } @Override public Icon getIcon(int flags) { return PythonFileType.INSTANCE.getIcon(); } @Override public void accept(@NotNull PsiElementVisitor visitor) { if (isAcceptedFor(visitor.getClass())) { if (visitor instanceof PyElementVisitor) { ((PyElementVisitor)visitor).visitPyFile(this); } else { super.accept(visitor); } } } public boolean isAcceptedFor(@NotNull Class visitorClass) { for (Language lang : getViewProvider().getLanguages()) { final List filters = PythonVisitorFilter.INSTANCE.allForLanguage(lang); for (PythonVisitorFilter filter : filters) { if (!filter.isSupported(visitorClass, this)) { return false; } } } return true; } private final Key> PROCESSED_FILES = Key.create("PyFileImpl.processDeclarations.processedFiles"); @Override public boolean processDeclarations(@NotNull final PsiScopeProcessor processor, @NotNull ResolveState resolveState, PsiElement lastParent, @NotNull PsiElement place) { final List dunderAll = getDunderAll(); final List remainingDunderAll = dunderAll == null ? null : new ArrayList(dunderAll); PsiScopeProcessor wrapper = new PsiScopeProcessor() { @Override public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) { if (!processor.execute(element, state)) return false; if (remainingDunderAll != null && element instanceof PyElement) { remainingDunderAll.remove(((PyElement)element).getName()); } return true; } @Override public T getHint(@NotNull Key hintKey) { return processor.getHint(hintKey); } @Override public void handleEvent(@NotNull Event event, @Nullable Object associated) { processor.handleEvent(event, associated); } }; Set pyFiles = resolveState.get(PROCESSED_FILES); if (pyFiles == null) { pyFiles = new HashSet(); resolveState = resolveState.put(PROCESSED_FILES, pyFiles); } if (pyFiles.contains(this)) return true; pyFiles.add(this); for (PyClass c : getTopLevelClasses()) { if (c == lastParent) continue; if (!wrapper.execute(c, resolveState)) return false; } for (PyFunction f : getTopLevelFunctions()) { if (f == lastParent) continue; if (!wrapper.execute(f, resolveState)) return false; } for (PyTargetExpression e : getTopLevelAttributes()) { if (e == lastParent) continue; if (!wrapper.execute(e, resolveState)) return false; } for (PyImportElement e : getImportTargets()) { if (e == lastParent) continue; if (!wrapper.execute(e, resolveState)) return false; } for (PyFromImportStatement e : getFromImports()) { if (e == lastParent) continue; if (!e.processDeclarations(wrapper, resolveState, null, this)) return false; } if (remainingDunderAll != null) { for (String s : remainingDunderAll) { if (!PyNames.isIdentifier(s)) { continue; } if (!processor.execute(new LightNamedElement(myManager, PythonLanguage.getInstance(), s), resolveState)) return false; } } return true; } @Override public List getStatements() { List stmts = new ArrayList(); for (PsiElement child : getChildren()) { if (child instanceof PyStatement) { PyStatement statement = (PyStatement)child; stmts.add(statement); } } return stmts; } @Override public List getTopLevelClasses() { return PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.CLASS_DECLARATION, PyClass.class); } @NotNull @Override public List getTopLevelFunctions() { return PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.FUNCTION_DECLARATION, PyFunction.class); } @Override public List getTopLevelAttributes() { return PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.TARGET_EXPRESSION, PyTargetExpression.class); } @Nullable public PsiElement findExportedName(String name) { final List stack = myFindExportedNameStack.get(); if (stack.contains(name)) { return null; } stack.add(name); try { PsiElement result = getExportedNameCache().resolve(name); if (result != null) { return result; } List allNames = getDunderAll(); if (allNames != null && allNames.contains(name)) { return findExportedName(PyNames.ALL); } return null; } finally { stack.remove(name); } } private ExportedNameCache getExportedNameCache() { ExportedNameCache cache; cache = myExportedNameCache != null ? myExportedNameCache.get() : null; final long modificationStamp = getModificationStamp(); if (myExportedNameCache != null && cache != null && modificationStamp != cache.getModificationStamp()) { myExportedNameCache.clear(); cache = null; } if (cache == null) { cache = new ExportedNameCache(modificationStamp); myExportedNameCache = new SoftReference(cache); } return cache; } @Nullable private PsiElement findNameInStarImport(String name, PyFromImportStatement statement) { if (PyUtil.isClassPrivateName(name)) { return null; } PsiElement starImportSource = statement.resolveImportSource(); if (starImportSource != null) { starImportSource = PyUtil.turnDirIntoInit(starImportSource); if (starImportSource instanceof PyFile) { final PyFile file = (PyFile)starImportSource; final PsiElement result = file.getElementNamed(name); if (result != null && PyUtil.isStarImportableFrom(name, file)) { return result; } } } // http://stackoverflow.com/questions/6048786/from-module-import-in-init-py-makes-module-name-visible if (PyNames.INIT_DOT_PY.equals(getName())) { final QualifiedName qName = statement.getImportSourceQName(); if (qName != null && qName.endsWith(name)) { final PsiElement element = PyUtil.turnInitIntoDir(statement.resolveImportSource()); if (element != null && element.getParent() == getContainingDirectory()) { return element; } } } return null; } @Nullable private PsiElement findNameInImportElement(String name, PyImportElement importElement, final boolean resolveImportElement) { final PsiElement result = importElement.getElementNamed(name, resolveImportElement); if (result != null) { return result; } final QualifiedName qName = importElement.getImportedQName(); // http://stackoverflow.com/questions/6048786/from-module-import-in-init-py-makes-module-name-visible if (qName != null && qName.getComponentCount() > 1 && name.equals(qName.getLastComponent()) && PyNames.INIT_DOT_PY.equals(getName())) { final List elements = ResolveImportUtil.resolveNameInImportStatement(importElement, qName.removeLastComponent()); for (RatedResolveResult element : elements) { if (PyUtil.turnDirIntoInit(element.getElement()) == this) { return importElement; } } } return null; } @Override @Nullable public PsiElement getElementNamed(String name) { PsiElement exportedName = findExportedName(name); if (exportedName instanceof PyImportElement) { PsiElement importedElement = ((PyImportElement)exportedName).getElementNamed(name); if (importedElement != null && !importedElement.isValid()) { throw new PsiInvalidElementAccessException(importedElement); } return importedElement; } else if (exportedName != null && !exportedName.isValid()) { throw new PsiInvalidElementAccessException(exportedName); } return exportedName; } @Override @NotNull public Iterable iterateNames() { final List result = new ArrayList(); VariantsProcessor processor = new VariantsProcessor(this) { @Override protected void addElement(String name, PsiElement element) { element = PyUtil.turnDirIntoInit(element); if (element instanceof PyElement) { result.add((PyElement)element); } } }; processor.setAllowedNames(getDunderAll()); processDeclarations(processor, ResolveState.initial(), null, this); return result; } @Override public boolean mustResolveOutside() { return false; } @Override @NotNull public List getImportTargets() { List ret = new ArrayList(); List imports = PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.IMPORT_STATEMENT, PyImportStatement.class); for (PyImportStatement one : imports) { ContainerUtil.addAll(ret, one.getImportElements()); } return ret; } @Override @NotNull public List getFromImports() { return PyPsiUtils.collectStubChildren(this, getStub(), PyElementTypes.FROM_IMPORT_STATEMENT, PyFromImportStatement.class); } @Override public List getDunderAll() { final StubElement stubElement = getStub(); if (stubElement instanceof PyFileStub) { return ((PyFileStub)stubElement).getDunderAll(); } if (!myDunderAllCalculated) { final List dunderAll = calculateDunderAll(); myDunderAll = dunderAll == null ? null : Collections.unmodifiableList(dunderAll); myDunderAllCalculated = true; } return myDunderAll; } @Nullable public List calculateDunderAll() { final DunderAllBuilder builder = new DunderAllBuilder(); accept(builder); return builder.result(); } private static class DunderAllBuilder extends PyRecursiveElementVisitor { private List myResult = null; private boolean myDynamic = false; private boolean myFoundDunderAll = false; // hashlib builds __all__ by concatenating multiple lists of strings, and we want to understand this private final Map> myDunderLike = new HashMap>(); @Override public void visitPyFile(PyFile node) { if (node.getText().contains(PyNames.ALL)) { super.visitPyFile(node); } } @Override public void visitPyTargetExpression(PyTargetExpression node) { if (PyNames.ALL.equals(node.getName())) { myFoundDunderAll = true; PyExpression value = node.findAssignedValue(); if (value instanceof PyBinaryExpression) { PyBinaryExpression binaryExpression = (PyBinaryExpression)value; if (binaryExpression.isOperator("+")) { List lhs = getStringListFromValue(binaryExpression.getLeftExpression()); List rhs = getStringListFromValue(binaryExpression.getRightExpression()); if (lhs != null && rhs != null) { myResult = new ArrayList(lhs); myResult.addAll(rhs); } } } else { myResult = PyUtil.getStringListFromTargetExpression(node); } } if (!myFoundDunderAll) { List names = PyUtil.getStringListFromTargetExpression(node); if (names != null) { myDunderLike.put(node.getName(), names); } } } @Nullable private List getStringListFromValue(PyExpression expression) { if (expression instanceof PyReferenceExpression && !((PyReferenceExpression)expression).isQualified()) { return myDunderLike.get(((PyReferenceExpression)expression).getReferencedName()); } return PyUtil.strListValue(expression); } @Override public void visitPyAugAssignmentStatement(PyAugAssignmentStatement node) { if (PyNames.ALL.equals(node.getTarget().getName())) { myDynamic = true; } } @Override public void visitPyCallExpression(PyCallExpression node) { final PyExpression callee = node.getCallee(); if (callee instanceof PyQualifiedExpression) { final PyExpression qualifier = ((PyQualifiedExpression)callee).getQualifier(); if (qualifier != null && PyNames.ALL.equals(qualifier.getText())) { // TODO handle append and extend with constant arguments here myDynamic = true; } } } @Nullable List result() { return myDynamic ? null : myResult; } } @Nullable public static List getStringListFromTargetExpression(final String name, List attrs) { for (PyTargetExpression attr : attrs) { if (name.equals(attr.getName())) { return PyUtil.getStringListFromTargetExpression(attr); } } return null; } @Override public boolean hasImportFromFuture(FutureFeature feature) { final StubElement stub = getStub(); if (stub instanceof PyFileStub) { return ((PyFileStub)stub).getFutureFeatures().get(feature.ordinal()); } Boolean enabled = myFutureFeatures.get(feature); if (enabled == null) { enabled = calculateImportFromFuture(feature); myFutureFeatures.put(feature, enabled); // NOTE: ^^^ not synchronized. if two threads will try to modify this, both can only be expected to set the same value. } return enabled; } @Override public String getDeprecationMessage() { final StubElement stub = getStub(); if (stub instanceof PyFileStub) { return ((PyFileStub)stub).getDeprecationMessage(); } return extractDeprecationMessage(); } @Override public List getImportBlock() { List result = new ArrayList(); ASTNode firstImport = getNode().getFirstChildNode(); while (firstImport != null && !isImport(firstImport, false)) { firstImport = firstImport.getTreeNext(); } if (firstImport != null) { result.add(firstImport.getPsi(PyImportStatementBase.class)); ASTNode lastImport = firstImport.getTreeNext(); while (lastImport != null && isImport(lastImport.getTreeNext(), true)) { if (isImport(lastImport, false)) { result.add(lastImport.getPsi(PyImportStatementBase.class)); } lastImport = lastImport.getTreeNext(); } } return result; } public String extractDeprecationMessage() { if (canHaveDeprecationMessage(getText())) { return PyFunctionImpl.extractDeprecationMessage(getStatements()); } else { return null; } } private static boolean canHaveDeprecationMessage(String text) { return text.contains(PyNames.DEPRECATION_WARNING) || text.contains(PyNames.PENDING_DEPRECATION_WARNING); } public boolean calculateImportFromFuture(FutureFeature feature) { if (getText().contains(feature.toString())) { final List fromImports = getFromImports(); for (PyFromImportStatement fromImport : fromImports) { if (fromImport.isFromFuture()) { final PyImportElement[] pyImportElements = fromImport.getImportElements(); for (PyImportElement element : pyImportElements) { final QualifiedName qName = element.getImportedQName(); if (qName != null && qName.matches(feature.toString())) { return true; } } } } } return false; } @Override public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) { if (myType == null) myType = new PyModuleType(this); return myType; } @Nullable @Override public String getDocStringValue() { return DocStringUtil.getDocStringValue(this); } @Nullable @Override public StructuredDocString getStructuredDocString() { return DocStringUtil.getStructuredDocString(this); } @Nullable @Override public PyStringLiteralExpression getDocStringExpression() { return DocStringUtil.findDocStringExpression(this); } @Override public void subtreeChanged() { super.subtreeChanged(); ControlFlowCache.clear(this); myDunderAllCalculated = false; myFutureFeatures.clear(); // probably no need to synchronize myExportedNameCache.clear(); } @Override public void delete() throws IncorrectOperationException { String path = getVirtualFile().getPath(); super.delete(); PyUtil.deletePycFiles(path); } @Override public PsiElement setName(@NotNull String name) throws IncorrectOperationException { String path = getVirtualFile().getPath(); final PsiElement newElement = super.setName(name); PyUtil.deletePycFiles(path); return newElement; } private static class ArrayListThreadLocal extends ThreadLocal> { @Override protected List initialValue() { return new ArrayList(); } } public static boolean isImport(ASTNode node, boolean orWhitespace) { if (node == null) return false; IElementType elementType = node.getElementType(); if (orWhitespace && elementType == TokenType.WHITE_SPACE) { return true; } return elementType == PyElementTypes.IMPORT_STATEMENT || elementType == PyElementTypes.FROM_IMPORT_STATEMENT; } @Override public ItemPresentation getPresentation() { return new ItemPresentation() { @Override public String getPresentableText() { return getModuleName(PyFileImpl.this); } @Override public String getLocationString() { final String name = getLocationName(); return name != null ? "(" + name + ")" : null; } @Override public Icon getIcon(final boolean open) { if (PyUtil.isPackage(PyFileImpl.this)) { return AllIcons.Modules.SourceFolder; } return PyFileImpl.this.getIcon(0); } @NotNull private String getModuleName(@NotNull PyFile file) { if (PyUtil.isPackage(file)) { final PsiDirectory dir = file.getContainingDirectory(); if (dir != null) { return dir.getName(); } } return FileUtil.getNameWithoutExtension(file.getName()); } @Nullable private String getLocationName() { final QualifiedName name = QualifiedNameFinder.findShortestImportableQName(PyFileImpl.this); if (name != null) { final QualifiedName prefix = name.removeTail(1); if (prefix.getComponentCount() > 0) { return prefix.toString(); } } final String relativePath = getRelativeContainerPath(); if (relativePath != null) { return relativePath; } final PsiDirectory psiDirectory = getParent(); if (psiDirectory != null) { return psiDirectory.getVirtualFile().getPresentableUrl(); } return null; } @Nullable private String getRelativeContainerPath() { final PsiDirectory psiDirectory = getParent(); if (psiDirectory != null) { final VirtualFile virtualFile = getVirtualFile(); if (virtualFile != null) { final VirtualFile root = ProjectFileIndex.SERVICE.getInstance(getProject()).getContentRootForFile(virtualFile); if (root != null) { final VirtualFile parent = virtualFile.getParent(); final VirtualFile rootParent = root.getParent(); if (rootParent != null && parent != null) { return VfsUtilCore.getRelativePath(parent, rootParent, File.separatorChar); } } } } return null; } }; } }