2 * Copyright 2000-2014 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.jetbrains.python.psi.impl;
18 import com.google.common.collect.Lists;
19 import com.google.common.collect.Maps;
20 import com.intellij.extapi.psi.PsiFileBase;
21 import com.intellij.icons.AllIcons;
22 import com.intellij.lang.Language;
23 import com.intellij.navigation.ItemPresentation;
24 import com.intellij.openapi.fileTypes.FileType;
25 import com.intellij.openapi.roots.ProjectFileIndex;
26 import com.intellij.openapi.util.Computable;
27 import com.intellij.openapi.util.Key;
28 import com.intellij.openapi.util.RecursionManager;
29 import com.intellij.openapi.util.io.FileUtil;
30 import com.intellij.openapi.vfs.VfsUtilCore;
31 import com.intellij.openapi.vfs.VirtualFile;
32 import com.intellij.psi.*;
33 import com.intellij.psi.scope.PsiScopeProcessor;
34 import com.intellij.psi.stubs.StubElement;
35 import com.intellij.psi.util.PsiModificationTracker;
36 import com.intellij.psi.util.PsiTreeUtil;
37 import com.intellij.psi.util.QualifiedName;
38 import com.intellij.reference.SoftReference;
39 import com.intellij.util.IncorrectOperationException;
40 import com.intellij.util.Processor;
41 import com.intellij.util.containers.ContainerUtil;
42 import com.intellij.util.indexing.IndexingDataKeys;
43 import com.jetbrains.python.PyElementTypes;
44 import com.jetbrains.python.PyNames;
45 import com.jetbrains.python.PythonFileType;
46 import com.jetbrains.python.PythonLanguage;
47 import com.jetbrains.python.codeInsight.controlflow.ControlFlowCache;
48 import com.jetbrains.python.documentation.docstrings.DocStringUtil;
49 import com.jetbrains.python.inspections.PythonVisitorFilter;
50 import com.jetbrains.python.psi.*;
51 import com.jetbrains.python.psi.impl.references.PyReferenceImpl;
52 import com.jetbrains.python.psi.resolve.*;
53 import com.jetbrains.python.psi.stubs.PyFileStub;
54 import com.jetbrains.python.psi.types.PyModuleType;
55 import com.jetbrains.python.psi.types.PyType;
56 import com.jetbrains.python.psi.types.TypeEvalContext;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
64 public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
65 protected PyType myType;
67 //private volatile Boolean myAbsoluteImportEnabled;
68 private final Map<FutureFeature, Boolean> myFutureFeatures;
69 private List<String> myDunderAll;
70 private boolean myDunderAllCalculated;
71 private volatile SoftReference<ExportedNameCache> myExportedNameCache = new SoftReference<>(null);
72 private final PsiModificationTracker myModificationTracker;
74 private class ExportedNameCache {
75 private final List<String> myNameDefinerNegativeCache = new ArrayList<>();
76 private long myNameDefinerOOCBModCount = -1;
77 private final long myModificationStamp;
78 private final Map<String, List<PsiNamedElement>> myNamedElements = Maps.newHashMap();
79 private final List<PyImportedNameDefiner> myImportedNameDefiners = Lists.newArrayList();
81 private ExportedNameCache(long modificationStamp) {
82 myModificationStamp = modificationStamp;
84 processDeclarations(PyPsiUtils.collectAllStubChildren(PyFileImpl.this, getStub()), element -> {
85 if (element instanceof PsiNamedElement && !(element instanceof PyKeywordArgument)) {
86 final PsiNamedElement namedElement = (PsiNamedElement)element;
87 final String name = namedElement.getName();
88 if (!myNamedElements.containsKey(name)) {
89 myNamedElements.put(name, Lists.<PsiNamedElement>newArrayList());
91 final List<PsiNamedElement> elements = myNamedElements.get(name);
92 elements.add(namedElement);
94 if (element instanceof PyImportedNameDefiner) {
95 myImportedNameDefiners.add((PyImportedNameDefiner)element);
97 if (element instanceof PyFromImportStatement) {
98 final PyFromImportStatement fromImportStatement = (PyFromImportStatement)element;
99 final PyStarImportElement starImportElement = fromImportStatement.getStarImportElement();
100 if (starImportElement != null) {
101 myImportedNameDefiners.add(starImportElement);
104 Collections.addAll(myImportedNameDefiners, fromImportStatement.getImportElements());
107 else if (element instanceof PyImportStatement) {
108 final PyImportStatement importStatement = (PyImportStatement)element;
109 Collections.addAll(myImportedNameDefiners, importStatement.getImportElements());
113 for (List<PsiNamedElement> elements : myNamedElements.values()) {
114 Collections.reverse(elements);
116 Collections.reverse(myImportedNameDefiners);
119 private boolean processDeclarations(@NotNull List<PsiElement> elements, @NotNull Processor<PsiElement> processor) {
120 for (PsiElement child : elements) {
121 if (!processor.process(child)) {
124 if (child instanceof PyExceptPart) {
125 final PyExceptPart part = (PyExceptPart)child;
126 if (!processDeclarations(PyPsiUtils.collectAllStubChildren(part, part.getStub()), processor)) {
135 private List<RatedResolveResult> multiResolve(@NotNull String name) {
136 synchronized (myNameDefinerNegativeCache) {
137 final long modCount = myModificationTracker.getOutOfCodeBlockModificationCount();
138 if (modCount != myNameDefinerOOCBModCount) {
139 myNameDefinerNegativeCache.clear();
140 myNameDefinerOOCBModCount = modCount;
143 if (myNameDefinerNegativeCache.contains(name)) {
144 return Collections.emptyList();
149 final PyResolveProcessor processor = new PyResolveProcessor(name);
150 boolean stopped = false;
151 if (myNamedElements.containsKey(name)) {
152 for (PsiNamedElement element : myNamedElements.get(name)) {
153 if (!processor.execute(element, ResolveState.initial())) {
160 for (PyImportedNameDefiner definer : myImportedNameDefiners) {
161 if (!processor.execute(definer, ResolveState.initial())) {
166 final Map<PsiElement, PyImportedNameDefiner> results = processor.getResults();
167 if (!results.isEmpty()) {
168 final ResolveResultList resultList = new ResolveResultList();
169 final TypeEvalContext typeEvalContext = TypeEvalContext.codeInsightFallback(getProject());
170 for (Map.Entry<PsiElement, PyImportedNameDefiner> entry : results.entrySet()) {
171 final PsiElement element = entry.getKey();
172 final PyImportedNameDefiner definer = entry.getValue();
173 if (element != null) {
174 final int elementRate = PyReferenceImpl.getRate(element, typeEvalContext);
175 if (definer != null) {
176 resultList.add(new ImportedResolveResult(element, elementRate, definer));
179 resultList.poke(element, elementRate);
186 synchronized (myNameDefinerNegativeCache) {
187 myNameDefinerNegativeCache.add(name);
189 return Collections.emptyList();
192 public long getModificationStamp() {
193 return myModificationStamp;
197 public PyFileImpl(FileViewProvider viewProvider) {
198 this(viewProvider, PythonLanguage.getInstance());
201 public PyFileImpl(FileViewProvider viewProvider, Language language) {
202 super(viewProvider, language);
203 myFutureFeatures = new HashMap<>();
204 myModificationTracker = PsiModificationTracker.SERVICE.getInstance(getProject());
209 public FileType getFileType() {
210 return PythonFileType.INSTANCE;
213 public String toString() {
214 return "PyFile:" + getName();
218 public PyFunction findTopLevelFunction(@NotNull String name) {
219 return findByName(name, getTopLevelFunctions());
223 public PyClass findTopLevelClass(@NotNull String name) {
224 return findByName(name, getTopLevelClasses());
228 public PyTargetExpression findTopLevelAttribute(@NotNull String name) {
229 return findByName(name, getTopLevelAttributes());
233 private static <T extends PsiNamedElement> T findByName(@NotNull String name, @NotNull List<T> namedElements) {
234 for (T namedElement : namedElements) {
235 if (name.equals(namedElement.getName())) {
243 public LanguageLevel getLanguageLevel() {
244 if (myOriginalFile != null) {
245 return ((PyFileImpl)myOriginalFile).getLanguageLevel();
247 VirtualFile virtualFile = getVirtualFile();
249 if (virtualFile == null) {
250 virtualFile = getUserData(IndexingDataKeys.VIRTUAL_FILE);
252 if (virtualFile == null) {
253 virtualFile = getViewProvider().getVirtualFile();
255 return PyUtil.getLanguageLevelForVirtualFile(getProject(), virtualFile);
259 public Icon getIcon(int flags) {
260 return PythonFileType.INSTANCE.getIcon();
264 public void accept(@NotNull PsiElementVisitor visitor) {
265 if (isAcceptedFor(visitor.getClass())) {
266 if (visitor instanceof PyElementVisitor) {
267 ((PyElementVisitor)visitor).visitPyFile(this);
270 super.accept(visitor);
275 public boolean isAcceptedFor(@NotNull Class visitorClass) {
276 for (Language lang : getViewProvider().getLanguages()) {
277 final List<PythonVisitorFilter> filters = PythonVisitorFilter.INSTANCE.allForLanguage(lang);
278 for (PythonVisitorFilter filter : filters) {
279 if (!filter.isSupported(visitorClass, this)) {
287 private final Key<Set<PyFile>> PROCESSED_FILES = Key.create("PyFileImpl.processDeclarations.processedFiles");
290 public boolean processDeclarations(@NotNull final PsiScopeProcessor processor,
291 @NotNull ResolveState resolveState,
292 PsiElement lastParent,
293 @NotNull PsiElement place) {
294 final List<String> dunderAll = getDunderAll();
295 final List<String> remainingDunderAll = dunderAll == null ? null : new ArrayList<>(dunderAll);
296 PsiScopeProcessor wrapper = new PsiScopeProcessor() {
298 public boolean execute(@NotNull PsiElement element, @NotNull ResolveState state) {
299 if (!processor.execute(element, state)) return false;
300 if (remainingDunderAll != null && element instanceof PyElement) {
301 remainingDunderAll.remove(((PyElement)element).getName());
307 public <T> T getHint(@NotNull Key<T> hintKey) {
308 return processor.getHint(hintKey);
312 public void handleEvent(@NotNull Event event, @Nullable Object associated) {
313 processor.handleEvent(event, associated);
317 Set<PyFile> pyFiles = resolveState.get(PROCESSED_FILES);
318 if (pyFiles == null) {
319 pyFiles = new HashSet<>();
320 resolveState = resolveState.put(PROCESSED_FILES, pyFiles);
322 if (pyFiles.contains(this)) return true;
324 for (PyClass c : getTopLevelClasses()) {
325 if (c == lastParent) continue;
326 if (!wrapper.execute(c, resolveState)) return false;
328 for (PyFunction f : getTopLevelFunctions()) {
329 if (f == lastParent) continue;
330 if (!wrapper.execute(f, resolveState)) return false;
332 for (PyTargetExpression e : getTopLevelAttributes()) {
333 if (e == lastParent) continue;
334 if (!wrapper.execute(e, resolveState)) return false;
337 for (PyImportElement e : getImportTargets()) {
338 if (e == lastParent) continue;
339 if (!wrapper.execute(e, resolveState)) return false;
342 for (PyFromImportStatement e : getFromImports()) {
343 if (e == lastParent) continue;
344 if (!e.processDeclarations(wrapper, resolveState, null, this)) return false;
347 if (remainingDunderAll != null) {
348 for (String s : remainingDunderAll) {
349 if (!PyNames.isIdentifier(s)) {
352 if (!processor.execute(new LightNamedElement(myManager, PythonLanguage.getInstance(), s), resolveState)) return false;
359 public List<PyStatement> getStatements() {
360 List<PyStatement> stmts = new ArrayList<>();
361 for (PsiElement child : getChildren()) {
362 if (child instanceof PyStatement) {
363 PyStatement statement = (PyStatement)child;
364 stmts.add(statement);
371 public List<PyClass> getTopLevelClasses() {
372 return PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.CLASS_DECLARATION, PyClass.class);
377 public List<PyFunction> getTopLevelFunctions() {
378 return PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.FUNCTION_DECLARATION, PyFunction.class);
382 public List<PyTargetExpression> getTopLevelAttributes() {
383 return PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.TARGET_EXPRESSION, PyTargetExpression.class);
388 public PsiElement findExportedName(final String name) {
389 final List<RatedResolveResult> results = multiResolveName(name);
390 final List<PsiElement> elements = Lists.newArrayList();
391 for (RatedResolveResult result : results) {
392 final PsiElement element = result.getElement();
393 final ImportedResolveResult importedResult = PyUtil.as(result, ImportedResolveResult.class);
394 if (importedResult != null) {
395 final PyImportedNameDefiner definer = importedResult.getDefiner();
396 if (definer != null) {
397 elements.add(definer);
400 else if (element != null && element.getContainingFile() == this) {
401 elements.add(element);
404 final PsiElement element = elements.isEmpty() ? null : elements.get(elements.size() - 1);
405 if (element != null && !element.isValid()) {
406 throw new PsiInvalidElementAccessException(element);
413 public List<RatedResolveResult> multiResolveName(@NotNull final String name) {
414 final List<RatedResolveResult> results = RecursionManager.doPreventingRecursion(this, false,
415 () -> getExportedNameCache().multiResolve(name));
416 if (results != null && !results.isEmpty()) {
419 final List<String> allNames = getDunderAll();
420 if (allNames != null && allNames.contains(name)) {
421 final PsiElement allElement = findExportedName(PyNames.ALL);
422 final ResolveResultList allFallbackResults = new ResolveResultList();
423 allFallbackResults.poke(allElement, RatedResolveResult.RATE_LOW);
424 return allFallbackResults;
426 return Collections.emptyList();
429 private ExportedNameCache getExportedNameCache() {
430 ExportedNameCache cache;
431 cache = myExportedNameCache != null ? myExportedNameCache.get() : null;
432 final long modificationStamp = getModificationStamp();
433 if (myExportedNameCache != null && cache != null && modificationStamp != cache.getModificationStamp()) {
434 myExportedNameCache.clear();
438 cache = new ExportedNameCache(modificationStamp);
439 myExportedNameCache = new SoftReference<>(cache);
445 public PsiElement getElementNamed(final String name) {
446 final List<RatedResolveResult> results = multiResolveName(name);
447 final List<PsiElement> elements = PyUtil.filterTopPriorityResults(results.toArray(new ResolveResult[results.size()]));
448 final PsiElement element = elements.isEmpty() ? null : elements.get(elements.size() - 1);
449 if (element != null) {
450 if (!element.isValid()) {
451 throw new PsiInvalidElementAccessException(element);
459 public Iterable<PyElement> iterateNames() {
460 final List<PyElement> result = new ArrayList<>();
461 VariantsProcessor processor = new VariantsProcessor(this) {
463 protected void addElement(String name, PsiElement element) {
464 element = PyUtil.turnDirIntoInit(element);
465 if (element instanceof PyElement) {
466 result.add((PyElement)element);
470 processor.setAllowedNames(getDunderAll());
471 processDeclarations(processor, ResolveState.initial(), null, this);
477 public List<PyImportElement> getImportTargets() {
478 List<PyImportElement> ret = new ArrayList<>();
479 List<PyImportStatement> imports =
480 PyPsiUtils.collectStubChildren(this, this.getStub(), PyElementTypes.IMPORT_STATEMENT, PyImportStatement.class);
481 for (PyImportStatement one : imports) {
482 ContainerUtil.addAll(ret, one.getImportElements());
489 public List<PyFromImportStatement> getFromImports() {
490 return PyPsiUtils.collectStubChildren(this, getStub(), PyElementTypes.FROM_IMPORT_STATEMENT, PyFromImportStatement.class);
494 public List<String> getDunderAll() {
495 final StubElement stubElement = getStub();
496 if (stubElement instanceof PyFileStub) {
497 return ((PyFileStub)stubElement).getDunderAll();
499 if (!myDunderAllCalculated) {
500 final List<String> dunderAll = calculateDunderAll();
501 myDunderAll = dunderAll == null ? null : Collections.unmodifiableList(dunderAll);
502 myDunderAllCalculated = true;
508 public List<String> calculateDunderAll() {
509 final DunderAllBuilder builder = new DunderAllBuilder();
511 return builder.result();
514 private static class DunderAllBuilder extends PyRecursiveElementVisitor {
515 private List<String> myResult = null;
516 private boolean myDynamic = false;
517 private boolean myFoundDunderAll = false;
519 // hashlib builds __all__ by concatenating multiple lists of strings, and we want to understand this
520 private final Map<String, List<String>> myDunderLike = new HashMap<>();
523 public void visitPyFile(PyFile node) {
524 if (node.getText().contains(PyNames.ALL)) {
525 super.visitPyFile(node);
530 public void visitPyTargetExpression(PyTargetExpression node) {
531 if (PyNames.ALL.equals(node.getName())) {
532 myFoundDunderAll = true;
533 PyExpression value = node.findAssignedValue();
534 if (value instanceof PyBinaryExpression) {
535 PyBinaryExpression binaryExpression = (PyBinaryExpression)value;
536 if (binaryExpression.isOperator("+")) {
537 List<String> lhs = getStringListFromValue(binaryExpression.getLeftExpression());
538 List<String> rhs = getStringListFromValue(binaryExpression.getRightExpression());
539 if (lhs != null && rhs != null) {
540 myResult = new ArrayList<>(lhs);
541 myResult.addAll(rhs);
546 myResult = PyUtil.getStringListFromTargetExpression(node);
549 if (!myFoundDunderAll) {
550 List<String> names = PyUtil.getStringListFromTargetExpression(node);
552 myDunderLike.put(node.getName(), names);
558 private List<String> getStringListFromValue(PyExpression expression) {
559 if (expression instanceof PyReferenceExpression && !((PyReferenceExpression)expression).isQualified()) {
560 return myDunderLike.get(((PyReferenceExpression)expression).getReferencedName());
562 return PyUtil.strListValue(expression);
566 public void visitPyAugAssignmentStatement(PyAugAssignmentStatement node) {
567 if (PyNames.ALL.equals(node.getTarget().getName())) {
573 public void visitPyCallExpression(PyCallExpression node) {
574 final PyExpression callee = node.getCallee();
575 if (callee instanceof PyQualifiedExpression) {
576 final PyExpression qualifier = ((PyQualifiedExpression)callee).getQualifier();
577 if (qualifier != null && PyNames.ALL.equals(qualifier.getText())) {
578 // TODO handle append and extend with constant arguments here
585 List<String> result() {
586 return myDynamic ? null : myResult;
591 public static List<String> getStringListFromTargetExpression(final String name, List<PyTargetExpression> attrs) {
592 for (PyTargetExpression attr : attrs) {
593 if (name.equals(attr.getName())) {
594 return PyUtil.getStringListFromTargetExpression(attr);
601 public boolean hasImportFromFuture(FutureFeature feature) {
602 final StubElement stub = getStub();
603 if (stub instanceof PyFileStub) {
604 return ((PyFileStub)stub).getFutureFeatures().get(feature.ordinal());
606 Boolean enabled = myFutureFeatures.get(feature);
607 if (enabled == null) {
608 enabled = calculateImportFromFuture(feature);
609 myFutureFeatures.put(feature, enabled);
610 // NOTE: ^^^ not synchronized. if two threads will try to modify this, both can only be expected to set the same value.
616 public String getDeprecationMessage() {
617 final StubElement stub = getStub();
618 if (stub instanceof PyFileStub) {
619 return ((PyFileStub)stub).getDeprecationMessage();
621 return extractDeprecationMessage();
625 public List<PyImportStatementBase> getImportBlock() {
626 final List<PyImportStatementBase> result = new ArrayList<>();
627 final PsiElement firstChild = getFirstChild();
628 final PyImportStatementBase firstImport;
629 if (firstChild instanceof PyImportStatementBase) {
630 firstImport = (PyImportStatementBase)firstChild;
633 firstImport = PsiTreeUtil.getNextSiblingOfType(firstChild, PyImportStatementBase.class);
635 if (firstImport != null) {
636 result.add(firstImport);
637 PsiElement nextImport = PyPsiUtils.getNextNonCommentSibling(firstImport, true);
638 while (nextImport instanceof PyImportStatementBase) {
639 result.add((PyImportStatementBase)nextImport);
640 nextImport = PyPsiUtils.getNextNonCommentSibling(nextImport, true);
646 public String extractDeprecationMessage() {
647 if (canHaveDeprecationMessage(getText())) {
648 return PyFunctionImpl.extractDeprecationMessage(getStatements());
655 private static boolean canHaveDeprecationMessage(String text) {
656 return text.contains(PyNames.DEPRECATION_WARNING) || text.contains(PyNames.PENDING_DEPRECATION_WARNING);
659 public boolean calculateImportFromFuture(FutureFeature feature) {
660 if (getText().contains(feature.toString())) {
661 final List<PyFromImportStatement> fromImports = getFromImports();
662 for (PyFromImportStatement fromImport : fromImports) {
663 if (fromImport.isFromFuture()) {
664 final PyImportElement[] pyImportElements = fromImport.getImportElements();
665 for (PyImportElement element : pyImportElements) {
666 final QualifiedName qName = element.getImportedQName();
667 if (qName != null && qName.matches(feature.toString())) {
679 public PyType getType(@NotNull TypeEvalContext context, @NotNull TypeEvalContext.Key key) {
680 if (myType == null) myType = new PyModuleType(this);
686 public String getDocStringValue() {
687 return DocStringUtil.getDocStringValue(this);
692 public StructuredDocString getStructuredDocString() {
693 return DocStringUtil.getStructuredDocString(this);
698 public PyStringLiteralExpression getDocStringExpression() {
699 return DocStringUtil.findDocStringExpression(this);
703 public void subtreeChanged() {
704 super.subtreeChanged();
705 ControlFlowCache.clear(this);
706 myDunderAllCalculated = false;
707 myFutureFeatures.clear(); // probably no need to synchronize
708 myExportedNameCache.clear();
712 public void delete() throws IncorrectOperationException {
713 String path = getVirtualFile().getPath();
715 PyUtil.deletePycFiles(path);
719 public PsiElement setName(@NotNull String name) throws IncorrectOperationException {
720 String path = getVirtualFile().getPath();
721 final PsiElement newElement = super.setName(name);
722 PyUtil.deletePycFiles(path);
726 private static class ArrayListThreadLocal extends ThreadLocal<List<String>> {
728 protected List<String> initialValue() {
729 return new ArrayList<>();
734 public ItemPresentation getPresentation() {
735 return new ItemPresentation() {
737 public String getPresentableText() {
738 return getModuleName(PyFileImpl.this);
742 public String getLocationString() {
743 final String name = getLocationName();
744 return name != null ? "(" + name + ")" : null;
748 public Icon getIcon(final boolean open) {
749 if (PyUtil.isPackage(PyFileImpl.this)) {
750 return AllIcons.Modules.SourceFolder;
752 return PyFileImpl.this.getIcon(0);
756 private String getModuleName(@NotNull PyFile file) {
757 if (PyUtil.isPackage(file)) {
758 final PsiDirectory dir = file.getContainingDirectory();
760 return dir.getName();
763 return FileUtil.getNameWithoutExtension(file.getName());
767 private String getLocationName() {
768 final QualifiedName name = QualifiedNameFinder.findShortestImportableQName(PyFileImpl.this);
770 final QualifiedName prefix = name.removeTail(1);
771 if (prefix.getComponentCount() > 0) {
772 return prefix.toString();
775 final String relativePath = getRelativeContainerPath();
776 if (relativePath != null) {
779 final PsiDirectory psiDirectory = getParent();
780 if (psiDirectory != null) {
781 return psiDirectory.getVirtualFile().getPresentableUrl();
787 private String getRelativeContainerPath() {
788 final PsiDirectory psiDirectory = getParent();
789 if (psiDirectory != null) {
790 final VirtualFile virtualFile = getVirtualFile();
791 if (virtualFile != null) {
792 final VirtualFile root = ProjectFileIndex.SERVICE.getInstance(getProject()).getContentRootForFile(virtualFile);
794 final VirtualFile parent = virtualFile.getParent();
795 final VirtualFile rootParent = root.getParent();
796 if (rootParent != null && parent != null) {
797 return VfsUtilCore.getRelativePath(parent, rootParent, File.separatorChar);