[command-completion] IDEA-373902 Command completion. New actions: Quick Documentation

GitOrigin-RevId: 1b11c9c09af1413480f2f3237387080b14ad7b72
This commit is contained in:
Mikhail Pyltsin
2025-07-31 13:02:39 +02:00
committed by intellij-monorepo-bot
parent b27f71e6f8
commit 5b2b71e74e
10 changed files with 236 additions and 7 deletions

View File

@@ -1496,6 +1496,7 @@
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaCopyCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaTypeHierarchyCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaTypeInfoCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaQuickDocumentationCompletionCommand" order="last"/>
<weigher implementationClass="com.intellij.codeInsight.completion.LoggerWeigher" key="completion" id="logger"/>

View File

@@ -0,0 +1,22 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.completion.commands.impl
import com.intellij.codeInsight.completion.command.commands.AbstractQuickDocumentationCompletionCommand
import com.intellij.codeInsight.completion.command.getCommandContext
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
public class JavaQuickDocumentationCompletionCommand : AbstractQuickDocumentationCompletionCommand() {
override fun findElement(offset: Int, psiFile: PsiFile): PsiElement? {
var context = getCommandContext(offset, psiFile) ?: return null
if (context is PsiWhiteSpace) context = context.prevSibling
if (context !is PsiIdentifier) return null
if (context.parent is PsiMember) return context
if (((context.parent?.parent as? PsiTypeElement)?.type as? PsiClassType)?.resolve() is PsiMember) return context
if (context.parent?.parent is PsiReferenceList && (context.parent as? PsiJavaCodeReferenceElement)?.resolve() is PsiMember) return context
val value = PsiTreeUtil.getParentOfType(context, PsiJavaCodeReferenceElement::class.java, false) ?: return null
val resolved = value.resolve()
if (resolved is PsiMember || resolved is PsiPackage) return value
return null
}
}

View File

@@ -9,12 +9,11 @@ public class JavaTypeHierarchyCompletionCommandProvider : AbstractTypeHierarchyC
override fun findElement(offset: Int, psiFile: PsiFile): PsiElement? {
var context = getCommandContext(offset, psiFile)
if (context is PsiWhiteSpace) context = context.prevSibling
if (context is PsiIdentifier && context.parent is PsiClass) return context
if (context is PsiIdentifier &&
((context.parent?.parent as? PsiTypeElement)?.type as? PsiClassType)?.resolve() is PsiClass) return context
if (context is PsiIdentifier &&
context.parent?.parent is PsiReferenceList &&
(context.parent as? PsiJavaCodeReferenceElement)?.resolve() is PsiClass) return context
if (context !is PsiIdentifier) return null
if (context.parent is PsiClass) return context
if (((context.parent?.parent as? PsiTypeElement)?.type as? PsiClassType)?.resolve() is PsiClass) return context
if (context.parent?.parent is PsiReferenceList && (context.parent as? PsiJavaCodeReferenceElement)?.resolve() is PsiClass) return context
if (context.parent is PsiNameValuePair) return context
return null
}
}

View File

