mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-44026 Add completion for empty attribute prefix to PyUnresolvedModuleAttributeCompletionContributor
GitOrigin-RevId: 3354826398aa85adc54742155da90138cd9a2279
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8e05b0d7b6
commit
383dc15c13
@@ -110,8 +110,6 @@
|
||||
implementationClass="com.jetbrains.python.codeInsight.completion.PyModulePackageCompletionContributor"/>
|
||||
<completion.contributor language="Python"
|
||||
implementationClass="com.jetbrains.python.codeInsight.completion.PyUnresolvedModuleAttributeCompletionContributor"/>
|
||||
<completion.contributor language="Python"
|
||||
implementationClass="com.jetbrains.python.codeInsight.completion.PyUnresolvedModuleTransitiveAttributeCompletionContributor"/>
|
||||
<completion.contributor language="Python"
|
||||
implementationClass="com.jetbrains.python.codeInsight.completion.PyFStringLikeCompletionContributor"/>
|
||||
<completion.contributor language="Python"
|
||||
|
||||
+93
-75
@@ -1,98 +1,116 @@
|
||||
// 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.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
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.PsiDocumentManager
|
||||
import com.intellij.psi.PsiFileSystemItem
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
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.codeInsight.imports.AddImportHelper
|
||||
import com.jetbrains.python.psi.*
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil
|
||||
import com.jetbrains.python.psi.search.PySearchUtilBase
|
||||
import com.jetbrains.python.psi.stubs.PyModuleNameIndex
|
||||
import com.jetbrains.python.psi.stubs.PyQualifiedNameCompletionMatcher
|
||||
import com.jetbrains.python.psi.stubs.PyQualifiedNameCompletionMatcher.QualifiedNameMatcher
|
||||
|
||||
|
||||
/**
|
||||
* Adds completion variants for unresolved module after dot.
|
||||
*/
|
||||
class PyUnresolvedModuleAttributeCompletionContributor : CompletionContributor() {
|
||||
/**
|
||||
* Checks whether completion should be performed for a given [parameters] and delegates actual work to [doFillCompletionVariants].
|
||||
*/
|
||||
override fun fillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet) {
|
||||
if (!shouldDoCompletion(parameters)) return
|
||||
doFillCompletionVariants(parameters, result)
|
||||
}
|
||||
|
||||
fun doFillCompletionVariants(parameters: CompletionParameters, result: CompletionResultSet) {
|
||||
private companion object {
|
||||
val QUALIFIED_REFERENCE_EXPRESSION = psiElement(PyTokenTypes.IDENTIFIER).withParent(
|
||||
psiElement(PyReferenceExpression::class.java)
|
||||
.andNot(psiElement().inside(PyImportStatementBase::class.java))
|
||||
.with(object : PatternCondition<PyReferenceExpression>("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<QualifiedName> = Key.create("QUALIFIER")
|
||||
|
||||
val element = parameters.position
|
||||
val parent = element.parent
|
||||
if (parent !is PyReferenceExpression || !parent.isQualified) {
|
||||
return
|
||||
private val importingInsertHandler: InsertHandler<LookupElement> = 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<LookupElement> = 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<RuntimeException> {
|
||||
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<RuntimeException> {
|
||||
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<CompletionParameters>() {
|
||||
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.ba<caret>IntellijIdeaRulezzz 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
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-63
@@ -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<PyReferenceExpression>("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<QualifiedName> = Key.create("QUALIFIER")
|
||||
}
|
||||
|
||||
init {
|
||||
extend(CompletionType.BASIC, QUALIFIED_REFERENCE_EXPRESSION, object : CompletionProvider<CompletionParameters>() {
|
||||
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.ba<caret>IntellijIdeaRulezzz 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
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user