Skip to content

Commit 6a828df

Browse files
committed
feat(python): add lookup funciton for target
1 parent c30111d commit 6a828df

File tree

1 file changed

+135
-5
lines changed

1 file changed

+135
-5
lines changed

pycharm/src/main/kotlin/cc/unitmesh/pycharm/provider/PythonLivingDocumentation.kt

Lines changed: 135 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@ package cc.unitmesh.pycharm.provider
22

33
import cc.unitmesh.devti.custom.document.LivingDocumentationType
44
import cc.unitmesh.devti.provider.LivingDocumentation
5+
import com.intellij.codeInsight.daemon.impl.CollectHighlightsUtil
6+
import com.intellij.openapi.editor.Document
57
import com.intellij.openapi.editor.Editor
68
import com.intellij.openapi.editor.SelectionModel
9+
import com.intellij.openapi.project.Project
710
import com.intellij.psi.PsiElement
811
import com.intellij.psi.PsiNameIdentifierOwner
12+
import com.intellij.psi.PsiNamedElement
13+
import com.intellij.psi.PsiWhiteSpace
14+
import com.intellij.psi.util.PsiTreeUtil
15+
import com.intellij.psi.util.PsiUtilBase
916
import com.intellij.util.IncorrectOperationException
1017
import com.jetbrains.python.documentation.docstrings.PyDocstringGenerator
11-
import com.jetbrains.python.psi.PyDocStringOwner
18+
import com.jetbrains.python.psi.*
19+
1220

1321
class PythonLivingDocumentation : LivingDocumentation {
1422
override val forbiddenRules: List<String> = listOf()
@@ -23,18 +31,140 @@ class PythonLivingDocumentation : LivingDocumentation {
2331
}
2432

2533
val docstringGenerator = PyDocstringGenerator.forDocStringOwner((target as PyDocStringOwner?)!!)
26-
// docstringGenerator.buildAndInsert(newDoc)
34+
docstringGenerator.buildAndInsert(newDoc, target)
2735
}
2836

29-
override fun findNearestDocumentationTarget(psiElement: PsiElement): PsiNameIdentifierOwner? {
30-
TODO("Not yet implemented")
37+
// fun findNearestDocumentationTargetForCaret(editor: Editor): PsiNameIdentifierOwner? {
38+
// val element = PsiUtilBase.getElementAtCaret(editor) ?: return null
39+
//
40+
// val pyFile = PsiTreeUtil.getParentOfType(element, PsiNamedElement::class.java, false) as? PyFile
41+
// val caretOffset = editor.caretModel.offset
42+
//
43+
// return when {
44+
// pyFile is PyFile -> {
45+
// return (pyFile.topLevelClasses + pyFile.topLevelFunctions)
46+
// .filterIsInstance<PsiNameIdentifierOwner>()
47+
// .filter { it.textRange.startOffset >= caretOffset }
48+
// .minByOrNull { it.textRange.startOffset - caretOffset }
49+
// }
50+
//
51+
// else -> findNearestDocumentationTarget(element)
52+
// }
53+
// }
54+
55+
override fun findNearestDocumentationTarget(element: PsiElement): PsiNameIdentifierOwner? {
56+
return when {
57+
element is PyFunction || element is PyClass -> element as PsiNameIdentifierOwner
58+
else -> {
59+
val closestIdentifierOwner = PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner::class.java)
60+
if (closestIdentifierOwner !is PyFunction) {
61+
val psiNameIdentifierOwner = PsiTreeUtil.getParentOfType(element, PyFunction::class.java) as? PsiNameIdentifierOwner
62+
return psiNameIdentifierOwner ?: closestIdentifierOwner
63+
}
64+
closestIdentifierOwner
65+
}
66+
}
3167
}
3268

