diff --git a/python/python-psi-impl/resources/META-INF/PythonPsiImpl.xml b/python/python-psi-impl/resources/META-INF/PythonPsiImpl.xml
index 2871443299a3..7cb41fc10306 100644
--- a/python/python-psi-impl/resources/META-INF/PythonPsiImpl.xml
+++ b/python/python-psi-impl/resources/META-INF/PythonPsiImpl.xml
@@ -110,8 +110,6 @@
implementationClass="com.jetbrains.python.codeInsight.completion.PyModulePackageCompletionContributor"/>
-
("plain qualified name") {
+ override fun accepts(expression: PyReferenceExpression, context: ProcessingContext): Boolean {
+ if (!expression.isQualified) return false
+ val qualifiedName = expression.asQualifiedName() ?: return false
+ context.put(REFERENCE_QUALIFIER, qualifiedName.removeLastComponent())
+ return true
+ }
+ })
+ )
+ val REFERENCE_QUALIFIER: Key = Key.create("QUALIFIER")
- val element = parameters.position
- val parent = element.parent
- if (parent !is PyReferenceExpression || !parent.isQualified) {
- return
+ private val importingInsertHandler: InsertHandler = InsertHandler { context, item ->
+ addImportForLookupElement(context, item, context.tailOffset - 1)
}
- val qualifier = parent.qualifier!!.name ?: return
- val prefixMatcher = PlainPrefixMatcher(qualifier)
-
- val targetFile = parameters.originalFile
- val moduleKeys = PyModuleNameIndex.getAllKeys(targetFile.project)
- val modulesFromIndex = moduleKeys.asSequence()
- .filter { prefixMatcher.prefixMatches(it) }
- .flatMap { PyModuleNameIndex.find(it, targetFile.project, true).asSequence() }
- .toList()
-
- val builders = modulesFromIndex.asSequence()
- .filter { PyUtil.isImportable(targetFile, it) }
- .flatMap { it.iterateNames().asSequence() }
- .filter { it !is PsiFileSystemItem && it.name != null }
- .mapNotNull {
- val lookupString = if (it is PyQualifiedNameOwner && it.qualifiedName != null) it.qualifiedName!! else it.name!!
- LookupElementBuilder.create(it, lookupString)
- .withIcon(it.getIcon(0))
+ private 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)
}
- .map { it.withInsertHandler(importingInsertHandler) }
-
- val newResultSet = result.withPrefixMatcher(PlainPrefixMatcher("$qualifier."))
- builders.forEach { newResultSet.addElement(it) }
- }
-
- private val importingInsertHandler: InsertHandler = InsertHandler { context, item ->
- addImportForLookupElement(context, item, context.tailOffset - 1)
- }
-
- private 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 && psiElement.containingFile != null) {
- val fileName = psiElement.containingFile.name
- val elementNameQualifier = if (psiElement is PyQualifiedNameOwner) psiElement.qualifiedName?.substringBeforeLast('.') else null
- val nameToImport = elementNameQualifier ?: fileName.substringBefore(".py")
- AddImportHelper.addImportStatement(context.file, nameToImport, null, null, ref.element as PyElement)
+ 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 && psiElement.containingFile != null) {
+ val fileName = psiElement.containingFile.name
+ val elementNameQualifier = if (psiElement is PyQualifiedNameOwner) psiElement.qualifiedName?.substringBeforeLast('.') else null
+ val nameToImport = elementNameQualifier ?: fileName.substringBefore(".py")
+ AddImportHelper.addImportStatement(context.file, nameToImport, null, null, ref.element as PyElement)
+ }
}
}
+
+ private fun filterAttributes(it: PyElement, qualifier: String): Boolean {
+ val qualifiedName = (it as? PyQualifiedNameOwner)?.qualifiedName ?: return false
+ return qualifiedName.startsWith(qualifier) && !qualifiedName.substringAfter(qualifier).startsWith("._")
+ }
}
- private fun shouldDoCompletion(parameters: CompletionParameters): Boolean {
- val element = parameters.position
- val parent = element.parent
- if (parent is PyReferenceExpression && parent.isQualified && PsiTreeUtil.getParentOfType(element,
- PyImportStatementBase::class.java) == null) {
- return PyResolveUtil.resolveLocally(parent).isEmpty()
- }
- return false
+ init {
+ extend(CompletionType.BASIC, QUALIFIED_REFERENCE_EXPRESSION, object : CompletionProvider() {
+ override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
+ val originalReferenceExpr = parameters.originalPosition?.parent as? PyReferenceExpression
+ // It cannot be checked in the pattern, because the default placeholder splits the reference, e.g. "foo.baIntellijIdeaRulezzz z".
+ val isOtherReferenceQualifier = originalReferenceExpr?.parent is PyReferenceExpression
+ if (isOtherReferenceQualifier) return
+
+ val project = parameters.position.project
+ val attribute = result.prefixMatcher.prefix
+ val qualifier = context.get(REFERENCE_QUALIFIER).toString()
+ if (attribute.isEmpty()) {
+ val builders = PyModuleNameIndex.find(qualifier, project, true).asSequence()
+ .filter { PyUtil.isImportable(parameters.originalFile, it) }
+ .flatMap { it.iterateNames().asSequence() }
+ .filter { filterAttributes(it, qualifier) }
+ .mapNotNull {
+ val lookupString = if (it is PyQualifiedNameOwner && it.qualifiedName != null) it.qualifiedName!! else it.name!!
+ LookupElementBuilder.create(it, lookupString)
+ .withIcon(it.getIcon(0))
+ .withInsertHandler(importingInsertHandler)
+ }
+
+ val newResultSet = result.withPrefixMatcher(PlainPrefixMatcher("$qualifier."))
+ builders.forEach { newResultSet.addElement(it) }
+
+ result.restartCompletionOnAnyPrefixChange()
+ return
+ }
+ val scope = PySearchUtilBase.excludeSdkTestsScope(project)
+ val resultMatchingCompleteReference = result.withPrefixMatcher(QualifiedNameMatcher(qualifier, attribute))
+ PyQualifiedNameCompletionMatcher.processMatchingExportedNames(qualifier, attribute, parameters.originalFile, scope, Processor {
+ ProgressManager.checkCanceled()
+ resultMatchingCompleteReference.addElement(LookupElementBuilder
+ .createWithSmartPointer(it.qualifiedName.toString(), it.element)
+ .withIcon(it.element.getIcon(0))
+ .withInsertHandler(importingInsertHandler))
+ return@Processor true
+ })
+ }
+ })
}
-}
+
+}
\ No newline at end of file
diff --git a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyUnresolvedModuleTransitiveAttributeCompletionContributor.kt b/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyUnresolvedModuleTransitiveAttributeCompletionContributor.kt
deleted file mode 100644
index 9eeb0b199c28..000000000000
--- a/python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyUnresolvedModuleTransitiveAttributeCompletionContributor.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.jetbrains.python.codeInsight.completion
-
-import com.intellij.codeInsight.completion.*
-import com.intellij.codeInsight.lookup.LookupElementBuilder
-import com.intellij.openapi.progress.ProgressManager
-import com.intellij.openapi.util.Key
-import com.intellij.patterns.PatternCondition
-import com.intellij.patterns.PlatformPatterns.psiElement
-import com.intellij.psi.util.QualifiedName
-import com.intellij.util.ProcessingContext
-import com.intellij.util.Processor
-import com.jetbrains.python.PyTokenTypes
-import com.jetbrains.python.psi.PyImportStatementBase
-import com.jetbrains.python.psi.PyReferenceExpression
-import com.jetbrains.python.psi.search.PySearchUtilBase
-import com.jetbrains.python.psi.stubs.PyQualifiedNameCompletionMatcher
-import com.jetbrains.python.psi.stubs.PyQualifiedNameCompletionMatcher.QualifiedNameMatcher
-
-class PyUnresolvedModuleTransitiveAttributeCompletionContributor : CompletionContributor() {
-
- private companion object {
- val QUALIFIED_REFERENCE_EXPRESSION = psiElement(PyTokenTypes.IDENTIFIER).withParent(
- psiElement(PyReferenceExpression::class.java)
- .andNot(psiElement().inside(PyImportStatementBase::class.java))
- .with(object : PatternCondition("plain qualified name") {
- override fun accepts(expression: PyReferenceExpression, context: ProcessingContext): Boolean {
- if (!expression.isQualified) return false
- val qualifiedName = expression.asQualifiedName() ?: return false
- context.put(REFERENCE_QUALIFIER, qualifiedName.removeLastComponent())
- return true
- }
- })
- )
- val REFERENCE_QUALIFIER: Key = Key.create("QUALIFIER")
- }
-
- init {
- extend(CompletionType.BASIC, QUALIFIED_REFERENCE_EXPRESSION, object : CompletionProvider() {
- override fun addCompletions(parameters: CompletionParameters, context: ProcessingContext, result: CompletionResultSet) {
- val originalReferenceExpr = parameters.originalPosition?.parent as? PyReferenceExpression
- // It cannot be checked in the pattern, because the default placeholder splits the reference, e.g. "foo.baIntellijIdeaRulezzz z".
- val isOtherReferenceQualifier = originalReferenceExpr?.parent is PyReferenceExpression
- if (isOtherReferenceQualifier) return
-
- val attribute = result.prefixMatcher.prefix
- val qualifier = context.get(REFERENCE_QUALIFIER).toString()
- if (attribute.isEmpty()) {
- result.restartCompletionOnAnyPrefixChange()
- return
- }
- val scope = PySearchUtilBase.excludeSdkTestsScope(parameters.position.project)
- val resultMatchingCompleteReference = result.withPrefixMatcher(QualifiedNameMatcher(qualifier, attribute))
- PyQualifiedNameCompletionMatcher.processMatchingExportedNames(qualifier, attribute, parameters.originalFile, scope, Processor {
- ProgressManager.checkCanceled()
- resultMatchingCompleteReference.addElement(LookupElementBuilder
- .createWithSmartPointer(it.qualifiedName.toString(), it.element)
- .withIcon(it.element.getIcon(0)))
- return@Processor true
- })
- }
- })
- }
-}
\ No newline at end of file