From 383dc15c139c575b4ff34de2647208fd8ac9c2fb Mon Sep 17 00:00:00 2001 From: Lada Gagina Date: Mon, 28 Sep 2020 17:39:17 +0300 Subject: [PATCH] PY-44026 Add completion for empty attribute prefix to PyUnresolvedModuleAttributeCompletionContributor GitOrigin-RevId: 3354826398aa85adc54742155da90138cd9a2279 --- .../resources/META-INF/PythonPsiImpl.xml | 2 - ...vedModuleAttributeCompletionContributor.kt | 168 ++++++++++-------- ...ransitiveAttributeCompletionContributor.kt | 63 ------- 3 files changed, 93 insertions(+), 140 deletions(-) delete mode 100644 python/python-psi-impl/src/com/jetbrains/python/codeInsight/completion/PyUnresolvedModuleTransitiveAttributeCompletionContributor.kt 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