11package space.whitememory.pythoninlayparams
22
3- import com.intellij.codeInsight.hints.FactoryInlayHintsCollector
4- import com.intellij.codeInsight.hints.InlayHintsSink
53import com.intellij.codeInsight.hints.InlayInfo
6- import com.intellij.openapi.editor.Editor
4+ import com.intellij.codeInsight.hints.InlayParameterHintsProvider
5+ import com.intellij.codeInsight.hints.Option
76import com.intellij.psi.PsiElement
87import com.intellij.psi.util.PsiTreeUtil
98import com.jetbrains.python.psi.*
109import com.jetbrains.python.psi.types.TypeEvalContext
1110
11+
1212@Suppress(" UnstableApiUsage" )
13- class PythonInlayHintsCollector (
14- editor : Editor , private val settings : PythonInlayHintsProvider .Settings
15- ) : FactoryInlayHintsCollector(editor) {
13+ class PythonInlayParameterHintsProvider : InlayParameterHintsProvider {
14+
15+ companion object {
16+ val classHints = Option (" hints.classes.parameters" , { " Class hints" }, true )
17+ val functionHints = Option (" hints.functions.parameters" , { " Function hints" }, true )
18+ val lambdaHints = Option (" hints.lambdas.parameters" , { " Lambda hints" }, true )
19+ }
1620
1721 private val forbiddenBuiltinFiles = setOf (" builtins.pyi" , " typing.pyi" )
1822
19- override fun collect (element : PsiElement , editor : Editor , sink : InlayHintsSink ): Boolean {
23+ override fun getDefaultBlackList () = setOf<String >()
24+
25+ override fun isBlackListSupported () = false
26+
27+ override fun getDescription () = " Help you pass correct arguments by showing parameter names at call sites"
28+
29+ override fun getSupportedOptions () = listOf (classHints, functionHints, lambdaHints)
30+
31+ override fun getProperty (key : String? ): String? {
32+ val prefix = " inlay.parameters"
33+ return when (key) {
34+ " $prefix .hints.classes.parameters" -> " Show parameter names for class constructors and dataclasses."
35+ " $prefix .hints.functions.parameters" -> " Show parameter names for function and method calls."
36+ " $prefix .hints.lambdas.parameters" -> " Show parameter names for lambda calls."
37+ else -> null
38+ }
39+ }
40+
41+ override fun getParameterHints (element : PsiElement ): MutableList <InlayInfo > {
42+ val inlayInfos = mutableListOf<InlayInfo >()
43+
2044 // This method gets every element in the editor,
2145 // so we have to verify it's a Python call expression
2246 if (element !is PyCallExpression || element is PyDecorator ) {
23- return true
47+ return inlayInfos
2448 }
2549
2650 // Don't show hints if there's no arguments
2751 // Or the only argument is unpacking (*list, **dict)
2852 if (element.arguments.isEmpty() || (element.arguments.size == 1 && element.arguments[0 ] is PyStarArgument )) {
29- return true
53+ return inlayInfos
3054 }
3155
3256 // Try to resolve the object that made this call
33- var resolved = element.callee?.reference?.resolve() ? : return true
57+ var resolved = element.callee?.reference?.resolve() ? : return inlayInfos
3458 if (isForbiddenBuiltinElement(resolved)) {
35- return true
59+ return inlayInfos
3660 }
3761
3862 var classAttributes = listOf<PyTargetExpression >()
39- if (resolved is PyTargetExpression && settings. lambdaHints) {
63+ if (resolved is PyTargetExpression && lambdaHints.isEnabled() ) {
4064 // TODO: Handle cases other than lambda expressions
4165 // Use the target to find the lambda expression object, and assign it to get its parameters up ahead
42- resolved = PsiTreeUtil .getNextSiblingOfType(resolved, PyLambdaExpression ::class .java) ? : return true
43- } else if (resolved is PyClass && settings. classHints) {
66+ resolved = PsiTreeUtil .getNextSiblingOfType(resolved, PyLambdaExpression ::class .java) ? : return inlayInfos
67+ } else if (resolved is PyClass && classHints.isEnabled() ) {
4468 // This call is made by a class (initialization), so we want to find the parameters it takes.
4569 // In order to do so, we first have to check for an init method, and if not found,
4670 // We will use the class attributes instead. (Handle dataclasses, attrs, etc.)
@@ -55,8 +79,8 @@ class PythonInlayHintsCollector(
5579 classAttributes = resolved.classAttributes
5680 entryMethod ? : resolved
5781 }
58- } else if (! settings. functionHints) {
59- return true
82+ } else if (! functionHints.isEnabled() ) {
83+ return inlayInfos
6084 }
6185
6286 val resolvedParameters = getElementFilteredParameters(resolved)
@@ -66,7 +90,7 @@ class PythonInlayHintsCollector(
6690 // in case this is a class
6791 classAttributes
6892 } else if (resolvedParameters.isEmpty()) {
69- return true
93+ return inlayInfos
7094 } else {
7195 resolvedParameters
7296 }
@@ -75,27 +99,11 @@ class PythonInlayHintsCollector(
7599 // Don't need a hint if there's only one parameter,
76100 // Make an exception for *args
77101 finalParameters[0 ].let {
78- if (it !is PyNamedParameter || ! it.isPositionalContainer) return true
102+ if (it !is PyNamedParameter || ! it.isPositionalContainer) return inlayInfos
79103 }
80104 }
81105
82- getInlayInfos(finalParameters, element.arguments).forEach {
83- val hintText = factory.smallText(" ${it.text} :" )
84- val presentation = factory.roundWithBackground(hintText)
85-
86- sink.addInlineElement(it.offset, false , presentation, false )
87- }
88-
89- return true
90- }
91-
92- /* *
93- * Gets the list of [InlayInfo] that represents parameters worth showing along with their offset.
94- */
95- private fun getInlayInfos (parameters : List <PyElement >, arguments : Array <PyExpression >): MutableList <InlayInfo > {
96- val inlayInfos = mutableListOf<InlayInfo >()
97-
98- parameters.zip(arguments).forEach { (param, arg) ->
106+ finalParameters.zip(element.arguments).forEach { (param, arg) ->
99107 val paramName = param.name ? : return @forEach
100108 if (arg is PyStarArgument || arg is PyKeywordArgument ) {
101109 // It's a keyword argument or unpacking,
0 commit comments