diff --git a/python/python-psi-impl/resources/intellij.python.psi.impl.xml b/python/python-psi-impl/resources/intellij.python.psi.impl.xml
index e830886909b5..9a7d8241b640 100644
--- a/python/python-psi-impl/resources/intellij.python.psi.impl.xml
+++ b/python/python-psi-impl/resources/intellij.python.psi.impl.xml
@@ -158,10 +158,6 @@
-
-
(ThrowableRunnable {
val scope = createScope(originalFile)
val alreadySuggested: MutableSet = HashSet()
+
+ // Suggest importable modules and packages
+ val moduleKeys = PyModuleNameIndex.getAllKeys(originalFile.project)
+ val modulesFromIndex = moduleKeys.asSequence()
+ .filter { result.prefixMatcher.isStartMatch(it) }
+ // TODO Is there lazier API here?
+ .flatMap { PyModuleNameIndex.findByShortName(it, originalFile.project, scope) }
+ .toList()
+
+ val resolveContext = fromFoothold(originalFile)
+ modulesFromIndex.asSequence()
+ // TODO Do we need this resolve here?
+ .flatMap { resolve(it, resolveContext) }
+ .filter { PyUtil.isImportable(originalFile, it) }
+ .mapNotNull { createLookupElementBuilder(originalFile, it) }
+ .map { it.withInsertHandler(when {
+ insideStringLiteralInExtendedCompletion -> InsertHandlers.stringLiteralInsertHandler
+ else -> InsertHandlers.importingInsertHandler
+ }) }
+ .map { PrioritizedLookupElement.withPriority(it, PythonCompletionWeigher.NOT_IMPORTED_MODULE_WEIGHT.toDouble()) }
+ .forEach { result.addElement(it) }
+
+ // Suggest top-level importable names
forEachPublicNameFromIndex(scope) { elementName: String ->
ProgressManager.checkCanceled()
counters.scannedNames++
@@ -164,6 +189,12 @@ class PyClassNameCompletionContributor : CompletionContributor(), DumbAware {
})
}
+ private fun resolve(module: PsiFile, resolveContext: PyQualifiedNameResolveContext): Sequence {
+ val qualifiedName = QualifiedNameFinder.findCanonicalImportPath(module, null) ?: return emptySequence()
+ return resolveQualifiedName(qualifiedName, resolveContext).asSequence()
+ .filterIsInstance()
+ }
+
private fun getInsertHandler(
exported: PyElement,
position: PsiElement,
diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyCompletionUtils.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyCompletionUtils.kt
index 5afabb72a783..750ec6343143 100644
--- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyCompletionUtils.kt
+++ b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyCompletionUtils.kt
@@ -6,8 +6,7 @@ import com.intellij.codeInsight.completion.AutoCompletionContext
import com.intellij.codeInsight.completion.AutoCompletionDecision
import com.intellij.codeInsight.completion.CompletionParameters
import com.intellij.codeInsight.completion.CompletionResultSet
-import com.intellij.codeInsight.lookup.LookupElementBuilder
-import com.intellij.codeInsight.lookup.TailTypeDecorator
+import com.intellij.codeInsight.lookup.*
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.util.io.FileUtil
@@ -22,10 +21,10 @@ import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil
import com.jetbrains.python.psi.PyClass
import com.jetbrains.python.psi.PyFile
import com.jetbrains.python.psi.PyFunction
+import com.jetbrains.python.psi.icons.PythonPsiApiIcons
import com.jetbrains.python.psi.resolve.QualifiedNameFinder
import com.jetbrains.python.psi.types.TypeEvalContext
import com.jetbrains.python.sdk.legacy.PythonSdkUtil
-import com.jetbrains.python.psi.icons.PythonPsiApiIcons
import com.jetbrains.python.sdk.skeleton.PySkeletonUtil
import one.util.streamex.StreamEx
@@ -101,15 +100,18 @@ fun createLookupElementBuilder(file: PsiFile, element: PsiFileSystemItem): Looku
val name = FileUtil.getNameWithoutExtension(element.name)
if (!PyNames.isIdentifier(name)) return null
- val importPath = QualifiedNameFinder.findCanonicalImportPath(element, file)?.removeLastComponent()
- val tailText = if (importPath != null && importPath.componentCount > 0) " ($importPath)" else null
-
return LookupElementBuilder.create(element, name)
- .withTailText(tailText, true)
- .withIcon(element.getIcon(0))
+ .withExpensiveRenderer(object : LookupElementRenderer() {
+ override fun renderElement(lookupElement: LookupElement, presentation: LookupElementPresentation) {
+ val importPath = QualifiedNameFinder.findCanonicalImportPath(element, file)?.removeLastComponent()
+ presentation.setItemText(lookupElement.getLookupString())
+ presentation.setIcon(element.getIcon(0))
+ if (importPath == null) return
+ presentation.typeText = importPath.toString()
+ }
+ })
}
-
private const val ELEMENT_TYPE = 10
private const val LOCATION = 100
private const val PRIVATE_API = 1_000
diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyImportableNameCompletionContributor.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyImportableNameCompletionContributor.kt
deleted file mode 100644
index cdf5d7fa1a54..000000000000
--- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyImportableNameCompletionContributor.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
-package com.jetbrains.python.codeInsight.completion
-
-import com.intellij.codeInsight.completion.*
-import com.intellij.codeInsight.lookup.LookupElement
-import com.intellij.openapi.command.WriteCommandAction
-import com.intellij.openapi.project.DumbAware
-import com.intellij.patterns.StandardPatterns
-import com.intellij.psi.PsiDocumentManager
-import com.intellij.psi.PsiNamedElement
-import com.intellij.psi.util.PsiTreeUtil
-import com.jetbrains.python.codeInsight.imports.AddImportHelper
-import com.jetbrains.python.psi.*
-import com.jetbrains.python.psi.resolve.QualifiedNameFinder
-
-/**
- * Provides basic functionality for providing completion variants that should add an import statement or be expanded into a qualified name.
- */
-abstract class PyImportableNameCompletionContributor : CompletionContributor(), DumbAware {
-
- protected val importingInsertHandler: InsertHandler = InsertHandler { context, item ->
- addImportForLookupElement(context, item, context.tailOffset - 1)
- }
-
- protected val functionInsertHandler: InsertHandler = object : PyFunctionInsertHandler() {
- override fun handleInsert(context: InsertionContext, item: LookupElement) {
- val tailOffset = context.tailOffset - 1
- super.handleInsert(context, item) // adds parentheses, modifies tail offset
- context.commitDocument()
- addImportForLookupElement(context, item, tailOffset)
- }
- }
-
- protected val genericTypeInsertHandler: InsertHandler = InsertHandler { context, item ->
- val tailOffset = context.tailOffset - 1
- PyParameterizedTypeInsertHandler.INSTANCE.handleInsert(context, item)
- context.commitDocument()
- addImportForLookupElement(context, item, tailOffset)
- }
-
- protected val stringLiteralInsertHandler: InsertHandler = InsertHandler { context, item ->
- val element = item.psiElement
- if (element == null) return@InsertHandler
- if (element is PyQualifiedNameOwner) {
- insertStringLiteralPrefix(element.qualifiedName, element.name, context)
- }
- else {
- val importPath = QualifiedNameFinder.findCanonicalImportPath(element, null)
- if (importPath != null) {
- insertStringLiteralPrefix(importPath.toString(), importPath.lastComponent.toString(), context)
- }
- }
- }
-
- private fun insertStringLiteralPrefix(qualifiedName: String?, name: String?, context: InsertionContext) {
- if (qualifiedName != null && name != null) {
- val qualifiedNamePrefix = qualifiedName.substring(0, qualifiedName.length - name.length)
- context.document.insertString(context.startOffset, qualifiedNamePrefix)
- }
- }
-
- /**
- * Checks whether completion should be performed for a given [parameters] and delegates actual work to [doFillCompletionVariants].
- */
- final override fun fillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet) {
- if (!shouldDoCompletion(parameters, result)) return
- doFillCompletionVariants(parameters, result)
- }
-
- /**
- * Subclasses should override the method to provide completion variants.
- */
- protected abstract fun doFillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet)
-
- private fun shouldDoCompletion(parameters: CompletionParameters, result: CompletionResultSet): Boolean {
- if (result.prefixMatcher.prefix.isEmpty()) {
- result.restartCompletionOnPrefixChange(StandardPatterns.string().longerThan(0))
- return false
- }
-
- val element = parameters.position
- val parent = element.parent
- if (parent is PyReferenceExpression && parent.isQualified) {
- return false
- }
- if (parent is PyStringLiteralExpression) {
- val prefix = parent.text.substring(0, parameters.offset - parent.textRange.startOffset)
- if (prefix.contains(".")) {
- return false
- }
- }
-
- return PsiTreeUtil.getParentOfType(element, PyImportStatementBase::class.java) == null
- }
-
- fun addImportForLookupElement(context: InsertionContext, item: LookupElement, tailOffset: Int) {
- val manager = PsiDocumentManager.getInstance(context.project)
- val document = manager.getDocument(context.file)
- if (document != null) {
- manager.commitDocument(document)
- }
- val ref = context.file.findReferenceAt(tailOffset)
- if (ref == null || ref.resolve() === item.psiElement) {
- // no import statement needed
- return
- }
- WriteCommandAction.writeCommandAction(context.project, context.file).run {
- val psiElement = item.psiElement
- if (psiElement is PsiNamedElement) {
- AddImportHelper.addImport(psiElement, context.file, ref.element as PyElement)
- }
- }
- }
-}
diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyModulePackageCompletionContributor.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyModulePackageCompletionContributor.kt
deleted file mode 100644
index 2e0648322f4d..000000000000
--- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyModulePackageCompletionContributor.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
-package com.jetbrains.python.codeInsight.completion
-
-import com.intellij.codeInsight.completion.CompletionParameters
-import com.intellij.codeInsight.completion.CompletionResultSet
-import com.intellij.codeInsight.completion.PrioritizedLookupElement
-import com.intellij.psi.PsiFile
-import com.intellij.psi.PsiFileSystemItem
-import com.jetbrains.python.psi.PyStringLiteralExpression
-import com.jetbrains.python.psi.PyUtil
-import com.jetbrains.python.psi.resolve.PyQualifiedNameResolveContext
-import com.jetbrains.python.psi.resolve.QualifiedNameFinder
-import com.jetbrains.python.psi.resolve.fromFoothold
-import com.jetbrains.python.psi.resolve.resolveQualifiedName
-import com.jetbrains.python.psi.search.PySearchUtilBase
-import com.jetbrains.python.psi.stubs.PyModuleNameIndex
-
-/**
- * Add completion variants for modules and packages.
- *
- * The completion contributor ensures that completion variants are resolvable with project source root configuration.
- * The list of completion variants does not include namespace packages (but includes their modules where appropriate).
- */
-class PyModulePackageCompletionContributor : PyImportableNameCompletionContributor() {
-
- override fun doFillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet) {
- if (!parameters.isExtendedCompletion) {
- return
- }
-
- val targetFile = parameters.originalFile
- val inStringLiteral = parameters.position.parent is PyStringLiteralExpression
- val moduleKeys = PyModuleNameIndex.getAllKeys(targetFile.project)
- val scope = PySearchUtilBase.defaultSuggestionScope(targetFile)
- val modulesFromIndex = moduleKeys.asSequence()
- .filter { result.prefixMatcher.prefixMatches(it) }
- .flatMap { PyModuleNameIndex.findByShortName(it, targetFile.project, scope).asSequence() }
- .toList()
-
- val resolveContext = fromFoothold(targetFile)
- modulesFromIndex.asSequence()
- .flatMap { resolve(it, resolveContext) }
- .filter { PyUtil.isImportable(targetFile, it) }
- .mapNotNull { createLookupElementBuilder(targetFile, it) }
- .map { it.withInsertHandler(if (inStringLiteral) stringLiteralInsertHandler else importingInsertHandler) }
- .map { PrioritizedLookupElement.withPriority(it, PythonCompletionWeigher.NOT_IMPORTED_MODULE_WEIGHT.toDouble()) }
- .forEach { result.addElement(it) }
- }
-
- private fun resolve(module: PsiFile, resolveContext: PyQualifiedNameResolveContext): Sequence {
- val qualifiedName = QualifiedNameFinder.findCanonicalImportPath(module, null) ?: return emptySequence()
- return resolveQualifiedName(qualifiedName, resolveContext).asSequence()
- .filterIsInstance()
- }
-
-}