mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
DS-4868 Wrong attribute-style completion items for polar dataframes
GitOrigin-RevId: d377d740c358ee22490c891862ad1eb7fbf0f2d8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
b221598456
commit
830dc346e3
@@ -13,6 +13,7 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.patterns.PlatformPatterns
|
import com.intellij.patterns.PlatformPatterns
|
||||||
import com.intellij.util.ProcessingContext
|
import com.intellij.util.ProcessingContext
|
||||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
|
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
|
||||||
|
import com.jetbrains.python.PyTokenTypes
|
||||||
import com.jetbrains.python.debugger.PyDebugValue
|
import com.jetbrains.python.debugger.PyDebugValue
|
||||||
import com.jetbrains.python.debugger.state.PyRuntime
|
import com.jetbrains.python.debugger.state.PyRuntime
|
||||||
import com.jetbrains.python.debugger.values.DataFrameDebugValue
|
import com.jetbrains.python.debugger.values.DataFrameDebugValue
|
||||||
@@ -34,9 +35,10 @@ private fun postProcessingChildren(completionResultData: CompletionResultData,
|
|||||||
return when (completionResultData.completionType) {
|
return when (completionResultData.completionType) {
|
||||||
PyRuntimeCompletionType.DATA_FRAME_COLUMNS -> {
|
PyRuntimeCompletionType.DATA_FRAME_COLUMNS -> {
|
||||||
val project = parameters.editor.project ?: return emptyList()
|
val project = parameters.editor.project ?: return emptyList()
|
||||||
processDataFrameColumns(candidate.psiName,
|
val needValidatorCheck = (candidate.pyQualifiedExpressionList.lastOrNull()?.delimiter ?: candidate.psiName.delimiter) != PyTokenTypes.LBRACKET
|
||||||
|
processDataFrameColumns(candidate.psiName.pyQualifiedName,
|
||||||
completionResultData.setOfCompletionItems,
|
completionResultData.setOfCompletionItems,
|
||||||
candidate.needValidatorCheck,
|
needValidatorCheck,
|
||||||
parameters.position,
|
parameters.position,
|
||||||
project,
|
project,
|
||||||
true)
|
true)
|
||||||
@@ -52,12 +54,12 @@ interface PyRuntimeCompletionRetrievalService {
|
|||||||
*/
|
*/
|
||||||
fun canComplete(parameters: CompletionParameters): Boolean
|
fun canComplete(parameters: CompletionParameters): Boolean
|
||||||
|
|
||||||
fun extractItemsForCompletion(result: Pair<XValueNodeImpl, List<String>>?,
|
fun extractItemsForCompletion(result: Pair<XValueNodeImpl, List<PyQualifiedExpressionItem>>?,
|
||||||
candidate: PyObjectCandidate): CompletionResultData? {
|
candidate: PyObjectCandidate): CompletionResultData? {
|
||||||
val (node, listOfCalls) = result ?: return null
|
val (node, listOfCalls) = result ?: return null
|
||||||
val debugValue = node.valueContainer
|
val debugValue = node.valueContainer
|
||||||
if (debugValue is DataFrameDebugValue) {
|
if (debugValue is DataFrameDebugValue) {
|
||||||
val dfColumns = completePandasDataFrameColumns(debugValue.treeColumns, listOfCalls) ?: return null
|
val dfColumns = completePandasDataFrameColumns(debugValue.treeColumns, listOfCalls.map { it.pyQualifiedName }) ?: return null
|
||||||
return CompletionResultData(dfColumns, PyRuntimeCompletionType.DATA_FRAME_COLUMNS)
|
return CompletionResultData(dfColumns, PyRuntimeCompletionType.DATA_FRAME_COLUMNS)
|
||||||
}
|
}
|
||||||
computeChildrenIfNeeded(node)
|
computeChildrenIfNeeded(node)
|
||||||
@@ -119,11 +121,10 @@ abstract class AbstractRuntimeCompletionContributor : CompletionContributor(), D
|
|||||||
abstract fun getCompletionRetrievalService(project: Project): PyRuntimeCompletionRetrievalService
|
abstract fun getCompletionRetrievalService(project: Project): PyRuntimeCompletionRetrievalService
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createCompletionResultSet(
|
|
||||||
retrievalService: PyRuntimeCompletionRetrievalService,
|
fun createCompletionResultSet(retrievalService: PyRuntimeCompletionRetrievalService,
|
||||||
runtimeService: PyRuntime,
|
runtimeService: PyRuntime,
|
||||||
parameters: CompletionParameters,
|
parameters: CompletionParameters): List<LookupElement> {
|
||||||
): List<LookupElement> {
|
|
||||||
if (!retrievalService.canComplete(parameters)) return emptyList()
|
if (!retrievalService.canComplete(parameters)) return emptyList()
|
||||||
val project = parameters.editor.project ?: return emptyList()
|
val project = parameters.editor.project ?: return emptyList()
|
||||||
val treeNodeList = runtimeService.getGlobalPythonVariables(parameters.originalFile.virtualFile, project, parameters.editor)
|
val treeNodeList = runtimeService.getGlobalPythonVariables(parameters.originalFile.virtualFile, project, parameters.editor)
|
||||||
@@ -132,15 +133,17 @@ private fun createCompletionResultSet(
|
|||||||
|
|
||||||
return ApplicationUtil.runWithCheckCanceled(Callable {
|
return ApplicationUtil.runWithCheckCanceled(Callable {
|
||||||
return@Callable pyObjectCandidates.flatMap { candidate ->
|
return@Callable pyObjectCandidates.flatMap { candidate ->
|
||||||
val parentNode = getParentNodeByName(treeNodeList, candidate.psiName)
|
val parentNode = getParentNodeByName(treeNodeList, candidate.psiName.pyQualifiedName)
|
||||||
val valueContainer = parentNode?.valueContainer
|
val valueContainer = parentNode?.valueContainer
|
||||||
/**
|
if (valueContainer is PyDebugValue) {
|
||||||
* Don't need to send requests to jupyter server about Python's module,
|
/**
|
||||||
* because LegacyCompletionContributor provide completion items for Python's modules
|
* Don't need to send requests to jupyter server about Python's module,
|
||||||
* @see com.intellij.codeInsight.completion.LegacyCompletionContributor
|
* because LegacyCompletionContributor provide completion items for Python's modules
|
||||||
*/
|
* @see com.intellij.codeInsight.completion.LegacyCompletionContributor
|
||||||
if (valueContainer is PyDebugValue && valueContainer.type == "module") return@flatMap emptyList()
|
*/
|
||||||
|
if (valueContainer.type == "module") return@flatMap emptyList()
|
||||||
|
if (checkDelimiterByType(valueContainer.qualifiedType, candidate.psiName.delimiter)) return@flatMap emptyList()
|
||||||
|
}
|
||||||
getSetOfChildrenByListOfCall(parentNode, candidate.pyQualifiedExpressionList)
|
getSetOfChildrenByListOfCall(parentNode, candidate.pyQualifiedExpressionList)
|
||||||
.let { retrievalService.extractItemsForCompletion(it, candidate) }
|
.let { retrievalService.extractItemsForCompletion(it, candidate) }
|
||||||
?.let { postProcessingChildren(it, candidate, parameters) }
|
?.let { postProcessingChildren(it, candidate, parameters) }
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import com.intellij.openapi.project.Project
|
|||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.tree.IElementType
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
import com.intellij.psi.util.elementType
|
import com.intellij.psi.util.elementType
|
||||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeListener
|
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeListener
|
||||||
@@ -24,6 +25,7 @@ import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl
|
|||||||
import com.jetbrains.python.PyBundle
|
import com.jetbrains.python.PyBundle
|
||||||
import com.jetbrains.python.PyTokenTypes
|
import com.jetbrains.python.PyTokenTypes
|
||||||
import com.jetbrains.python.PythonLanguage
|
import com.jetbrains.python.PythonLanguage
|
||||||
|
import com.jetbrains.python.debugger.PyDebugValue
|
||||||
import com.jetbrains.python.debugger.PyXValueGroup
|
import com.jetbrains.python.debugger.PyXValueGroup
|
||||||
import com.jetbrains.python.debugger.pydev.ProcessDebugger
|
import com.jetbrains.python.debugger.pydev.ProcessDebugger
|
||||||
import com.jetbrains.python.debugger.values.DataFrameDebugValue
|
import com.jetbrains.python.debugger.values.DataFrameDebugValue
|
||||||
@@ -33,16 +35,21 @@ import com.jetbrains.python.psi.impl.PyStringLiteralExpressionImpl
|
|||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
import javax.swing.tree.TreeNode
|
import javax.swing.tree.TreeNode
|
||||||
|
|
||||||
|
data class PyQualifiedExpressionItem(val pyQualifiedName: String, val delimiter: IElementType)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This data class stores information about a possible python object.
|
* This data class stores information about a possible python object.
|
||||||
* @param psiName - name of PsiElement, that could be a python object
|
* @param psiName - name of PsiElement, that could be a python object
|
||||||
* @param needValidatorCheck - boolean flag to switch on check for validating completion items name
|
|
||||||
* @param pyQualifiedExpressionList - represents a qualified expression in the list
|
|
||||||
* LanguageNamesValidation.INSTANCE.forLanguage(PythonLanguage.getInstance()).isIdentifier()
|
* LanguageNamesValidation.INSTANCE.forLanguage(PythonLanguage.getInstance()).isIdentifier()
|
||||||
|
* @param pyQualifiedExpressionList - represents a qualified expression in the list
|
||||||
|
* @see PyQualifiedExpressionItem
|
||||||
*
|
*
|
||||||
* An example "a.b.c": psiName = "a", needValidatorCheck = false, pyQualifiedExpressionList = ["b","c"]
|
* An example "a.b.c.":
|
||||||
|
* psiName = PyQualifiedExpressionItem("a",PyTokenTypes.DOT),
|
||||||
|
* pyQualifiedExpressionList = [PyQualifiedExpressionItem("b",PyTokenTypes.DOT),PyQualifiedExpressionItem("c",PyTokenTypes.DOT)]
|
||||||
*/
|
*/
|
||||||
data class PyObjectCandidate(val psiName: String, val needValidatorCheck: Boolean, val pyQualifiedExpressionList: List<String>)
|
data class PyObjectCandidate(val psiName: PyQualifiedExpressionItem,
|
||||||
|
val pyQualifiedExpressionList: List<PyQualifiedExpressionItem>)
|
||||||
|
|
||||||
|
|
||||||
// Temporary priority value to control order in CompletionResultSet (DS-3746)
|
// Temporary priority value to control order in CompletionResultSet (DS-3746)
|
||||||
@@ -51,9 +58,8 @@ private const val RUNTIME_COMPLETION_PRIORITY = 100.0
|
|||||||
fun getCompleteAttribute(parameters: CompletionParameters): List<PyObjectCandidate> {
|
fun getCompleteAttribute(parameters: CompletionParameters): List<PyObjectCandidate> {
|
||||||
|
|
||||||
val callInnerReferenceExpression = getCallInnerReferenceExpression(parameters)
|
val callInnerReferenceExpression = getCallInnerReferenceExpression(parameters)
|
||||||
val (currentElement, needValidatorCheck) =
|
val (currentElement, lastDelimiter) = findCompleteAttribute(parameters) ?: return getPossibleObjectsDataFrame(parameters,
|
||||||
findCompleteAttribute(parameters)
|
callInnerReferenceExpression)
|
||||||
?: return getPossibleObjectsDataFrame(parameters, callInnerReferenceExpression)
|
|
||||||
|
|
||||||
return when (currentElement) {
|
return when (currentElement) {
|
||||||
is PyCallExpression, is PyParenthesizedExpression -> {
|
is PyCallExpression, is PyParenthesizedExpression -> {
|
||||||
@@ -61,10 +67,10 @@ fun getCompleteAttribute(parameters: CompletionParameters): List<PyObjectCandida
|
|||||||
}
|
}
|
||||||
is PyExpression -> buildList {
|
is PyExpression -> buildList {
|
||||||
val parentLambdaExpression = collectParentOfLambdaExpression(currentElement, callInnerReferenceExpression)
|
val parentLambdaExpression = collectParentOfLambdaExpression(currentElement, callInnerReferenceExpression)
|
||||||
parentLambdaExpression?.let {
|
parentLambdaExpression?.let { expression ->
|
||||||
add(createPandasDataFrameCandidate(it, needValidatorCheck))
|
createPyObjectCandidate(expression, lastDelimiter)?.let { add(it) }
|
||||||
}
|
}
|
||||||
add(createPandasDataFrameCandidate(currentElement, needValidatorCheck))
|
createPyObjectCandidate(currentElement, lastDelimiter)?.let { add(it) }
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
emptyList()
|
emptyList()
|
||||||
@@ -72,36 +78,56 @@ fun getCompleteAttribute(parameters: CompletionParameters): List<PyObjectCandida
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createPandasDataFrameCandidate(psiElement: PsiElement, needValidatorCheck: Boolean): PyObjectCandidate {
|
private fun getPyElementText(child: PsiElement?): String? {
|
||||||
val columns = mutableListOf<String>()
|
child ?: return null
|
||||||
|
if (child.elementType != PyTokenTypes.DOT && child.elementType != PyTokenTypes.LBRACKET && child.elementType != PyTokenTypes.RBRACKET) {
|
||||||
|
return when (child) {
|
||||||
|
is PyStringLiteralExpression -> {
|
||||||
|
return (child as PyStringLiteralExpressionImpl).stringValue
|
||||||
|
}
|
||||||
|
is PyStringElement -> {
|
||||||
|
child.content
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
child.text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createPyObjectCandidate(psiElement: PsiElement, lastDelimiter: IElementType): PyObjectCandidate? {
|
||||||
|
val names = mutableListOf<String>()
|
||||||
|
val delimiter = mutableListOf<IElementType>()
|
||||||
|
|
||||||
val firstChild: PsiElement = PsiTreeUtil.getDeepestFirst(psiElement)
|
val firstChild: PsiElement = PsiTreeUtil.getDeepestFirst(psiElement)
|
||||||
|
|
||||||
val lastChild = PsiTreeUtil.getDeepestLast(psiElement)
|
val lastChild = PsiTreeUtil.getDeepestLast(psiElement)
|
||||||
if (firstChild != lastChild) {
|
if (firstChild != lastChild) {
|
||||||
var child = PsiTreeUtil.nextLeaf(firstChild)
|
var child = PsiTreeUtil.nextLeaf(firstChild)
|
||||||
while (child != null) {
|
while (child != null) {
|
||||||
if (child.elementType != PyTokenTypes.DOT && child.elementType != PyTokenTypes.LBRACKET && child.elementType != PyTokenTypes.RBRACKET) {
|
if (child.elementType == PyTokenTypes.DOT || child.elementType == PyTokenTypes.LBRACKET) {
|
||||||
when (child) {
|
child.elementType?.let { delimiter.add(it) }
|
||||||
is PyStringLiteralExpression -> {
|
child = PsiTreeUtil.nextLeaf(child)
|
||||||
columns.add((child as PyStringLiteralExpressionImpl).stringValue)
|
continue
|
||||||
}
|
|
||||||
is PyStringElement -> {
|
|
||||||
columns.add(child.content)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
columns.add(child.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
getPyElementText(child)?.let { names.add(it) }
|
||||||
if (child == lastChild) {
|
if (child == lastChild) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
child = PsiTreeUtil.nextLeaf(child)
|
child = PsiTreeUtil.nextLeaf(child)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return PyObjectCandidate(firstChild.text, needValidatorCheck, columns)
|
if (delimiter.size == names.size) {
|
||||||
|
val firstDelimiter = delimiter.removeAt(0)
|
||||||
|
delimiter.add(lastDelimiter)
|
||||||
|
return PyObjectCandidate(PyQualifiedExpressionItem(firstChild.text, firstDelimiter),
|
||||||
|
names.zip(delimiter).map { pair ->
|
||||||
|
PyQualifiedExpressionItem(pair.first, pair.second)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return PyObjectCandidate(PyQualifiedExpressionItem(firstChild.text, lastDelimiter), emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPossibleObjectsDataFrame(parameters: CompletionParameters,
|
private fun getPossibleObjectsDataFrame(parameters: CompletionParameters,
|
||||||
@@ -109,22 +135,25 @@ private fun getPossibleObjectsDataFrame(parameters: CompletionParameters,
|
|||||||
return setOfNotNull(callInnerReferenceExpression?.text, getSliceSubscriptionReferenceExpression(parameters)?.text,
|
return setOfNotNull(callInnerReferenceExpression?.text, getSliceSubscriptionReferenceExpression(parameters)?.text,
|
||||||
getAttributeReferenceExpression(parameters)?.text).map {
|
getAttributeReferenceExpression(parameters)?.text).map {
|
||||||
|
|
||||||
PyObjectCandidate(it, false, emptyList())
|
PyObjectCandidate(PyQualifiedExpressionItem(it, PyTokenTypes.LBRACKET), emptyList())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findCompleteAttribute(parameters: CompletionParameters): Pair<PsiElement, Boolean>? {
|
private fun findCompleteAttribute(parameters: CompletionParameters): Pair<PsiElement, IElementType>? {
|
||||||
var needCheck = true
|
|
||||||
var element = parameters.position
|
var element = parameters.position
|
||||||
|
val delimiter: IElementType?
|
||||||
|
|
||||||
if (element.prevSibling?.elementType != PyTokenTypes.DOT && element.parent?.prevSibling?.elementType == PyTokenTypes.LBRACKET) {
|
if (element.prevSibling?.elementType != PyTokenTypes.DOT && element.parent?.prevSibling?.elementType == PyTokenTypes.LBRACKET) {
|
||||||
needCheck = false
|
delimiter = element.parent?.prevSibling?.elementType
|
||||||
element = element.parent
|
element = element.parent
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
delimiter = element.prevSibling?.elementType
|
||||||
|
}
|
||||||
|
|
||||||
val exactElement = element.prevSibling?.prevSibling
|
val exactElement = element.prevSibling?.prevSibling
|
||||||
return when {
|
return when {
|
||||||
exactElement != null -> Pair(exactElement, needCheck)
|
exactElement != null && delimiter != null -> Pair(exactElement, delimiter)
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -296,7 +325,21 @@ fun getParentNodeByName(children: List<TreeNode>, psiName: String): XValueNodeIm
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getSetOfChildrenByListOfCall(valueNode: XValueNodeImpl?, listOfCall: List<String>): Pair<XValueNodeImpl, List<String>>? {
|
val typeToDelimiter = mapOf(
|
||||||
|
"polars.internals.dataframe.frame.DataFrame" to setOf(PyTokenTypes.LBRACKET),
|
||||||
|
"polars.dataframe.frame.DataFrame" to setOf(PyTokenTypes.LBRACKET),
|
||||||
|
"pandas.core.frame.DataFrame" to setOf(PyTokenTypes.LBRACKET, PyTokenTypes.DOT)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun checkDelimiterByType(qualifiedType: String?, delimiter: IElementType): Boolean {
|
||||||
|
qualifiedType ?: return false
|
||||||
|
val delimiters = typeToDelimiter[qualifiedType]
|
||||||
|
if (delimiters != null && delimiter !in delimiters) return true
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSetOfChildrenByListOfCall(valueNode: XValueNodeImpl?,
|
||||||
|
listOfCall: List<PyQualifiedExpressionItem>): Pair<XValueNodeImpl, List<PyQualifiedExpressionItem>>? {
|
||||||
var currentNode = valueNode ?: return null
|
var currentNode = valueNode ?: return null
|
||||||
|
|
||||||
listOfCall.forEachIndexed { index, call ->
|
listOfCall.forEachIndexed { index, call ->
|
||||||
@@ -306,9 +349,13 @@ fun getSetOfChildrenByListOfCall(valueNode: XValueNodeImpl?, listOfCall: List<St
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
computeChildrenIfNeeded(currentNode)
|
computeChildrenIfNeeded(currentNode)
|
||||||
currentNode = extractChildByName(currentNode.children, call) ?: return null
|
currentNode = extractChildByName(currentNode.children, call.pyQualifiedName) ?: return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val valueContainer = currentNode.valueContainer
|
||||||
|
if (valueContainer is PyDebugValue) {
|
||||||
|
if (checkDelimiterByType(valueContainer.qualifiedType, call.delimiter)) return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Pair(currentNode, emptyList())
|
return Pair(currentNode, emptyList())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user