@@ -2,13 +2,21 @@ package cc.unitmesh.pycharm.provider
2
2
3
3
import cc.unitmesh.devti.custom.document.LivingDocumentationType
4
4
import cc.unitmesh.devti.provider.LivingDocumentation
5
+ import com.intellij.codeInsight.daemon.impl.CollectHighlightsUtil
6
+ import com.intellij.openapi.editor.Document
5
7
import com.intellij.openapi.editor.Editor
6
8
import com.intellij.openapi.editor.SelectionModel
9
+ import com.intellij.openapi.project.Project
7
10
import com.intellij.psi.PsiElement
8
11
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
9
16
import com.intellij.util.IncorrectOperationException
10
17
import com.jetbrains.python.documentation.docstrings.PyDocstringGenerator
11
- import com.jetbrains.python.psi.PyDocStringOwner
18
+ import com.jetbrains.python.psi.*
19
+
12
20
13
21
class PythonLivingDocumentation : LivingDocumentation {
14
22
override val forbiddenRules: List <String > = listOf ()
@@ -23,18 +31,140 @@ class PythonLivingDocumentation : LivingDocumentation {
23
31
}
24
32
25
33
val docstringGenerator = PyDocstringGenerator .forDocStringOwner((target as PyDocStringOwner ? )!! )
26
- // docstringGenerator.buildAndInsert(newDoc)
34
+ docstringGenerator.buildAndInsert(newDoc, target )
27
35
}
28
36
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
+ }
31
67
}
32
68
69
+
33
70
override fun findDocTargetsInSelection (
34
71
root : PsiElement ,
35
72
selectionModel : SelectionModel
36
73
): 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
38
118
}
39
119
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
40
170
}
0 commit comments