mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[command-completion] IDEA-373902 Command completion. New actions: Generate constructors
GitOrigin-RevId: 02d2af96982d53a0734c9c1f57a452758a184b85
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0fc0b74dd2
commit
a119bc85cc
@@ -1497,6 +1497,7 @@
|
||||
<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"/>
|
||||
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaGenerateConstructorCompletionCommandProvider" order="last"/>
|
||||
|
||||
<weigher implementationClass="com.intellij.codeInsight.completion.LoggerWeigher" key="completion" id="logger"/>
|
||||
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
// 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.CodeInsightBundle
|
||||
import com.intellij.codeInsight.completion.command.CommandCompletionProviderContext
|
||||
import com.intellij.codeInsight.completion.command.CommandProvider
|
||||
import com.intellij.codeInsight.completion.command.CompletionCommand
|
||||
import com.intellij.codeInsight.completion.command.getCommandContext
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.AddDefaultConstructorFix
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.AddMethodFix
|
||||
import com.intellij.codeInsight.generation.ClassMember
|
||||
import com.intellij.codeInsight.generation.GenerateConstructorHandler
|
||||
import com.intellij.codeInsight.generation.GenerationInfo
|
||||
import com.intellij.codeInsight.generation.PsiMethodMember
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
|
||||
import com.intellij.java.JavaBundle
|
||||
import com.intellij.modcommand.ActionContext
|
||||
import com.intellij.modcommand.ModCommandExecutor
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.util.TypeConversionUtil
|
||||
import com.intellij.psi.util.parentOfType
|
||||
import org.jetbrains.annotations.Nls
|
||||
|
||||
public class JavaGenerateConstructorCompletionCommandProvider : CommandProvider {
|
||||
private fun findContext(context: CommandCompletionProviderContext): PsiClass? {
|
||||
val element = getCommandContext(context.offset, context.psiFile) ?: return null
|
||||
val containingClass = element.parentOfType<PsiClass>() ?: return null
|
||||
if (containingClass.isInterface || containingClass.isRecord || containingClass is PsiImplicitClass) return null
|
||||
if (element is PsiIdentifier && element.parent is PsiClass) return element.parent as PsiClass
|
||||
if (!(element is PsiWhiteSpace && element.text.contains("\n"))) return null
|
||||
return element.parentOfType<PsiClass>()
|
||||
}
|
||||
|
||||
override fun getCommands(context: CommandCompletionProviderContext): List<CompletionCommand> {
|
||||
val clazz = findContext(context) ?: return emptyList()
|
||||
val result = mutableListOf<CompletionCommand>()
|
||||
if (clazz.constructors.none { it.parameters.isEmpty() }) {
|
||||
result.add(object : CompletionCommand() {
|
||||
private val fix = AddDefaultConstructorFix(clazz, PsiModifier.PUBLIC)
|
||||
|
||||
override val presentableName: @Nls String
|
||||
get() = CodeInsightBundle.message("command.completion.generate.text", JavaBundle.message("command.completion.generate.no.args.constructor.text"))
|
||||
|
||||
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
|
||||
val actionContext = ActionContext.from(editor, psiFile)
|
||||
ModCommandExecutor.executeInteractively(actionContext, presentableName, editor) {
|
||||
fix.perform(actionContext)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPreview(): IntentionPreviewInfo {
|
||||
val actionContext = ActionContext.from(context.editor, context.psiFile)
|
||||
return fix.generatePreview(actionContext)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
val handler = object : GenerateConstructorHandler() {
|
||||
public override fun getAllOriginalMembers(aClass: PsiClass?): Array<out ClassMember?>? {
|
||||
return super.getAllOriginalMembers(aClass)
|
||||
}
|
||||
|
||||
public override fun generateMemberPrototypes(aClass: PsiClass?, members: Array<out ClassMember?>?): List<GenerationInfo?> {
|
||||
return super.generateMemberPrototypes(aClass, members)
|
||||
}
|
||||
}
|
||||
var members = handler.getAllOriginalMembers(clazz) ?: return result
|
||||
if (members.isEmpty()) return result
|
||||
|
||||
//let's simplify condition, because it can be quite tricky to find out whether there is a constructor with the same arguments or
|
||||
//which super constructor should be called
|
||||
val superClass = clazz.superClass
|
||||
if ((clazz.constructors.none { it.parameters.size == members.size } &&
|
||||
(superClass == null ||
|
||||
superClass.constructors.isEmpty() ||
|
||||
superClass.constructors.any { it.parameters.isEmpty() })) ||
|
||||
canUseSuperConstructor(superClass, clazz, members)) {
|
||||
val superConstructors = superClass?.constructors
|
||||
if (superConstructors?.size == 1) {
|
||||
val substitutor = TypeConversionUtil.getSuperClassSubstitutor(superClass, clazz, PsiSubstitutor.EMPTY)
|
||||
val superConstructor = PsiMethodMember(superConstructors[0], substitutor)
|
||||
members = arrayOf(superConstructor, *members)
|
||||
}
|
||||
val prototypes = handler.generateMemberPrototypes(clazz, members)
|
||||
if (prototypes.size == 1) {
|
||||
val generationInfo = prototypes[0]
|
||||
val text = (generationInfo?.psiMember as? PsiMethod)?.text ?: return result
|
||||
result.add(object : CompletionCommand() {
|
||||
private val fix = AddMethodFix(text, clazz)
|
||||
|
||||
override val presentableName: @Nls String
|
||||
get() = CodeInsightBundle.message("command.completion.generate.text", JavaBundle.message("command.completion.generate.all.args.constructor.text"))
|
||||
|
||||
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
|
||||
val actionContext = ActionContext.from(editor, psiFile)
|
||||
ModCommandExecutor.executeInteractively(actionContext, presentableName, editor) {
|
||||
fix.perform(actionContext)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getPreview(): IntentionPreviewInfo {
|
||||
val actionContext = ActionContext.from(context.editor, context.psiFile)
|
||||
return fix.generatePreview(actionContext)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun canUseSuperConstructor(
|
||||
superClass: PsiClass?,
|
||||
clazz: PsiClass,
|
||||
members: Array<out ClassMember?>,
|
||||
): Boolean {
|
||||
if (superClass == null) return false
|
||||
if (superClass.constructors.size != 1) return false
|
||||
val method = superClass.constructors[0]
|
||||
val targetSize = members.size + method.parameters.size
|
||||
return clazz.constructors.none {
|
||||
it.parameters.size == targetSize
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
// 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.application.impl.NonBlockingReadActionImpl
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.testFramework.NeedsIndex
|
||||
|
||||
@NeedsIndex.SmartMode(reason = "it requires highlighting")
|
||||
class JavaCommandsCompletionGenerateConstructorsTest : 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 testGenerateNoArgsConstructor() {
|
||||
myFixture.configureByText(JavaFileType.INSTANCE, """
|
||||
class A {
|
||||
int a;
|
||||
g<caret>
|
||||
}
|
||||
""".trimIndent())
|
||||
val elements = myFixture.completeBasic()
|
||||
selectItem(elements.first { element -> element.lookupString.contains("Generate 'No-Args Constructor'", ignoreCase = true) })
|
||||
NonBlockingReadActionImpl.waitForAsyncTaskCompletion()
|
||||
myFixture.checkResult("""
|
||||
class A {
|
||||
int a;
|
||||
|
||||
public A() {
|
||||
}
|
||||
}""".trimIndent())
|
||||
}
|
||||
|
||||
fun testGenerateAllArgsConstructor() {
|
||||
myFixture.configureByText(JavaFileType.INSTANCE, """
|
||||
class A {
|
||||
int a;
|
||||
g<caret>
|
||||
}
|
||||
""".trimIndent())
|
||||
val elements = myFixture.completeBasic()
|
||||
selectItem(elements.first { element -> element.lookupString.contains("Generate 'All-Args Constructor'", ignoreCase = true) })
|
||||
NonBlockingReadActionImpl.waitForAsyncTaskCompletion()
|
||||
myFixture.checkResult("""
|
||||
class A {
|
||||
int a;
|
||||
|
||||
public A(int a) {
|
||||
this.a = a;
|
||||
}
|
||||
}""".trimIndent())
|
||||
}
|
||||
|
||||
fun testGenerateAllArgsConstructorWithSuper() {
|
||||
myFixture.configureByText(JavaFileType.INSTANCE, """
|
||||
class A extends B<String>{
|
||||
int a;
|
||||
g<caret>
|
||||
}
|
||||
|
||||
class B<T> {
|
||||
public B(T a){}
|
||||
}
|
||||
""".trimIndent())
|
||||
val elements = myFixture.completeBasic()
|
||||
selectItem(elements.first { element -> element.lookupString.contains("Generate 'All-Args Constructor'", ignoreCase = true) })
|
||||
NonBlockingReadActionImpl.waitForAsyncTaskCompletion()
|
||||
myFixture.checkResult("""
|
||||
class A extends B<String>{
|
||||
int a;
|
||||
|
||||
public A(String a, int a1) {
|
||||
super(a);
|
||||
this.a = a1;
|
||||
}
|
||||
}
|
||||
|
||||
class B<T> {
|
||||
public B(T a){}
|
||||
}""".trimIndent())
|
||||
}
|
||||
|
||||
fun testNoGenerateNoArgsConstructor() {
|
||||
myFixture.configureByText(JavaFileType.INSTANCE, """
|
||||
class A {
|
||||
public A(){}
|
||||
int a;
|
||||
g<caret>
|
||||
}
|
||||
""".trimIndent())
|
||||
val elements = myFixture.completeBasic()
|
||||
assertNull(elements.firstOrNull { element -> element.lookupString.contains("Generate 'No-Args Constructor'", ignoreCase = true) })
|
||||
}
|
||||
|
||||
fun testNoGenerateAllArgsConstructor() {
|
||||
myFixture.configureByText(JavaFileType.INSTANCE, """
|
||||
class A extends B{
|
||||
int a;
|
||||
g<caret>
|
||||
}
|
||||
|
||||
class B {
|
||||
public B(int a){}
|
||||
public B(String a){}
|
||||
}
|
||||
""".trimIndent())
|
||||
val elements = myFixture.completeBasic()
|
||||
assertNull(elements.firstOrNull { element -> element.lookupString.contains("Generate 'All-Args Constructor'", ignoreCase = true) })
|
||||
}
|
||||
}
|
||||
@@ -1954,6 +1954,8 @@ command.completion.getters.and.setters.text=Getters and Setters
|
||||
command.completion.generate.getter.setter=Generate 'Getter' and 'Setter'
|
||||
command.completion.generate.getter=Generate 'Getter'
|
||||
command.completion.generate.setter=Generate 'Setter'
|
||||
command.completion.generate.no.args.constructor.text=No-Args Constructor
|
||||
command.completion.generate.all.args.constructor.text=All-Args Constructor
|
||||
|
||||
advanced.setting.java.show.irrelevant.templates.in.source.roots=Show irrelevant New File templates in Java source roots
|
||||
java.test.use.wall.time=Use Wall Time
|
||||
|
||||
Reference in New Issue
Block a user