From f3dae0e5edd06a10505d199ec330eb9c94dd5487 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Mon, 28 Jul 2025 15:07:29 +0200 Subject: [PATCH] [command-completion] IDEA-375959 Command completion: optimize imports is available only after imports section, but not on the line with imports GitOrigin-RevId: eee1a3077747d0e7e75f98efed5c855e3f28dc52 --- .../resources/META-INF/JavaPlugin.xml | 2 +- ...OptimizeImportCompletionCommandProvider.kt | 22 +++++++++ .../commands/JavaCommandsCompletionTest.kt | 14 ++++++ platform/lang-impl/api-dump-experimental.txt | 7 +++ ...OptimizeImportCompletionCommandProvider.kt | 45 +++++++++++++++++++ .../OptimizeImportCompletionCommand.kt | 22 --------- ...OptimizeImportCompletionCommandProvider.kt | 22 +++++++++ .../fir/completion/K2CommandCompletionTest.kt | 14 ++++++ .../META-INF/command-completion-k2.xml | 2 +- 9 files changed, 126 insertions(+), 24 deletions(-) create mode 100644 java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaOptimizeImportCompletionCommandProvider.kt create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractOptimizeImportCompletionCommandProvider.kt delete mode 100644 platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/OptimizeImportCompletionCommand.kt create mode 100644 plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinOptimizeImportCompletionCommandProvider.kt diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index 513d2c9e270a..93c8886688f1 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -1472,7 +1472,7 @@ - + (withSelf = true) != null + } + + override fun getTextRangeImportList(psiFile: PsiFile, offset: Int): TextRange? { + if (psiFile is PsiJavaFile) return psiFile.importList?.textRange + return null + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt index 208cb8a7d5e2..07338f9b1419 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt +++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionTest.kt @@ -237,6 +237,20 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() { }""".trimIndent()) } + fun testOptimizeImport2() { + Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable()) + myFixture.configureByText(JavaFileType.INSTANCE, """ + import java.util.List;. + + class A { + void foo() { + String y = "1"; + } + }""".trimIndent()) + val elements = myFixture.completeBasic() + assertTrue(elements.any { element -> element.lookupString.contains("Optimize im", ignoreCase = true) }) + } + fun testGenerateGetter() { Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable()) myFixture.configureByText(JavaFileType.INSTANCE, """ diff --git a/platform/lang-impl/api-dump-experimental.txt b/platform/lang-impl/api-dump-experimental.txt index 3be8ebfeca7f..3a1c3c6e9bd6 100644 --- a/platform/lang-impl/api-dump-experimental.txt +++ b/platform/lang-impl/api-dump-experimental.txt @@ -248,6 +248,13 @@ c:com.intellij.codeInsight.actions.VcsFacade - p:createCommand(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext):com.intellij.codeInsight.completion.command.commands.ActionCompletionCommand - a:findMoveClassOffset(I,com.intellij.psi.PsiFile):java.lang.Integer - p:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z +*a:com.intellij.codeInsight.completion.command.commands.AbstractOptimizeImportCompletionCommandProvider +- com.intellij.codeInsight.completion.command.commands.ActionCommandProvider +- ():V +- p:createCommand(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext):com.intellij.codeInsight.completion.command.commands.ActionCompletionCommand +- 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.AbstractRenameActionCommandProvider - com.intellij.codeInsight.completion.command.commands.ActionCommandProvider - ():V diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractOptimizeImportCompletionCommandProvider.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractOptimizeImportCompletionCommandProvider.kt new file mode 100644 index 000000000000..daed59fbb2ac --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractOptimizeImportCompletionCommandProvider.kt @@ -0,0 +1,45 @@ +// 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.lang.injection.InjectedLanguageManager +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.colors.EditorColors +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiFile + +abstract class AbstractOptimizeImportCompletionCommandProvider : + ActionCommandProvider(actionId = "OptimizeImports", + synonyms = listOf("Optimize imports"), + presentableName = ActionsBundle.message("action.OptimizeImports.text"), + icon = null, + priority = -100, + previewText = ActionsBundle.message("action.OptimizeImports.description")) { + + override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean { + if (!super.isApplicable(offset, psiFile, editor)) return false + if (InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)) return false + return isApplicableToProject(offset, psiFile) || isImportList(psiFile, offset) + } + + override fun createCommand(context: CommandCompletionProviderContext): ActionCompletionCommand? { + val range: TextRange? = getTextRangeImportList(context.psiFile, context.offset) + return ActionCompletionCommand(actionId = super.actionId, + presentableActionName = super.presentableName, + icon = super.icon, + priority = super.priority, + previewText = super.previewText, + synonyms = super.synonyms, + highlightInfo = if (range != null) { + HighlightInfoLookup(range, EditorColors.SEARCH_RESULT_ATTRIBUTES, 0) + } + else { + null + }) + } + + abstract fun isImportList(psiFile: PsiFile, offset: Int): Boolean + abstract fun getTextRangeImportList(psiFile: PsiFile, offset: Int): TextRange? +} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/OptimizeImportCompletionCommand.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/OptimizeImportCompletionCommand.kt deleted file mode 100644 index b5d1535c4c2f..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/OptimizeImportCompletionCommand.kt +++ /dev/null @@ -1,22 +0,0 @@ -// 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.idea.ActionsBundle -import com.intellij.lang.injection.InjectedLanguageManager -import com.intellij.openapi.editor.Editor -import com.intellij.psi.PsiFile - -internal class OptimizeImportCompletionCommandProvider : - ActionCommandProvider(actionId = "OptimizeImports", - synonyms = listOf("Optimize imports"), - presentableName = ActionsBundle.message("action.OptimizeImports.text"), - icon = null, - priority = -100, - previewText = ActionsBundle.message("action.OptimizeImports.description")) { - - override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean { - if (!super.isApplicable(offset, psiFile, editor)) return false - if (InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)) return false - return isApplicableToProject(offset, psiFile) - } -} \ No newline at end of file diff --git a/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinOptimizeImportCompletionCommandProvider.kt b/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinOptimizeImportCompletionCommandProvider.kt new file mode 100644 index 000000000000..23e79578c90b --- /dev/null +++ b/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinOptimizeImportCompletionCommandProvider.kt @@ -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 org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands + +import com.intellij.codeInsight.completion.command.commands.AbstractOptimizeImportCompletionCommandProvider +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiFile +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.psi.KtImportList + +class KotlinOptimizeImportCompletionCommandProvider : AbstractOptimizeImportCompletionCommandProvider() { + override fun isImportList(psiFile: PsiFile, offset: Int): Boolean { + if (offset - 1 < 0) return false + val element = psiFile.findElementAt(offset - 1) + return element?.parentOfType(withSelf = true) != null + } + + override fun getTextRangeImportList(psiFile: PsiFile, offset: Int): TextRange? { + if (psiFile is KtFile) return psiFile.importList?.textRange + return null + } +} \ No newline at end of file diff --git a/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/K2CommandCompletionTest.kt b/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/K2CommandCompletionTest.kt index a307dda18f94..43f81353171e 100644 --- a/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/K2CommandCompletionTest.kt +++ b/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/K2CommandCompletionTest.kt @@ -588,6 +588,7 @@ class K2CommandCompletionTest : KotlinLightCodeInsightFixtureTestCase() { val elements = myFixture.complete(CompletionType.BASIC, 0) assertTrue(elements[0].`as`(CommandCompletionLookupElement::class.java) != null) } + fun testNotFirstCompletion() { Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable()) myFixture.configureByText( @@ -605,6 +606,19 @@ class K2CommandCompletionTest : KotlinLightCodeInsightFixtureTestCase() { assertFalse(elements[0].`as`(CommandCompletionLookupElement::class.java) != null) } + fun testOptimizeImport() { + Registry.get("ide.completion.command.force.enabled").setValue(true, getTestRootDisposable()) + myFixture.configureByText( + "x.kt", """" + import java.util.List. + + class A { + }""".trimIndent() + ) + val elements = myFixture.completeBasic() + assertTrue(elements.any { element -> element.lookupString.contains("Optimize im", ignoreCase = true) }) + } + private fun selectItem(item: LookupElement, completionChar: Char = 0.toChar()) { val lookup: LookupImpl = myFixture.lookup as LookupImpl lookup.setCurrentItem(item) diff --git a/plugins/kotlin/plugin/k2/resources/META-INF/command-completion-k2.xml b/plugins/kotlin/plugin/k2/resources/META-INF/command-completion-k2.xml index d037d861ffb0..766d494041b3 100644 --- a/plugins/kotlin/plugin/k2/resources/META-INF/command-completion-k2.xml +++ b/plugins/kotlin/plugin/k2/resources/META-INF/command-completion-k2.xml @@ -11,7 +11,7 @@ + implementationClass="org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands.KotlinOptimizeImportCompletionCommandProvider"/>