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");
+ }
+}