From af0f19abb96b605ce0949aaaf926fb2e949096b4 Mon Sep 17 00:00:00 2001 From: "Aleksandr.Govenko" Date: Thu, 24 Jul 2025 18:11:54 +0200 Subject: [PATCH] PY-86123 Add navigation to attribute for keyword pattern inlay hints GitOrigin-RevId: aeabf8a75d4725be7a17a20be34a22ad45a31c15 --- .../intellij.python.community.impl.xml | 3 + .../references/PyKeywordPatternReference.java | 7 ++- .../inlayHints/PyTypeInlayHintsProvider.kt | 58 ++++++++++++++++++- 3 files changed, 63 insertions(+), 5 deletions(-) diff --git a/python/pluginResources/intellij.python.community.impl.xml b/python/pluginResources/intellij.python.community.impl.xml index 42b63ccd893f..dda298f9c4d9 100644 --- a/python/pluginResources/intellij.python.community.impl.xml +++ b/python/pluginResources/intellij.python.community.impl.xml @@ -70,6 +70,9 @@ optionId="python.type.inlays.function.return"/> + + StreamEx.of(ContainerUtil.notNullize(t.resolveMember(keywordPattern.getKeyword(), - null, AccessDirection.READ, resolveContext)))) + .flatMap(t -> StreamEx.of(ContainerUtil.notNullize(t.resolveMember(keyword, null, AccessDirection.READ, resolveContext)))) .toArray(ResolveResult.EMPTY_ARRAY); } diff --git a/python/src/com/jetbrains/python/inlayHints/PyTypeInlayHintsProvider.kt b/python/src/com/jetbrains/python/inlayHints/PyTypeInlayHintsProvider.kt index 4d132d711614..5ef9cc7a9a28 100644 --- a/python/src/com/jetbrains/python/inlayHints/PyTypeInlayHintsProvider.kt +++ b/python/src/com/jetbrains/python/inlayHints/PyTypeInlayHintsProvider.kt @@ -1,11 +1,20 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.python.inlayHints +import com.intellij.codeInsight.CodeInsightBundle +import com.intellij.codeInsight.hint.HintManager import com.intellij.codeInsight.hints.declarative.* import com.intellij.codeInsight.hints.declarative.impl.PresentationTreeBuilderImpl.Companion.MAX_SEGMENT_TEXT_LENGTH +import com.intellij.openapi.application.EDT +import com.intellij.openapi.application.readAction import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.event.EditorMouseEvent import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile +import com.intellij.psi.SmartPointerManager +import com.intellij.psi.SmartPsiElementPointer +import com.intellij.util.PsiNavigateUtil +import com.intellij.util.concurrency.annotations.RequiresEdt import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.REVEAL_TYPE import com.jetbrains.python.codeInsight.typing.PyTypingTypeProvider.REVEAL_TYPE_EXT import com.jetbrains.python.documentation.PythonDocumentationProvider @@ -14,9 +23,14 @@ import com.jetbrains.python.psi.PyClassPattern import com.jetbrains.python.psi.PyFunction import com.jetbrains.python.psi.PyKeywordPattern import com.jetbrains.python.psi.impl.PyClassPatternImpl +import com.jetbrains.python.psi.impl.references.PyKeywordPatternReference import com.jetbrains.python.psi.resolve.PyResolveContext import com.jetbrains.python.psi.types.PyClassType import com.jetbrains.python.psi.types.TypeEvalContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext class PyTypeInlayHintsProvider : InlayHintsProvider { companion object { @@ -28,7 +42,7 @@ class PyTypeInlayHintsProvider : InlayHintsProvider { override fun createCollector(file: PsiFile, editor: Editor): InlayHintsCollector? = Collector() private class Collector : SharedBypassCollector { - val hintFormat = HintFormat.Companion.default + val hintFormat = HintFormat.default .withFontSize(HintFontSize.ABitSmallerThanInEditor) .withHorizontalMargin(HintMarginPadding.MarginAndSmallerPadding) @@ -96,13 +110,51 @@ class PyTypeInlayHintsProvider : InlayHintsProvider { val classType = typeEvalContext.getType(classPattern) as? PyClassType ?: return val matchArgs = PyClassPatternImpl.getMatchArgs(classType, typeEvalContext) ?: return classPattern.argumentList.patterns.mapIndexed { idx, pattern -> - if (pattern is PyKeywordPattern) return + if (pattern is PyKeywordPattern) return // positional patterns can only be before keyword patterns val name = matchArgs.getOrNull(idx) ?: return@mapIndexed sink.addPresentation(position = InlineInlayPosition(pattern.textRange.startOffset, false), hintFormat = hintFormat) { - text(name) + val navigationData = InlayActionData( + PyPseudoKeywordPatternInlayActionHandler.Payload(SmartPointerManager.createPointer(classPattern), name), + PyPseudoKeywordPatternInlayActionHandler.HANDLER_NAME + ) + text(name, actionData = navigationData) text("=") } } } } +} + + + +class PyPseudoKeywordPatternInlayActionHandler(private val cs: CoroutineScope) : InlayActionHandler { + companion object { + const val HANDLER_NAME: String = "py.pseudo.keyword.pattern" + } + + data class Payload(val classPattern: SmartPsiElementPointer, val keyword: String) : InlayActionPayload + + @RequiresEdt + override fun handleClick(e: EditorMouseEvent, payload: InlayActionPayload) { + payload as Payload + + cs.launch { + val resolveResults = readAction { + val classPattern = payload.classPattern.element ?: return@readAction null + val keyword = payload.keyword + val typeEvalContext = TypeEvalContext.userInitiated(classPattern.project, classPattern.containingFile) + PyKeywordPatternReference.resolveKeyword(classPattern, keyword, PyResolveContext.defaultContext(typeEvalContext)) + } + val element = resolveResults?.firstOrNull()?.element ?: run { + withContext(Dispatchers.EDT) { + HintManager.getInstance().showInformationHint(e.editor, CodeInsightBundle.message("declaration.navigation.nowhere.to.go")) + } + return@launch + } + + withContext(Dispatchers.EDT) { + PsiNavigateUtil.navigate(element) + } + } + } } \ No newline at end of file