69+
3370
override fun findDocTargetsInSelection(
3471
root: PsiElement,
3572
selectionModel: SelectionModel
3673
): List<PsiNameIdentifierOwner> {
37-
TODO("Not yet implemented")
74+
val commonParent =
75+
CollectHighlightsUtil.findCommonParent(root, selectionModel.selectionStart, selectionModel.selectionEnd)
76+
?: return emptyList()
77+
78+
return when (commonParent) {
79+
is PyFile -> {
80+
val topLevelClasses = commonParent.topLevelClasses
81+
val topLevelFunctions = commonParent.topLevelFunctions
82+
getSelectedClassesAndFunctions(topLevelClasses, topLevelFunctions, selectionModel)
83+
}
84+
85+
is PyClass -> {
86+
val documentationTarget = findNearestDocumentationTarget(commonParent) ?: return emptyList()
87+
88+
if (documentationTarget is PyClass && !containsElement(
89+
selectionModel,
90+
documentationTarget as PsiElement
91+
)
92+
) {
93+
val nestedClasses = documentationTarget.nestedClasses.toList()
94+
val methods = documentationTarget.methods.toList()
95+
getSelectedClassesAndFunctions(nestedClasses, methods, selectionModel)
96+
} else {
97+
listOf(documentationTarget)
98+
}
99+
}
100+
101+
else -> emptyList()
102+
}
103+
}
104+
105+
private fun getSelectedClassesAndFunctions(
106+
pyClasses: List<PyClass>,
107+
pyFunctions: List<PyFunction>,
108+
selectionModel: SelectionModel
109+
): List<PsiNameIdentifierOwner> {
110+
val filteredClasses = pyClasses.filter { intersectsElement(selectionModel, it as PsiElement) }
111+
val filteredFunctions = pyFunctions.filter { intersectsElement(selectionModel, it as PsiElement) }
112+
113+
return filteredClasses + filteredFunctions
114+
}
115+
116+
private fun intersectsElement(selectionModel: SelectionModel, element: PsiElement): Boolean {
117+
return selectionModel.selectionStart < element.textRange.endOffset && selectionModel.selectionEnd > element.textRange.startOffset
38118
}
39119

120+
fun containsElement(selectionModel: SelectionModel, element: PsiElement): Boolean {
121+
return selectionModel.selectionStart <= element.textRange.startOffset && element.textRange.endOffset <= selectionModel.selectionEnd
122+
}
123+
124+
}
125+
126+
//private fun PyDocstringGenerator.buildAndInsert(newDoc: String) {
127+
// TODO("Not yet implemented")
128+
//}
129+
130+
fun PyDocstringGenerator.buildAndInsert(replacementText: String, myDocStringOwner: PyDocStringOwner): PyDocStringOwner {
131+
val project: Project = myDocStringOwner.getProject()
132+
val elementGenerator = PyElementGenerator.getInstance(project)
133+
val replacement = elementGenerator.createDocstring(replacementText)
134+
135+
val docStringExpression: PyStringLiteralExpression? = myDocStringOwner.getDocStringExpression()
136+
if (docStringExpression != null) {
137+
docStringExpression.replace(replacement.expression)
138+
} else {
139+
val container = PyUtil.`as`(
140+
myDocStringOwner,
141+
PyStatementListContainer::class.java
142+
) ?: throw IncorrectOperationException("Cannot find container for docstring, Should be a function or class")
143+
144+
val statements = container.statementList
145+
val indentation = PyIndentUtil.getElementIndent(statements)
146+
147+
PyUtil.updateDocumentUnblockedAndCommitted(myDocStringOwner) { document: Document ->
148+
val beforeStatements = statements.prevSibling
149+
var replacementWithLineBreaks = """
150+
151+
$indentation$replacementText
152+
""".trimIndent()
153+
if (statements.statements.isNotEmpty()) {
154+
replacementWithLineBreaks += """
155+
156+
$indentation
157+
""".trimIndent()
158+
}
159+
val range = beforeStatements.textRange
160+
if (beforeStatements !is PsiWhiteSpace) {
161+
document.insertString(range.endOffset, replacementWithLineBreaks)
162+
} else if (statements.statements.isEmpty() && beforeStatements.textContains('\n')) {
163+
document.insertString(range.startOffset, replacementWithLineBreaks)
164+
} else {
165+
document.replaceString(range.startOffset, range.endOffset, replacementWithLineBreaks)
166+
}
167+
}
168+
}
169+
return myDocStringOwner
40170
}

0 commit comments

Comments
 (0)