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 @@ +