diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml
index a0882b7cdd41..b3c1684cc699 100644
--- a/java/java-impl/resources/META-INF/JavaPlugin.xml
+++ b/java/java-impl/resources/META-INF/JavaPlugin.xml
@@ -1494,6 +1494,7 @@
+
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaSafeDeleteCompletionCommandProvider.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaSafeDeleteCompletionCommandProvider.kt
new file mode 100644
index 000000000000..215654274ade
--- /dev/null
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaSafeDeleteCompletionCommandProvider.kt
@@ -0,0 +1,44 @@
+// 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.CompletionCommand
+import com.intellij.codeInsight.completion.command.commands.AbstractSafeDeleteCompletionCommandProvider
+import com.intellij.codeInsight.completion.command.commands.DirectInspectionFixCompletionCommand
+import com.intellij.codeInsight.completion.command.getCommandContext
+import com.intellij.codeInsight.completion.command.getTargetContext
+import com.intellij.openapi.editor.Editor
+import com.intellij.psi.*
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.psi.util.elementType
+
+public class JavaSafeDeleteCompletionCommandProvider : AbstractSafeDeleteCompletionCommandProvider() {
+ override fun findElement(offset: Int, psiFile: PsiFile, editor: Editor?): PsiElement? {
+ if (editor == null) return null
+ var element = getCommandContext(offset, psiFile) ?: return null
+ if (element is PsiWhiteSpace) element = PsiTreeUtil.skipWhitespacesBackward(element) ?: return null
+ if (element is PsiIdentifier && (element.parent is PsiMember || element.parent is PsiVariable)) {
+ return element
+ }
+ if (element.elementType == JavaTokenType.RBRACE) {
+ val parentElement = element.parent
+ if (parentElement is PsiMember) {
+ val targetElement = (if (parentElement is PsiNameIdentifierOwner) parentElement.nameIdentifier
+ else PsiTreeUtil.findChildOfType(parentElement, PsiIdentifier::class.java)) ?: return null
+ return targetElement
+ }
+ val grandparentElement = element.parent?.parent
+ if (grandparentElement is PsiMember) {
+ val targetElement = (if (grandparentElement is PsiNameIdentifierOwner) grandparentElement.nameIdentifier
+ else PsiTreeUtil.findChildOfType(grandparentElement, PsiIdentifier::class.java)) ?: return null
+ return targetElement
+ }
+ }
+ val targetContext = getTargetContext(offset, editor)
+ if (targetContext?.isWritable != true) return null
+ return element
+ }
+
+ override fun skipForHighlightCommand(command: CompletionCommand): Boolean {
+ return command is DirectInspectionFixCompletionCommand && command.inspectionId == "unused"
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionSafeDeleteTest.kt b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionSafeDeleteTest.kt
new file mode 100644
index 000000000000..71de9bbb3def
--- /dev/null
+++ b/java/java-tests/testSrc/com/intellij/java/codeInsight/completion/commands/JavaCommandsCompletionSafeDeleteTest.kt
@@ -0,0 +1,152 @@
+// 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 JavaCommandsCompletionSafeDeleteTest : 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 testSafeDeleteClassEnd() {
+ myFixture.configureByText(JavaFileType.INSTANCE, """
+ class A {
+ int a;
+ }.
+
+ class B {
+ public B(){
+ new A();
+ }
+ }
+ """.trimIndent())
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testSafeDeleteClassIdentifier() {
+ myFixture.configureByText(JavaFileType.INSTANCE, """
+ class A. {
+ int a;
+ }
+
+ class B {
+ public B(){
+ new A();
+ }
+ }
+ """.trimIndent())
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testSafeDeleteClassReference() {
+ myFixture.configureByText(JavaFileType.INSTANCE, """
+ class A {
+ int a;
+ }
+
+ class B {
+ public B(){
+ new A.();
+ }
+ }
+ """.trimIndent())
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testSafeDeleteMethodEnd() {
+ myFixture.configureByText(JavaFileType.INSTANCE, """
+ class A {
+ int a;
+ public void foo(){}.
+ }
+
+ class B {
+ public B(){
+ new A().foo();
+ }
+ }
+ """.trimIndent())
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testSafeDeleteMethodIdentifier() {
+ myFixture.configureByText(JavaFileType.INSTANCE, """
+ class A {
+ int a;
+ public void foo.(){}
+ }
+
+ class B {
+ public B(){
+ new A().foo();
+ }
+ }
+ """.trimIndent())
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testSafeDeleteMethodReference() {
+ myFixture.configureByText(JavaFileType.INSTANCE, """
+ class A {
+ int a;
+ public void foo(){}
+ }
+
+ class B {
+ public B(){
+ new A().foo.();
+ }
+ }
+ """.trimIndent())
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testSafeDeleteVariable() {
+ myFixture.configureByText(JavaFileType.INSTANCE, """
+ class A {
+ int a;
+ public void foo(){}
+ }
+
+ class B {
+ public B(){
+ A a. = new A().foo();
+ System.out.println(a);
+ }
+ }
+ """.trimIndent())
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testSafeDeleteVariableReference() {
+ myFixture.configureByText(JavaFileType.INSTANCE, """
+ class A {
+ int a;
+ public void foo(){}
+ }
+
+ class B {
+ public B(){
+ A a = new A().foo();
+ System.out.println(a.);
+ }
+ }
+ """.trimIndent())
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/api-dump-experimental.txt b/platform/lang-impl/api-dump-experimental.txt
index d5d307519683..0f5065ac333b 100644
--- a/platform/lang-impl/api-dump-experimental.txt
+++ b/platform/lang-impl/api-dump-experimental.txt
@@ -267,6 +267,14 @@ c:com.intellij.codeInsight.actions.VcsFacade
- p:createCommand(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext):com.intellij.codeInsight.completion.command.commands.ActionCompletionCommand
- a:findRenameOffset(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.AbstractSafeDeleteCompletionCommandProvider
+- com.intellij.codeInsight.completion.command.commands.ActionCommandProvider
+- com.intellij.codeInsight.completion.command.commands.HighlightCommandSkipper
+- ():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.openapi.editor.Editor):com.intellij.psi.PsiElement
+- p:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
+- a:skipForHighlightCommand(com.intellij.codeInsight.completion.command.CompletionCommand):Z
*a:com.intellij.codeInsight.completion.command.commands.AbstractShowUsagesActionCompletionCommandProvider
- com.intellij.codeInsight.completion.command.commands.ActionCommandProvider
- ():V
@@ -322,11 +330,23 @@ c:com.intellij.codeInsight.actions.VcsFacade
- getPriority():java.lang.Integer
- getSynonyms():java.util.List
- f:setActionId(java.lang.String):V
+*f:com.intellij.codeInsight.completion.command.commands.DirectInspectionFixCompletionCommand
+- com.intellij.codeInsight.completion.command.CompletionCommand
+- (java.lang.String,java.lang.String,java.lang.Integer,javax.swing.Icon,com.intellij.codeInsight.completion.command.HighlightInfoLookup,I,kotlin.jvm.functions.Function0):V
+- execute(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):V
+- getHighlightInfo():com.intellij.codeInsight.completion.command.HighlightInfoLookup
+- getIcon():javax.swing.Icon
+- f:getInspectionId():java.lang.String
+- getPresentableName():java.lang.String
+- getPreview():com.intellij.codeInsight.intention.preview.IntentionPreviewInfo
+- getPriority():java.lang.Integer
*:com.intellij.codeInsight.completion.command.commands.ErrorFixCommandProvider
- com.intellij.openapi.project.PossiblyDumbAware
- *sf:Companion:com.intellij.codeInsight.completion.command.commands.ErrorFixCommandProvider$Companion
- a:getCommands(com.intellij.psi.PsiFile,java.util.List,I):java.util.List
*f:com.intellij.codeInsight.completion.command.commands.ErrorFixCommandProvider$Companion
+*:com.intellij.codeInsight.completion.command.commands.HighlightCommandSkipper
+- a:skipForHighlightCommand(com.intellij.codeInsight.completion.command.CompletionCommand):Z
*:com.intellij.codeInsight.completion.command.commands.IntentionCommandOffsetProvider
- *sf:Companion:com.intellij.codeInsight.completion.command.commands.IntentionCommandOffsetProvider$Companion
- findOffsets(com.intellij.psi.PsiFile,I):java.util.List
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandCompletionProvider.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandCompletionProvider.kt
index 49dd4660e490..55563a6aee3b 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandCompletionProvider.kt
+++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/CommandCompletionProvider.kt
@@ -2,6 +2,8 @@
package com.intellij.codeInsight.completion.command
import com.intellij.codeInsight.completion.*
+import com.intellij.codeInsight.completion.command.commands.DirectIntentionCommandProvider
+import com.intellij.codeInsight.completion.command.commands.HighlightCommandSkipper
import com.intellij.codeInsight.completion.command.configuration.ApplicationCommandCompletionService
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher
import com.intellij.codeInsight.completion.ml.MLWeigherUtil
@@ -237,7 +239,11 @@ internal class CommandCompletionProvider : CompletionProvider().firstOrNull()
+ val highlightCommandSkippers = commandProviders.filter { it is HighlightCommandSkipper }.toSet()
+ highlightCommandProvider?.setSkippers(highlightCommandSkippers)
+ for (provider in commandProviders.filter { it !is HighlightCommandSkipper }) {
try {
if (isReadOnly && !provider.supportsReadOnly()) continue
copyEditor.caretModel.moveToOffset(offset)
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractSafeDeleteCompletionCommand.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractSafeDeleteCompletionCommand.kt
new file mode 100644
index 000000000000..79846f0b10f0
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractSafeDeleteCompletionCommand.kt
@@ -0,0 +1,47 @@
+// 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.CompletionCommand
+import com.intellij.codeInsight.completion.command.HighlightInfoLookup
+import com.intellij.idea.ActionsBundle
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.editor.colors.EditorColors
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+
+abstract class AbstractSafeDeleteCompletionCommandProvider : HighlightCommandSkipper,
+ ActionCommandProvider(actionId = "SafeDelete",
+ presentableName = ActionsBundle.message("action.SafeDelete.text"),
+ synonyms = listOf("delete", "safe delete"),
+ icon = null,
+ priority = -100,
+ previewText = ActionsBundle.message("action.SafeDelete.description")) {
+
+ abstract fun findElement(offset: Int, psiFile: PsiFile, editor: Editor?): PsiElement?
+
+ abstract override fun skipForHighlightCommand(command: CompletionCommand): Boolean
+
+ override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
+ val element = findElement(offset, psiFile, editor) ?: return false
+ editor?.caretModel?.moveToOffset(element.textRange.endOffset)
+ return super.isApplicable(element.textRange.endOffset, psiFile, editor)
+ }
+
+ override fun createCommand(context: CommandCompletionProviderContext): ActionCompletionCommand? {
+ val element = findElement(context.offset, context.psiFile, context.editor) ?: return null
+ val startOffset = element.textRange.endOffset
+ return object : ActionCompletionCommand(actionId = super.actionId,
+ presentableActionName = super.presentableName,
+ icon = super.icon,
+ priority = super.priority,
+ previewText = super.previewText,
+ highlightInfo = HighlightInfoLookup(element.textRange, EditorColors.SEARCH_RESULT_ATTRIBUTES, 0),
+ synonyms = super.synonyms) {
+ override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
+ editor?.caretModel?.moveToOffset(startOffset)
+ super.execute(startOffset, psiFile, editor)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectInspectionFixCompletionCommand.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectInspectionFixCompletionCommand.kt
index e323bff7bbde..b56582fe8e10 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectInspectionFixCompletionCommand.kt
+++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectInspectionFixCompletionCommand.kt
@@ -31,8 +31,8 @@ import kotlinx.coroutines.job
import org.jetbrains.annotations.Nls
import javax.swing.Icon
-internal class DirectInspectionFixCompletionCommand(
- private val inspectionId: String,
+class DirectInspectionFixCompletionCommand(
+ val inspectionId: String,
override val presentableName: @Nls String,
override val priority: Int?,
override val icon: Icon?,
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt
index 97d18328521f..5855d9f8d13a 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt
+++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt
@@ -72,6 +72,11 @@ import java.util.function.Predicate
* based on intentions, errors, and inspections within the given context.
*/
internal class DirectIntentionCommandProvider : CommandProvider {
+ private var mySkipper: Set = emptySet()
+ fun setSkippers(skippers: Set) {
+ mySkipper = skippers
+ }
+
override fun getCommands(context: CommandCompletionProviderContext): List {
if (!ApplicationCommandCompletionService.getInstance().commandCompletionEnabled()) return emptyList()
val originalEditor = context.originalEditor
@@ -136,10 +141,32 @@ internal class DirectIntentionCommandProvider : CommandProvider {
val intentions = asyncIntentions.await()
result.addAll(intentions)
+ result.addAll(processSkippers(result, context))
return@runBlockingCancellable result
}
}
+ private fun processSkippers(fromHighlights: MutableList, context: CommandCompletionProviderContext): Collection {
+ val result = mutableListOf()
+ if (mySkipper.isEmpty()) return emptyList()
+ for (provider in mySkipper) {
+ if (provider is HighlightCommandSkipper) {
+ if (fromHighlights.any { provider.skipForHighlightCommand(it) }) continue
+ try {
+ context.editor.caretModel.moveToOffset(context.offset)
+ result.addAll(provider.getCommands(context))
+ }
+ catch (e: Exception) {
+ if (e is ControlFlowException || e is CancellationException) {
+ throw e
+ }
+ thisLogger().error("Can't collect commands", e)
+ }
+ }
+ }
+ return result
+ }
+
private fun createCustomEditor(
psiFile: PsiFile,
editor: Editor,
@@ -485,7 +512,7 @@ internal class DirectIntentionCommandProvider : CommandProvider {
modCommandAction.getPresentation(ActionContext.from(editor, psiFile))
?.rangesToHighlight()
?.firstOrNull { highlightRange ->
- highlightRange.highlightingKind() == Presentation.HighlightingKind.APPLICABLE_TO_RANGE &&
+ highlightRange.highlightingKind() == Presentation.HighlightingKind.APPLICABLE_TO_RANGE &&
highlightRange.range.startOffset <= currentOffset
}
?.let {
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/HighlightCommandSkipper.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/HighlightCommandSkipper.kt
new file mode 100644
index 000000000000..5a5a2050b9f4
--- /dev/null
+++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/HighlightCommandSkipper.kt
@@ -0,0 +1,8 @@
+// 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.CompletionCommand
+
+interface HighlightCommandSkipper {
+ fun skipForHighlightCommand(command: CompletionCommand): Boolean
+}
\ No newline at end of file
diff --git a/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinSafeDeleteCompletionCommandProvider.kt b/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinSafeDeleteCompletionCommandProvider.kt
new file mode 100644
index 000000000000..44a1cb8d32c3
--- /dev/null
+++ b/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinSafeDeleteCompletionCommandProvider.kt
@@ -0,0 +1,58 @@
+// 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.CompletionCommand
+import com.intellij.codeInsight.completion.command.commands.AbstractSafeDeleteCompletionCommandProvider
+import com.intellij.codeInsight.completion.command.commands.DirectInspectionFixCompletionCommand
+import com.intellij.codeInsight.completion.command.getCommandContext
+import com.intellij.codeInsight.completion.command.getTargetContext
+import com.intellij.openapi.editor.Editor
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiNameIdentifierOwner
+import com.intellij.psi.PsiWhiteSpace
+import com.intellij.psi.util.PsiTreeUtil
+import com.intellij.psi.util.parentOfType
+import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.KtFunction
+
+internal class KotlinSafeDeleteCompletionCommandProvider : AbstractSafeDeleteCompletionCommandProvider() {
+ override fun findElement(
+ offset: Int,
+ psiFile: PsiFile,
+ editor: Editor?
+ ): PsiElement? {
+ var currentOffset = offset
+ if (currentOffset == 0 || editor == null) return null
+ var element = getCommandContext(offset, psiFile) ?: return null
+ if (element is PsiWhiteSpace) {
+ element = PsiTreeUtil.prevVisibleLeaf(element) ?: return null
+ currentOffset = element.textRange.endOffset
+ }
+
+ val method = element.parentOfType()
+ if (method != null &&
+ (method.identifyingElement?.textRange?.endOffset == currentOffset ||
+ method.bodyBlockExpression?.textRange?.endOffset == currentOffset)
+ ) return method.identifyingElement
+
+ val psiClass = element.parentOfType()
+ if (psiClass != null && psiClass.getBody()?.rBrace != null && psiClass.getBody()?.rBrace?.textRange?.endOffset == currentOffset) {
+ return psiClass.identifyingElement
+ }
+
+ val nameIdentifierOwner = element.parentOfType()
+ if (nameIdentifierOwner != null && nameIdentifierOwner.nameIdentifier?.textRange?.endOffset == currentOffset) {
+ return nameIdentifierOwner.nameIdentifier
+ }
+
+ val targetContext = getTargetContext(offset, editor)
+ if (targetContext?.isWritable != true) return null
+ return element
+ }
+
+ override fun skipForHighlightCommand(command: CompletionCommand): Boolean {
+ return command is DirectInspectionFixCompletionCommand &&
+ (command.inspectionId == "UnusedVariable" || command.inspectionId == "unused")
+ }
+}
\ No newline at end of file
diff --git a/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/commands/K2CommandCompletionSafeDeleteTest.kt b/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/commands/K2CommandCompletionSafeDeleteTest.kt
new file mode 100644
index 000000000000..98e411621a7d
--- /dev/null
+++ b/plugins/kotlin/fir/tests/test/org/jetbrains/kotlin/idea/fir/completion/commands/K2CommandCompletionSafeDeleteTest.kt
@@ -0,0 +1,186 @@
+// 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 K2CommandCompletionSafeDeleteTest : 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 testClassIdentifier() {
+ myFixture.configureByText(
+ "x.kt", """
+ class A. {
+ fun foo(a: String) {
+ var b = a
+ }
+ }
+
+ class B{
+ fun bar(){
+ var c = A()
+ }
+ }
+ """.trimIndent()
+ )
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull() { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testClassEnd() {
+ myFixture.configureByText(
+ "x.kt", """
+ class A {
+ fun foo(a: String) {
+ var b = a
+ }
+ }.
+
+
+ class B{
+ fun bar(){
+ var c = A()
+ }
+ }
+ """.trimIndent()
+ )
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull() { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testClassReference() {
+ myFixture.configureByText(
+ "x.kt", """
+ class A {
+ fun foo(a: String) {
+ var b = a
+ }
+ }
+
+
+ class B{
+ fun bar(){
+ var c = A.()
+ }
+ }
+ """.trimIndent()
+ )
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull() { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+
+ fun testMethodIdentifier() {
+ myFixture.configureByText(
+ "x.kt", """
+ class A {
+ fun foo.(a: String) {
+ var b = a
+ }
+ }
+
+ class B{
+ fun bar(){
+ var c = A().foo("1")
+ }
+ }
+ """.trimIndent()
+ )
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull() { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testMethodEnd() {
+ myFixture.configureByText(
+ "x.kt", """
+ class A {
+ fun foo(a: String) {
+ var b = a
+ } .
+ }
+
+
+ class B{
+ fun bar(){
+ var c = A().foo("1")
+ }
+ }
+ """.trimIndent()
+ )
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull() { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+
+ fun testMethodReference() {
+ myFixture.configureByText(
+ "x.kt", """
+
+ class A {
+ fun foo(a: String) {
+ var b = a
+ }
+ }
+
+
+ class B{
+ fun bar(){
+ var c = A().foo.("1")
+ }
+ }
+ """.trimIndent()
+ )
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull() { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+
+ fun testVariable() {
+ myFixture.configureByText(
+ "x.kt", """
+
+ class A {
+ fun foo(a.: String) {
+ var b = a
+ }
+ }
+
+
+ class B{
+ fun bar(){
+ var c = A().foo("1")
+ }
+ }
+ """.trimIndent()
+ )
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+ fun testVariableReference() {
+ myFixture.configureByText(
+ "x.kt", """
+
+ class A {
+ fun foo(a: String) {
+ var b = a.
+ }
+ }
+
+
+ class B{
+ fun bar(){
+ var c = A().foo("1")
+ }
+ }
+ """.trimIndent()
+ )
+ val elements = myFixture.completeBasic()
+ assertNotNull(elements.firstOrNull { element -> element.lookupString.contains("Safe delete", ignoreCase = true) })
+ }
+}
\ No newline at end of file
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 044a7e8921db..695c2bf2c680 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
@@ -45,6 +45,7 @@
+