@@ -0,0 +1,56 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.codeInsight.completion.commands
import com.intellij.codeInsight.completion.LightFixtureCompletionTestCase
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.util.registry.Registry
import com.intellij.testFramework.NeedsIndex
@NeedsIndex.SmartMode(reason = "it requires highlighting")
class JavaCommandsCompletionQuickDocumentationTest : LightFixtureCompletionTestCase() {
override fun setUp() {
super.setUp()
Registry.get("ide.completion.command.enabled").setValue(false, getTestRootDisposable())
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
}
fun testMember() {
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo.<caret>() {
int y = 10;
System.out.println(y);
}
}
""".trimIndent())
val elements = myFixture.completeBasic()
assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Quick Documentation", ignoreCase = true) })
}
fun testReference() {
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo() {
int y = 10;
System.out.println.<caret>(y);
}
}
""".trimIndent())
val elements = myFixture.completeBasic()
assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Quick Documentation", ignoreCase = true) })
}
fun testNoLocalVariable() {
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo() {
int y = 10;
System.out.println(y.<caret>);
}
}
""".trimIndent())
val elements = myFixture.completeBasic()
assertNull(elements.firstOrNull { element -> element.lookupString.contains("Quick Documentation", ignoreCase = true) })
}
}

View File

@@ -254,6 +254,11 @@ c:com.intellij.codeInsight.actions.VcsFacade
- a:getTextRangeImportList(com.intellij.psi.PsiFile,I):com.intellij.openapi.util.TextRange
- p:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
- a:isImportList(com.intellij.psi.PsiFile,I):Z
*a:com.intellij.codeInsight.completion.command.commands.AbstractQuickDocumentationCompletionCommand
- com.intellij.codeInsight.completion.command.commands.ActionCommandProvider
- <init>():V
- p:createCommand(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext):com.intellij.codeInsight.completion.command.commands.ActionCompletionCommand
- a:findElement(I,com.intellij.psi.PsiFile):com.intellij.psi.PsiElement
*a:com.intellij.codeInsight.completion.command.commands.AbstractRenameActionCommandProvider
- com.intellij.codeInsight.completion.command.commands.ActionCommandProvider
- <init>():V

View File

@@ -0,0 +1,31 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInsight.completion.command.commands
import com.intellij.codeInsight.completion.command.CommandCompletionProviderContext
import com.intellij.codeInsight.completion.command.HighlightInfoLookup
import com.intellij.idea.ActionsBundle
import com.intellij.openapi.editor.colors.EditorColors
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
abstract class AbstractQuickDocumentationCompletionCommand :
ActionCommandProvider(actionId = "QuickJavaDoc",
presentableName = ActionsBundle.message("action.QuickJavaDoc.text"),
icon = null,
priority = -100,
previewText = ActionsBundle.message("action.QuickJavaDoc.description")) {
abstract fun findElement(offset: Int, psiFile: PsiFile): PsiElement?
override fun createCommand(context: CommandCompletionProviderContext): ActionCompletionCommand? {
val element = findElement(context.offset, context.psiFile) ?: return null
if (element.textRange == null) return null
return ActionCompletionCommand(actionId = super.actionId,
presentableActionName = super.presentableName,
icon = super.icon,
priority = super.priority,
previewText = super.previewText,
synonyms = super.synonyms,
highlightInfo = HighlightInfoLookup(element.textRange, EditorColors.SEARCH_RESULT_ATTRIBUTES, 0))
}
}

View File

@@ -0,0 +1,49 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands
import com.intellij.codeInsight.completion.command.commands.AbstractQuickDocumentationCompletionCommand
import com.intellij.codeInsight.completion.command.getCommandContext
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiWhiteSpace
import com.intellij.psi.util.parentOfType
import org.jetbrains.kotlin.analysis.api.analyze
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.CommentSaver.Companion.tokenType
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.KtUserType
class KotlinQuickDocumentationCompletionCommandProvider : AbstractQuickDocumentationCompletionCommand() {
override fun findElement(offset: Int, psiFile: PsiFile): PsiElement? {
var context = getCommandContext(offset, psiFile)
if (context is PsiWhiteSpace) context = context.prevSibling
if (context?.tokenType != KtTokens.IDENTIFIER) return null
if (isDeclaration(context.parent)) return context
if (context.parent is KtNameReferenceExpression &&
context.parent?.parent is KtUserType
) {
val type = (context.parent?.parent as? KtUserType) ?: return null
val resolved = type.referenceExpression?.mainReference?.resolve()
analyze(type) {
if (isDeclaration(resolved) || resolved is PsiMember) return context
}
}
val referenceExpression = context.parentOfType<KtNameReferenceExpression>()
if (referenceExpression != null && analyze(referenceExpression) {
val resolved = referenceExpression.mainReference.resolve()
isDeclaration(resolved) || resolved is PsiMember
}) {
return referenceExpression
}
return null
}
private fun isDeclaration(element: PsiElement?): Boolean {
return element is KtNamedDeclaration && !(element is KtProperty && element.isLocal)
}
}

View File

@@ -3,6 +3,7 @@ package org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands
import com.intellij.codeInsight.completion.command.commands.AbstractTypeHierarchyCompletionCommandProvider
import com.intellij.codeInsight.completion.command.getCommandContext
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiWhiteSpace
@@ -25,7 +26,8 @@ class KotlinTypeHierarchyCompletionCommandProvider : AbstractTypeHierarchyComple
) {
val type = (context.parent?.parent as? KtUserType) ?: return null
analyze(type) {
if (type.referenceExpression?.mainReference?.resolve() is KtClass) return context
val resolved = type.referenceExpression?.mainReference?.resolve()
if (resolved is KtClass || resolved is PsiClass) return context
}
}
return null

View File

@@ -0,0 +1,63 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.kotlin.idea.fir.completion.commands
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
class K2CommandCompletionQuickDocumentationTest : KotlinLightCodeInsightFixtureTestCase() {
override val pluginMode = KotlinPluginMode.K2
override fun setUp() {
super.setUp()
Registry.get("ide.completion.command.enabled").setValue(false, getTestRootDisposable())
Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable())
}
fun testClass() {
myFixture.configureByText(
"x.kt", """
class A.<caret> {
fun foo(a: String) {
var b = a
}
}
""".trimIndent()
)
val elements = myFixture.completeBasic()
assertNotNull(elements.firstOrNull() { element -> element.lookupString.contains("Quick Documentation", ignoreCase = true) })
}
fun testReference() {
myFixture.configureByText(
"x.kt", """
class A {
fun foo(a: String) {
print.<caret>(a)
}
fun print(a: String) {
var b = a
}
}
""".trimIndent()
)
val elements = myFixture.completeBasic()
assertNotNull(elements.firstOrNull() { element -> element.lookupString.contains("Quick Documentation", ignoreCase = true) })
}
fun testNoLocalVariable() {
myFixture.configureByText(
"x.kt", """
class A {
fun foo() {
val a.<caret> = "1"
print(a)
}
}
""".trimIndent()
)
val elements = myFixture.completeBasic()
assertNull(elements.firstOrNull() { element -> element.lookupString.contains("Quick Documentation", ignoreCase = true) })
}
}

View File

@@ -43,6 +43,7 @@
<codeInsight.completion.command.provider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands.KotlinCopyCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands.KotlinTypeHierarchyCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands.KotlinTypeInfoCompletionCommandProvider" order="last"/>
<codeInsight.completion.command.provider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands.KotlinQuickDocumentationCompletionCommandProvider" order="last"/>
</extensions>
</idea-plugin>