diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index f97820ddc739..59efea463b39 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -89,6 +89,7 @@ + diff --git a/python/src/com/jetbrains/python/PyGotoClassContributor.java b/python/src/com/jetbrains/python/PyGotoClassContributor.java index 11b759ee0c81..2662f6686fb7 100644 --- a/python/src/com/jetbrains/python/PyGotoClassContributor.java +++ b/python/src/com/jetbrains/python/PyGotoClassContributor.java @@ -19,11 +19,14 @@ import com.intellij.navigation.ChooseByNameContributor; import com.intellij.navigation.NavigationItem; import com.intellij.openapi.project.Project; import com.intellij.util.ArrayUtil; -import com.jetbrains.python.psi.PyClass; +import com.intellij.util.containers.HashSet; import com.jetbrains.python.psi.stubs.PyClassNameIndex; +import com.jetbrains.python.psi.stubs.PyModuleNameIndex; import org.jetbrains.annotations.NotNull; -import java.util.Collection; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; /** * @author yole @@ -31,13 +34,18 @@ import java.util.Collection; public class PyGotoClassContributor implements ChooseByNameContributor { @NotNull public String[] getNames(final Project project, final boolean includeNonProjectItems) { - final Collection classNames = PyClassNameIndex.allKeys(project); - return ArrayUtil.toStringArray(classNames); + Set results = new HashSet(); + results.addAll(PyClassNameIndex.allKeys(project)); + results.addAll(PyModuleNameIndex.getAllKeys(project)); + return ArrayUtil.toStringArray(results); } @NotNull - public NavigationItem[] getItemsByName(final String name, final String pattern, final Project project, final boolean includeNonProjectItems) { - final Collection classes = PyClassNameIndex.find(name, project, includeNonProjectItems); - return classes.toArray(new NavigationItem[classes.size()]); + public NavigationItem[] getItemsByName(final String name, final String pattern, final Project project, + final boolean includeNonProjectItems) { + final List results = new ArrayList(); + results.addAll(PyClassNameIndex.find(name, project, includeNonProjectItems)); + results.addAll(PyModuleNameIndex.find(name, project, includeNonProjectItems)); + return results.toArray(new NavigationItem[results.size()]); } } diff --git a/python/src/com/jetbrains/python/PyGotoSymbolContributor.java b/python/src/com/jetbrains/python/PyGotoSymbolContributor.java index 6a283fd54ab0..e5eb410b570e 100644 --- a/python/src/com/jetbrains/python/PyGotoSymbolContributor.java +++ b/python/src/com/jetbrains/python/PyGotoSymbolContributor.java @@ -25,6 +25,7 @@ import com.jetbrains.python.psi.PyQualifiedNameOwner; import com.jetbrains.python.psi.search.PyProjectScopeBuilder; import com.jetbrains.python.psi.stubs.PyClassNameIndex; import com.jetbrains.python.psi.stubs.PyFunctionNameIndex; +import com.jetbrains.python.psi.stubs.PyModuleNameIndex; import com.jetbrains.python.psi.stubs.PyVariableNameIndex; import org.jetbrains.annotations.NotNull; @@ -41,6 +42,7 @@ public class PyGotoSymbolContributor implements GotoClassContributor { public String[] getNames(final Project project, final boolean includeNonProjectItems) { Set symbols = new HashSet(); symbols.addAll(PyClassNameIndex.allKeys(project)); + symbols.addAll(PyModuleNameIndex.getAllKeys(project)); symbols.addAll(StubIndex.getInstance().getAllKeys(PyFunctionNameIndex.KEY, project)); symbols.addAll(StubIndex.getInstance().getAllKeys(PyVariableNameIndex.KEY, project)); return ArrayUtil.toStringArray(symbols); @@ -54,6 +56,7 @@ public class PyGotoSymbolContributor implements GotoClassContributor { List symbols = new ArrayList(); symbols.addAll(PyClassNameIndex.find(name, project, scope)); + symbols.addAll(PyModuleNameIndex.find(name, project, includeNonProjectItems)); symbols.addAll(PyFunctionNameIndex.find(name, project, scope)); symbols.addAll(PyVariableNameIndex.find(name, project, scope)); diff --git a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java index 1fa4b2101a44..c1aa84c0268b 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyFileImpl.java @@ -16,15 +16,19 @@ 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.templateLanguages.TemplateLanguageFileViewProvider; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiModificationTracker; import com.intellij.psi.util.QualifiedName; @@ -33,11 +37,15 @@ 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.*; +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; @@ -49,6 +57,7 @@ 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 { @@ -770,4 +779,78 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression { } 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; + } + }; + } } diff --git a/python/src/com/jetbrains/python/psi/stubs/PyModuleNameIndex.java b/python/src/com/jetbrains/python/psi/stubs/PyModuleNameIndex.java new file mode 100644 index 000000000000..105aec5c0f7b --- /dev/null +++ b/python/src/com/jetbrains/python/psi/stubs/PyModuleNameIndex.java @@ -0,0 +1,113 @@ +/* + * 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.stubs; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.indexing.*; +import com.intellij.util.io.EnumeratorStringDescriptor; +import com.intellij.util.io.KeyDescriptor; +import com.jetbrains.python.PyNames; +import com.jetbrains.python.PythonFileType; +import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.search.PyProjectScopeBuilder; +import org.jetbrains.annotations.NotNull; + +import java.util.*; + +/** + * @author vlan + */ +public class PyModuleNameIndex extends ScalarIndexExtension { + public static final ID NAME = ID.create("Py.module.name"); + + private final EnumeratorStringDescriptor myKeyDescriptor = new EnumeratorStringDescriptor(); + private final DataIndexer myDataIndexer = new DataIndexer() { + @NotNull + @Override + public Map map(FileContent inputData) { + final VirtualFile file = inputData.getFile(); + final String name = file.getName(); + if (PyNames.INIT_DOT_PY.equals(name)) { + final VirtualFile parent = file.getParent(); + if (parent != null && parent.isDirectory()) { + return Collections.singletonMap(parent.getName(), null); + } + } + else { + return Collections.singletonMap(FileUtil.getNameWithoutExtension(name), null); + } + return Collections.emptyMap(); + } + }; + + @NotNull + @Override + public ID getName() { + return NAME; + } + + @NotNull + @Override + public DataIndexer getIndexer() { + return myDataIndexer; + } + + @Override + public KeyDescriptor getKeyDescriptor() { + return myKeyDescriptor; + } + + @Override + public FileBasedIndex.InputFilter getInputFilter() { + return new DefaultFileTypeSpecificInputFilter(PythonFileType.INSTANCE); + } + + @Override + public boolean dependsOnFileContent() { + return true; + } + + @Override + public int getVersion() { + return 0; + } + + @NotNull + public static Collection getAllKeys(@NotNull Project project) { + return FileBasedIndex.getInstance().getAllKeys(NAME, project); + } + + @NotNull + public static List find(@NotNull String name, @NotNull Project project, boolean includeNonProjectItems) { + final List results = new ArrayList(); + final GlobalSearchScope scope = includeNonProjectItems + ? PyProjectScopeBuilder.excludeSdkTestsScope(project) + : GlobalSearchScope.projectScope(project); + final Collection files = FileBasedIndex.getInstance().getContainingFiles(NAME, name, scope); + for (VirtualFile virtualFile : files) { + final PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile); + if (psiFile instanceof PyFile) { + results.add((PyFile)psiFile); + } + } + return results; + } +} diff --git a/python/testData/indexing/ModuleNameIndex/a.py b/python/testData/indexing/ModuleNameIndex/a.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/indexing/ModuleNameIndex/bar/__init__.py b/python/testData/indexing/ModuleNameIndex/bar/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/indexing/ModuleNameIndex/baz.txt b/python/testData/indexing/ModuleNameIndex/baz.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testData/indexing/ModuleNameIndex/foo.py b/python/testData/indexing/ModuleNameIndex/foo.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/python/testSrc/com/jetbrains/python/PyIndexingTest.java b/python/testSrc/com/jetbrains/python/PyIndexingTest.java new file mode 100644 index 000000000000..31b78668c0e1 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/PyIndexingTest.java @@ -0,0 +1,44 @@ +/* + * 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; + +import com.jetbrains.python.fixtures.PyTestCase; +import com.jetbrains.python.psi.stubs.PyModuleNameIndex; + +import java.util.Collection; + +/** + * @author vlan + */ +public class PyIndexingTest extends PyTestCase { + public static final String TEST_DIRECTORY = "indexing/"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + final String testName = getTestName(false); + myFixture.copyDirectoryToProject(TEST_DIRECTORY + testName, ""); + myFixture.configureFromTempProjectFile("a.py"); + } + + public void testModuleNameIndex() { + final Collection modules = PyModuleNameIndex.getAllKeys(myFixture.getProject()); + assertContainsElements(modules, "foo"); + assertContainsElements(modules, "bar"); + assertDoesntContain(modules, "__init__"); + assertDoesntContain(modules, "baz"); + } +}