From 8e4ed43a3f80552d16a5727ebe7b76cbeb33554f Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Wed, 2 Apr 2025 16:00:39 +0200 Subject: [PATCH] [command-completion] IDEA-369839 part of Support intention preview for all commands in command completion GitOrigin-RevId: ece703b31a5e8ef765802e69b09f4629811deeb4 --- .../resources/META-INF/JavaPlugin.xml | 10 +- ...rateGetterSetterHandleCompletionCommand.kt | 67 +++++++---- .../impl/JavaDeleteCompletionCommand.kt | 65 +++++++---- .../JavaInlineVariableCompletionCommand.kt | 72 +++++++----- .../impl/JavaIntroduceVariableCommand.kt | 42 +++++-- .../analysis-api/api-dump-experimental.txt | 6 - .../completion/command/CompletionCommand.kt | 46 ++------ platform/lang-impl/api-dump-experimental.txt | 15 --- .../AbstractActionCompletionCommand.kt | 110 ++++++------------ .../DirectIntentionCommandProvider.kt | 27 ++++- .../commands/ExtensionBasedCommandProvider.kt | 34 ------ .../src/META-INF/LangExtensionPoints.xml | 4 - .../src/META-INF/LangExtensions.xml | 1 - .../commands/KotlinDeleteCompletionCommand.kt | 96 ++++++++------- .../META-INF/command-completion-k2.xml | 4 +- 15 files changed, 291 insertions(+), 308 deletions(-) delete mode 100644 platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/ExtensionBasedCommandProvider.kt diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml index bd63d0952089..9292ad62e922 100644 --- a/java/java-impl/resources/META-INF/JavaPlugin.xml +++ b/java/java-impl/resources/META-INF/JavaPlugin.xml @@ -1509,7 +1509,7 @@ implementationClass="com.intellij.codeInsight.completion.commands.JavaCommandCompletionFactory$JavaIntentionCommandOffsetProvider"/> - + @@ -1520,12 +1520,10 @@ implementationClass="com.intellij.codeInsight.completion.command.commands.PsiElementCommentByLineCompletionCommandProvider"/> - + - - - - + + diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaBaseGenerateGetterSetterHandleCompletionCommand.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaBaseGenerateGetterSetterHandleCompletionCommand.kt index bea6f994c5ea..b15ab74a4284 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaBaseGenerateGetterSetterHandleCompletionCommand.kt +++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaBaseGenerateGetterSetterHandleCompletionCommand.kt @@ -1,50 +1,69 @@ // Copyright 2000-2024 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.ApplicableCompletionCommand +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.CompletionCommandWithPreview +import com.intellij.codeInsight.completion.command.HighlightInfoLookup import com.intellij.codeInsight.completion.command.getCommandContext import com.intellij.codeInsight.daemon.QuickFixBundle import com.intellij.codeInsight.intention.QuickFixFactory import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler +import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewComputable +import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo import com.intellij.modcommand.ActionContext import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.colors.EditorColors import com.intellij.psi.PsiField import com.intellij.psi.PsiFile import com.intellij.psi.PsiIdentifier import com.intellij.psi.util.PsiTreeUtil import javax.swing.Icon +internal class GenerateGetterSetterHandleCompletionCommandProvider : CommandProvider { + override fun getCommands(context: CommandCompletionProviderContext): List { + val element = getCommandContext(context.offset, context.psiFile) ?: return emptyList() + if (element !is PsiIdentifier) return emptyList() + val field = PsiTreeUtil.getParentOfType(element, PsiField::class.java) ?: return emptyList() -internal class GenerateGetterSetterHandleCompletionCommand : - BaseGenerateGetterSetterHandleCompletionCommand(true, true, "Generate 'Getter/Setter'", - QuickFixBundle.message("create.getter.setter")) + val result = mutableListOf() + val possibleCases = listOf( + GetterSetterCase(true, true, "Generate 'Getter/Setter'", QuickFixBundle.message("create.getter.setter")), + GetterSetterCase(false, true, "Generate 'Setter'", QuickFixBundle.message("create.setter")), + GetterSetterCase(true, false, "Generate 'Getter'", QuickFixBundle.message("create.getter")), + ) + for (case in possibleCases) { + val action = QuickFixFactory.getInstance() + .createCreateGetterOrSetterFix(case.generateGetter, case.generateSetter, field) + val actionContext = ActionContext.from(context.editor, context.psiFile).withElement(field) + val modCommandAction = action.asModCommandAction() + if (modCommandAction == null || modCommandAction.getPresentation(actionContext) == null) continue + result.add(BaseGenerateGetterSetterHandleCompletionCommand(case.generateGetter, + case.generateSetter, + case.name, + case.i18nName, + HighlightInfoLookup(field.textRange, EditorColors.SEARCH_RESULT_ATTRIBUTES, 0)) { + IntentionPreviewComputable(context.project, action, context.psiFile, context.editor, context.offset).call() + }) + } + return result + } +} -internal class GenerateSetterHandleCompletionCommand : - BaseGenerateGetterSetterHandleCompletionCommand(false, true, "Generate 'Setter'", - QuickFixBundle.message("create.setter")) +private data class GetterSetterCase(val generateGetter: Boolean, val generateSetter: Boolean, val name: String, val i18nName: String) -internal class GenerateGetterHandleCompletionCommand : - BaseGenerateGetterSetterHandleCompletionCommand(true, false, "Generate 'Getter'", - QuickFixBundle.message("create.getter")) - -abstract class BaseGenerateGetterSetterHandleCompletionCommand( +private class BaseGenerateGetterSetterHandleCompletionCommand( val generateGetter: Boolean, val generateSetter: Boolean, override val name: String, override val i18nName: String, -) : ApplicableCompletionCommand() { + override val highlightInfo: HighlightInfoLookup?, + private val preview: () -> IntentionPreviewInfo?, +) : CompletionCommand(), CompletionCommandWithPreview { override val icon: Icon? = null - override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean { - val element = getCommandContext(offset, psiFile) ?: return false - if (element !is PsiIdentifier) return false - val field = PsiTreeUtil.getParentOfType(element, PsiField::class.java) ?: return false - val action = QuickFixFactory.getInstance().createCreateGetterOrSetterFix(generateGetter, generateSetter, field) - val context = ActionContext.from(editor, psiFile).withElement(field) - return action.asModCommandAction()?.getPresentation(context) != null - } - override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) { val element = getCommandContext(offset, psiFile) ?: return val field = PsiTreeUtil.getParentOfType(element, PsiField::class.java) ?: return @@ -53,4 +72,8 @@ abstract class BaseGenerateGetterSetterHandleCompletionCommand( @Suppress("DialogTitleCapitalization") ShowIntentionActionsHandler.chooseActionAndInvoke(psiFile, editor, action, action.text) } + + override fun getPreview(): IntentionPreviewInfo? { + return preview() + } } \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaDeleteCompletionCommand.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaDeleteCompletionCommand.kt index 78ec9e3e29a1..c4f71e195f59 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaDeleteCompletionCommand.kt +++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaDeleteCompletionCommand.kt @@ -2,9 +2,14 @@ package com.intellij.codeInsight.completion.commands.impl import com.intellij.codeInsight.actions.ReformatCodeProcessor -import com.intellij.codeInsight.completion.command.ApplicableCompletionCommand +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.CompletionCommandWithPreview import com.intellij.codeInsight.completion.command.HighlightInfoLookup import com.intellij.codeInsight.completion.command.getCommandContext +import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo +import com.intellij.ide.highlighter.JavaFileType import com.intellij.java.JavaBundle import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Editor @@ -21,27 +26,42 @@ import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.annotations.Nls import javax.swing.Icon -internal class JavaDeleteCompletionCommand : ApplicableCompletionCommand(), DumbAware { - private var _highlightInfo: HighlightInfoLookup? = null +internal class JavaDeleteCompletionCommandProvider : CommandProvider { + override fun getCommands(context: CommandCompletionProviderContext): List { + val element = getCommandContext(context.offset, context.psiFile) ?: return emptyList() + var psiElement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java, PsiMember::class.java) ?: return emptyList() + val hasTheSameOffset = psiElement.textRange.endOffset == context.offset + if (!hasTheSameOffset) return emptyList() + psiElement = getTopWithTheSameOffset(psiElement, context.offset) + val highlightInfo = HighlightInfoLookup(psiElement.textRange, EditorColors.SEARCH_RESULT_ATTRIBUTES, 0) + return listOf(createCommand(highlightInfo, psiElement)) + } + + private fun createCommand(highlightInfo: HighlightInfoLookup, psiElement: PsiElement): CompletionCommand { + return JavaDeleteCompletionCommand(highlightInfo, psiElement.text) + } +} + +private fun getTopWithTheSameOffset(psiElement: PsiElement, offset: Int): PsiElement { + var psiElement1 = psiElement + var curElement = psiElement1 + while (curElement.textRange.endOffset == offset) { + psiElement1 = curElement + curElement = PsiTreeUtil.getParentOfType(curElement, PsiStatement::class.java, PsiMember::class.java) ?: break + } + return psiElement1 +} + +private class JavaDeleteCompletionCommand( + override val highlightInfo: HighlightInfoLookup?, + private val originalText: String, +) : CompletionCommand(), CompletionCommandWithPreview, DumbAware { override val name: String get() = "Delete element" override val i18nName: @Nls String get() = JavaBundle.message("command.completion.delete.element.text") override val icon: Icon? get() = null - override val highlightInfo: HighlightInfoLookup? - get() = _highlightInfo - - - override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean { - val element = getCommandContext(offset, psiFile) ?: return false - var psiElement = PsiTreeUtil.getParentOfType(element, PsiStatement::class.java, PsiMember::class.java) ?: return false - val hasTheSameOffset = psiElement.textRange.endOffset == offset - if (!hasTheSameOffset) return false - psiElement = getTopWithTheSameOffset(psiElement, offset) - _highlightInfo = HighlightInfoLookup(psiElement.textRange, EditorColors.DELETED_TEXT_ATTRIBUTES, 0) - return true - } override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) { val element = getCommandContext(offset, psiFile) ?: return @@ -54,15 +74,10 @@ internal class JavaDeleteCompletionCommand : ApplicableCompletionCommand(), Dumb parent.element?.let { ReformatCodeProcessor(psiFile, arrayOf(it.textRange)).run() } - }, psiFile) } + }, psiFile) + } - private fun getTopWithTheSameOffset(psiElement: PsiElement, offset: Int): PsiElement { - var psiElement1 = psiElement - var curElement = psiElement1 - while (curElement.textRange.endOffset == offset) { - psiElement1 = curElement - curElement = PsiTreeUtil.getParentOfType(curElement, PsiStatement::class.java, PsiMember::class.java) ?: break - } - return psiElement1 + override fun getPreview(): IntentionPreviewInfo? { + return IntentionPreviewInfo.CustomDiff(JavaFileType.INSTANCE, originalText, "") } } \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaInlineVariableCompletionCommand.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaInlineVariableCompletionCommand.kt index a39f7aa72b67..7d9efd1386dd 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaInlineVariableCompletionCommand.kt +++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaInlineVariableCompletionCommand.kt @@ -1,12 +1,20 @@ // Copyright 2000-2024 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.ApplicableCompletionCommand +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.CompletionCommandWithPreview +import com.intellij.codeInsight.completion.command.HighlightInfoLookup import com.intellij.codeInsight.completion.command.getCommandContext +import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo import com.intellij.icons.AllIcons +import com.intellij.idea.ActionsBundle import com.intellij.java.JavaBundle import com.intellij.lang.refactoring.InlineActionHandler import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.colors.EditorColors +import com.intellij.openapi.project.DumbAware import com.intellij.psi.PsiFile import com.intellij.psi.PsiIdentifier import com.intellij.psi.PsiJavaCodeReferenceElement @@ -14,47 +22,55 @@ import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.annotations.Nls import javax.swing.Icon -internal class JavaInlineVariableCompletionCommand : ApplicableCompletionCommand() { - override val name: String - get() = "Inline" +internal class JavaInlineVariableCompletionCommandProvider : CommandProvider { + override fun getCommands(context: CommandCompletionProviderContext): List { + val element = getCommandContext(context.offset, context.psiFile) ?: return emptyList() + if (element !is PsiIdentifier) return emptyList() - override val i18nName: @Nls String - get() = JavaBundle.message("command.completion.inline.text") + val javaRef = PsiTreeUtil.getParentOfType(element, PsiJavaCodeReferenceElement::class.java) ?: return emptyList() + val psiElement = javaRef.resolve() ?: return emptyList() - override val icon: Icon - get() = AllIcons.Actions.RefactoringBulb // Use an appropriate icon from IntelliJ's icon set - - override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean { - if (editor == null) return false - val context = getCommandContext(offset, psiFile) - if (context !is PsiIdentifier) { - return false - } - val javaRef = PsiTreeUtil.getParentOfType(context, PsiJavaCodeReferenceElement::class.java) - - val psiElement = javaRef?.resolve() ?: return false + // Check if any inline handler can handle this element + val editor = context.editor val extensionList = InlineActionHandler.EP_NAME.extensionList for (extension in extensionList) { try { if (extension.canInlineElementInEditor(psiElement, editor)) { - return true + val highlightInfo = element.textRange?.let { + HighlightInfoLookup(it, EditorColors.SEARCH_RESULT_ATTRIBUTES, 0) + } + return listOf(JavaInlineVariableCompletionCommand(highlightInfo)) } } catch (_: Exception) { continue } } - return false + + return emptyList() } +} + +private class JavaInlineVariableCompletionCommand( + override val highlightInfo: HighlightInfoLookup?, +) : CompletionCommand(), DumbAware, CompletionCommandWithPreview { + override val name: String + get() = "Inline" + + override val i18nName: @Nls String + get() = JavaBundle.message("command.completion.inline.text") + + + override val icon: Icon? + get() = AllIcons.Actions.RefactoringBulb // Use an appropriate icon from IntelliJ's icon set override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) { - val context = getCommandContext(offset, psiFile) - if (context !is PsiIdentifier) { - return - } - val javaRef = PsiTreeUtil.getParentOfType(context, PsiJavaCodeReferenceElement::class.java) + if (editor == null) return + val element = getCommandContext(offset, psiFile) ?: return + if (element !is PsiIdentifier) return - val psiElement = javaRef?.resolve() ?: return + val javaRef = PsiTreeUtil.getParentOfType(element, PsiJavaCodeReferenceElement::class.java) ?: return + val psiElement = javaRef.resolve() ?: return val extensionList = InlineActionHandler.EP_NAME.extensionList for (extension in extensionList) { @@ -63,7 +79,9 @@ internal class JavaInlineVariableCompletionCommand : ApplicableCompletionCommand return } } - return } + override fun getPreview(): IntentionPreviewInfo? { + return IntentionPreviewInfo.Html(ActionsBundle.message("action.Inline.description")) + } } \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaIntroduceVariableCommand.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaIntroduceVariableCommand.kt index ee63b2fb9e86..d26dcbc6a6a5 100644 --- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaIntroduceVariableCommand.kt +++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/JavaIntroduceVariableCommand.kt @@ -1,11 +1,16 @@ // 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.ApplicableCompletionCommand +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.CompletionCommandWithPreview import com.intellij.codeInsight.completion.command.getDataContext import com.intellij.codeInsight.completion.command.getTargetContext +import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo import com.intellij.icons.AllIcons import com.intellij.ide.DataManager +import com.intellij.idea.ActionsBundle import com.intellij.lang.ContextAwareActionHandler import com.intellij.openapi.actionSystem.ActionPlaces import com.intellij.openapi.actionSystem.ActionUiKind @@ -20,7 +25,26 @@ import com.intellij.refactoring.actions.IntroduceVariableAction import org.jetbrains.annotations.Nls import javax.swing.Icon -internal class JavaIntroduceVariableCommand : ApplicableCompletionCommand() { +internal class JavaIntroduceVariableCommandProvider : CommandProvider { + override fun getCommands(context: CommandCompletionProviderContext): List { + val editor = context.editor + val psiFile = context.psiFile + val offset = context.offset + val factory = JavaRefactoringActionHandlerFactory.getInstance() + val variableHandler = factory.createIntroduceVariableHandler() + if (variableHandler is ContextAwareActionHandler && + variableHandler.isAvailableForQuickList(editor, psiFile, getDataContext(psiFile, editor, getTargetContext(offset, editor)))) { + return listOf(createCommand()) + } + return emptyList() + } + + private fun createCommand(): CompletionCommand { + return JavaIntroduceVariableCommand() + } +} + +internal class JavaIntroduceVariableCommand : CompletionCommand(), CompletionCommandWithPreview { override val name: String get() = "Introduce variable" @@ -30,16 +54,6 @@ internal class JavaIntroduceVariableCommand : ApplicableCompletionCommand() { override val icon: Icon get() = AllIcons.Nodes.Variable - override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean { - if (editor == null) return false - val factory = JavaRefactoringActionHandlerFactory.getInstance() - val variableHandler = factory.createIntroduceVariableHandler() - if (variableHandler is ContextAwareActionHandler) { - return variableHandler.isAvailableForQuickList(editor, psiFile, getDataContext(psiFile, editor, getTargetContext(offset, editor))) - } - return false - } - override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) { val action = IntroduceVariableAction() if (editor == null) return @@ -51,4 +65,8 @@ internal class JavaIntroduceVariableCommand : ApplicableCompletionCommand() { ActionUtil.performActionDumbAwareWithCallbacks(action, event) } } + + override fun getPreview(): IntentionPreviewInfo? { + return IntentionPreviewInfo.Html(ActionsBundle.message("action.IntroduceVariable.description")) + } } \ No newline at end of file diff --git a/platform/analysis-api/api-dump-experimental.txt b/platform/analysis-api/api-dump-experimental.txt index 1984f296fb37..c9c3be7cece4 100644 --- a/platform/analysis-api/api-dump-experimental.txt +++ b/platform/analysis-api/api-dump-experimental.txt @@ -1,9 +1,3 @@ -*a:com.intellij.codeInsight.completion.command.ApplicableCompletionCommand -- com.intellij.codeInsight.completion.command.CompletionCommand -- com.intellij.openapi.project.PossiblyDumbAware -- ():V -- a:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z -- supportsReadOnly():Z *:com.intellij.codeInsight.completion.command.CommandCompletionFactory - com.intellij.openapi.project.PossiblyDumbAware - commandProviders(com.intellij.openapi.project.Project,com.intellij.lang.Language):java.util.List diff --git a/platform/analysis-api/src/com/intellij/codeInsight/completion/command/CompletionCommand.kt b/platform/analysis-api/src/com/intellij/codeInsight/completion/command/CompletionCommand.kt index ca0badb6e702..0602586c1bfa 100644 --- a/platform/analysis-api/src/com/intellij/codeInsight/completion/command/CompletionCommand.kt +++ b/platform/analysis-api/src/com/intellij/codeInsight/completion/command/CompletionCommand.kt @@ -5,12 +5,10 @@ import com.intellij.codeInsight.completion.PrefixMatcher import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.colors.TextAttributesKey -import com.intellij.openapi.project.PossiblyDumbAware import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.util.concurrency.annotations.RequiresEdt -import com.intellij.util.concurrency.annotations.RequiresReadLock import org.jetbrains.annotations.Nls import javax.swing.Icon @@ -42,7 +40,19 @@ abstract class CompletionCommand { * can be left null, in which case a default priority will be assumed. */ open val priority: Int? = null + + /** + * Provides additional information about the command that can be displayed + * in the completion popup. This information helps users understand the + * purpose or effect of the command. + */ open val additionalInfo: String? = null + + /** + * Specifies how the command should be highlighted in the editor. + * Contains information about the text range to highlight, the highlighting style, + * and the priority of the highlight effect. + */ open val highlightInfo: HighlightInfoLookup? = null /** @@ -85,38 +95,6 @@ fun getCommandContext(offset: Int, psiFile: PsiFile): PsiElement? { return (if (offset == 0) psiFile.findElementAt(offset) else psiFile.findElementAt(offset - 1)) } -/** - * Represents an abstract base class for completion commands that are applicable under specific conditions. - * This class extends the functionality of `CompletionCommand` and introduces methods to evaluate the - * applicability of the command in a given context. - * - * Should be stateless and can be applied either to physical and non-physical classes - * Should implement DumbAware to support DumbMode - */ -@Deprecated("Use providers instead") -abstract class ApplicableCompletionCommand : CompletionCommand(), PossiblyDumbAware { - - /** - * Indicates whether the implementation supports non-written files. - * (For example, a command can navigate to another file) - * - * @return true if non-written files are supported; false otherwise. - */ - @Deprecated("Use providers instead") - open fun supportsReadOnly(): Boolean = false - - /** - * Determines whether the command is applicable based on the given context. - * Can be called on non-physical classes and imaginary editors - * - * @param offset The offset in the file where the applicability should be checked. - * @param psiFile The PSI file where the applicability should be evaluated. - * @param editor The editor. Can be null. Used only for compatibility with old actions - */ - @RequiresReadLock - abstract fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean -} - data class HighlightInfoLookup( val range: TextRange, val attributesKey: TextAttributesKey, diff --git a/platform/lang-impl/api-dump-experimental.txt b/platform/lang-impl/api-dump-experimental.txt index e2f3532ced2d..5cb624aacb2f 100644 --- a/platform/lang-impl/api-dump-experimental.txt +++ b/platform/lang-impl/api-dump-experimental.txt @@ -149,21 +149,6 @@ c:com.intellij.codeInsight.actions.VcsFacade - documentationTarget(com.intellij.psi.PsiFile,com.intellij.codeInsight.lookup.LookupElement,I):com.intellij.platform.backend.documentation.DocumentationTarget *f:com.intellij.codeInsight.completion.command.CommandCompletionDocumentationProviderKt - sf:combineFragments(java.util.List,java.util.List):java.util.List -*a:com.intellij.codeInsight.completion.command.commands.AbstractActionCompletionCommand -- com.intellij.codeInsight.completion.command.ApplicableCompletionCommand -- com.intellij.openapi.project.DumbAware -- (java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Integer):V -- b:(java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Integer,I,kotlin.jvm.internal.DefaultConstructorMarker):V -- execute(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):V -- f:getActionId():java.lang.String -- getAdditionalInfo():java.lang.String -- getI18nName():java.lang.String -- getIcon():javax.swing.Icon -- getName():java.lang.String -- getPriority():java.lang.Integer -- isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z -- pf:isApplicableToProject(I,com.intellij.psi.PsiFile):Z -- f:setActionId(java.lang.String):V *a:com.intellij.codeInsight.completion.command.commands.AbstractChangeSignatureCompletionCommandProvider - com.intellij.codeInsight.completion.command.commands.ActionCommandProvider - ():V diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractActionCompletionCommand.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractActionCompletionCommand.kt index a053ae44fc21..a5a045503562 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractActionCompletionCommand.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/AbstractActionCompletionCommand.kt @@ -1,7 +1,6 @@ // 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.ApplicableCompletionCommand import com.intellij.codeInsight.completion.command.CommandCompletionProviderContext import com.intellij.codeInsight.completion.command.CommandProvider import com.intellij.codeInsight.completion.command.CompletionCommand @@ -29,6 +28,18 @@ import org.jetbrains.annotations.Nls import javax.swing.Icon +/** + * Provides completion commands that execute IDE actions identified by their action IDs. + * This provider creates commands that integrate with IntelliJ Platform's action system, + * allowing actions to be triggered through code completion features. + * + * @property actionId The unique identifier of the IDE action to be executed + * @property name The display name of the command + * @property i18nName The internationalized name of the command + * @property icon Optional icon to be displayed with the command + * @property priority Optional priority value affecting command ordering + * @property previewText Optional preview text shown when the command is selected + */ open class ActionCommandProvider( @field:Language("devkit-action-id") var actionId: String, val name: String, @@ -38,12 +49,29 @@ open class ActionCommandProvider( val previewText: @Nls String?, ) : CommandProvider, DumbAware { + /** + * Creates and returns a list of completion commands based on the provided context. + * The method checks if the action is applicable in the current context and creates + * the appropriate command if conditions are met. + * + * @param context The context containing information about the completion environment + * @return A list of completion commands, empty if the action is not applicable + */ override fun getCommands(context: CommandCompletionProviderContext): List { if (!isApplicable(context.offset, context.psiFile, context.editor)) return emptyList() val element = createCommand(context) ?: return emptyList() return listOf(element) } + + /** + * Creates a new action completion command based on the provided context. + * This method instantiates an [ActionCompletionCommand] with the provider's configuration + * including action ID, name, icon, and preview settings. + * + * @param context The context containing information about the completion environment + * @return A new [ActionCompletionCommand] instance, or null if the command cannot be created + */ protected open fun createCommand(context: CommandCompletionProviderContext): ActionCompletionCommand? = ActionCompletionCommand(actionId = actionId, name = name, @@ -52,6 +80,14 @@ open class ActionCommandProvider( priority = priority, previewText = previewText) + /** + * Checks whether the action associated with this provider is applicable in the current context. + * + * @param offset The caret offset in the editor + * @param psiFile The PSI file being edited + * @param editor The current editor instance, may be null + * @return true if the action can be applied in the current context, false otherwise + */ protected open fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean { val action: AnAction? = ActionManager.getInstance().getAction(actionId) if (action == null || editor == null) return false @@ -129,76 +165,4 @@ open class ActionCompletionCommand( if (previewText == null) return null return IntentionPreviewInfo.Html(previewText) } -} - -/** - * Represents an abstract action completion command that triggers a specific IDE action - * identified by its `actionId`. This class allows integration of IntelliJ Platform actions - * as part of code completion features, ensuring that the action is applicable and - * executable within a given code editor context. - */ -@Deprecated("Use providers instead") -abstract class AbstractActionCompletionCommand( - @Language("devkit-action-id") var actionId: String, - override val name: String, - override val i18nName: @Nls String, - override val icon: Icon?, - override val priority: Int? = null, -) : ApplicableCompletionCommand(), DumbAware { - private val action: AnAction? = ActionManager.getInstance().getAction(actionId) - - override val additionalInfo: String? - get() { - val shortcutText = KeymapUtil.getFirstKeyboardShortcutText(actionId) - if (shortcutText.isNotEmpty()) { - return shortcutText - } - return null - } - - override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean { - val action = action ?: return false - if (editor == null) return false - if (!DumbService.Companion.getInstance(psiFile.project).isUsableInCurrentContext(action)) return false - val context = getTargetContext(offset, editor) - val dataContext = getDataContext(psiFile, editor, context) - val presentation: Presentation = action.templatePresentation.clone() - val event = AnActionEvent.createEvent(action, dataContext, presentation, ActionPlaces.ACTION_PLACE_QUICK_LIST_POPUP_ACTION, - ActionUiKind.Companion.NONE, null) - if (ActionUtil.performDumbAwareUpdate(action, event, false)) { - return false - } - return event.presentation.isEnabled && event.presentation.isVisible - } - - override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) { - val action = action ?: return - if (editor == null) return - val dataContext = DataManager.getInstance().getDataContext(editor.getComponent()) - val presentation: Presentation = action.templatePresentation.clone() - val event = AnActionEvent.createEvent(action, dataContext, presentation, ActionPlaces.ACTION_PLACE_QUICK_LIST_POPUP_ACTION, - ActionUiKind.Companion.NONE, null) - if (ActionUtil.lastUpdateAndCheckDumb(action, event, false)) { - ActionUtil.performActionDumbAwareWithCallbacks(action, event) - } - } - - /** - * Determines if the action associated with this command can be applied to the given project context - * by analyzing the specified offset and the PsiFile structure. - * It is expected to be called outside any meaningful PsiElements - */ - protected fun isApplicableToProject(offset: Int, psiFile: PsiFile): Boolean { - if (offset - 1 < 0) return true - val element = psiFile.findElementAt(offset - 1) - if (element is PsiComment) return true - val fileDocument = psiFile.fileDocument - val lineNumber = fileDocument.getLineNumber(offset) - val lineStartOffset = fileDocument.getLineStartOffset(lineNumber) - for (ch in fileDocument.immutableCharSequence.subSequence(lineStartOffset, offset)) { - if (!ch.isWhitespace()) return false - if (ch == '\n') return true - } - return true - } } \ No newline at end of file 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 19f4dd90b835..79dc7b75bd82 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 @@ -1,12 +1,28 @@ // 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.* +import com.intellij.codeInsight.completion.command.CommandCompletionProviderContext +import com.intellij.codeInsight.completion.command.CommandCompletionUnsupportedOperationException +import com.intellij.codeInsight.completion.command.CommandProvider +import com.intellij.codeInsight.completion.command.CompletionCommand +import com.intellij.codeInsight.completion.command.HighlightInfoLookup +import com.intellij.codeInsight.completion.command.MyEditor +import com.intellij.codeInsight.completion.command.commandCompletionEnabled import com.intellij.codeInsight.daemon.HighlightDisplayKey -import com.intellij.codeInsight.daemon.impl.* +import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator +import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfo.IntentionActionDescriptor +import com.intellij.codeInsight.daemon.impl.HighlightVisitorBasedInspection +import com.intellij.codeInsight.daemon.impl.IntentionActionFilter +import com.intellij.codeInsight.daemon.impl.IntentionMenuContributor +import com.intellij.codeInsight.daemon.impl.SeverityRegistrar +import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass.IntentionsInfo -import com.intellij.codeInsight.intention.* +import com.intellij.codeInsight.intention.CommonIntentionAction +import com.intellij.codeInsight.intention.EmptyIntentionAction +import com.intellij.codeInsight.intention.IntentionAction +import com.intellij.codeInsight.intention.IntentionActionDelegate +import com.intellij.codeInsight.intention.IntentionManager import com.intellij.codeInsight.intention.impl.CachedIntentions import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewComputable @@ -25,6 +41,7 @@ import com.intellij.lang.LanguageExtension import com.intellij.lang.annotation.HighlightSeverity import com.intellij.lang.annotation.HighlightSeverity.INFORMATION import com.intellij.lang.injection.InjectedLanguageManager +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.readAction import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor @@ -258,7 +275,9 @@ internal class DirectIntentionCommandProvider : CommandProvider { val isInjected = topLevelFile != psiFile val indicator = DaemonProgressIndicator() val errorHighlightings: List? = jobToIndicator(coroutineContext.job, indicator) { - HighlightVisitorBasedInspection.runAnnotatorsInGeneralHighlighting(topLevelFile, true, true, true) + ApplicationManager.getApplication().runReadAction?> { + HighlightVisitorBasedInspection.runAnnotatorsInGeneralHighlighting(topLevelFile, true, true, true) + } } if (errorHighlightings == null) return@readAction var insideRange = getLineRange(topLevelFile, topLevelOffset) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/ExtensionBasedCommandProvider.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/ExtensionBasedCommandProvider.kt deleted file mode 100644 index bf86a12704da..000000000000 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/ExtensionBasedCommandProvider.kt +++ /dev/null @@ -1,34 +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.codeInsight.completion.command.ApplicableCompletionCommand -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.commandCompletionEnabled -import com.intellij.lang.LanguageExtension -import com.intellij.openapi.project.DumbAware -import com.intellij.openapi.project.DumbService - -/** - * An implementation of the CommandProvider interface that supplies completion commands - * registered through extension points ('com.intellij.codeInsight.completion.applicable.command') for specific programming languages. - */ -@Deprecated("use providers") -internal class ExtensionPointCommandProvider : CommandProvider, DumbAware { - override fun getCommands(context: CommandCompletionProviderContext): List { - if (!commandCompletionEnabled()) return emptyList() - val completionCommands = - DumbService.getInstance(context.project) - .filterByDumbAwareness(EP_NAME.allForLanguage(context.psiFile.language)) - return completionCommands.filter { - !(context.isReadOnly && !it.supportsReadOnly()) && it.isApplicable(context.offset, context.psiFile, context.editor) - } - } - - override fun supportsReadOnly(): Boolean { - return true - } -} - -private val EP_NAME: LanguageExtension = LanguageExtension("com.intellij.codeInsight.completion.applicable.command") diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index 5572299df74c..5e96e3a20080 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -763,10 +763,6 @@ beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true"> - - - diff --git a/platform/platform-resources/src/META-INF/LangExtensions.xml b/platform/platform-resources/src/META-INF/LangExtensions.xml index 493b28a9802c..b62d821676aa 100644 --- a/platform/platform-resources/src/META-INF/LangExtensions.xml +++ b/platform/platform-resources/src/META-INF/LangExtensions.xml @@ -755,7 +755,6 @@ - diff --git a/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinDeleteCompletionCommand.kt b/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinDeleteCompletionCommand.kt index 12d3abb09fbe..cf2791dc2f63 100644 --- a/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinDeleteCompletionCommand.kt +++ b/plugins/kotlin/completion/impl-k2/src/org/jetbrains/kotlin/idea/completion/impl/k2/contributors/commands/KotlinDeleteCompletionCommand.kt @@ -2,9 +2,8 @@ package org.jetbrains.kotlin.idea.completion.impl.k2.contributors.commands import com.intellij.codeInsight.actions.ReformatCodeProcessor -import com.intellij.codeInsight.completion.command.ApplicableCompletionCommand -import com.intellij.codeInsight.completion.command.HighlightInfoLookup -import com.intellij.codeInsight.completion.command.getCommandContext +import com.intellij.codeInsight.completion.command.* +import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo import com.intellij.idea.ActionsBundle import com.intellij.openapi.command.WriteCommandAction import com.intellij.openapi.editor.Editor @@ -13,52 +12,63 @@ import com.intellij.openapi.project.DumbAware import com.intellij.psi.* import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.annotations.Nls +import org.jetbrains.kotlin.idea.KotlinFileType import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtNamedDeclaration import javax.swing.Icon -class KotlinDeleteCompletionCommand : ApplicableCompletionCommand(), DumbAware { - private var _highlightInfo: HighlightInfoLookup? = null - override val name: String - get() = "Delete element" - override val i18nName: @Nls String - get() = ActionsBundle.message("action.EditorDelete.text") - override val icon: Icon? - get() = null - override val highlightInfo: HighlightInfoLookup? - get() = _highlightInfo +internal class KotlinDeleteCompletionCommandProvider : CommandProvider { + override fun getCommands(context: CommandCompletionProviderContext): List { + val element = getCommandContext(context.offset, context.psiFile) ?: return emptyList() + var psiElement = PsiTreeUtil.getParentOfType(element, KtExpression::class.java, KtNamedDeclaration::class.java) ?: return emptyList() + val hasTheSameOffset = psiElement.textRange.endOffset == context.offset + if (!hasTheSameOffset) return emptyList() + psiElement = getTopWithTheSameOffset(psiElement, context.offset) + val highlightInfo = HighlightInfoLookup(psiElement.textRange, EditorColors.SEARCH_RESULT_ATTRIBUTES, 0) + return listOf(createCommand(highlightInfo, psiElement)) + } - override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean { - val element = getCommandContext(offset, psiFile) ?: return false - var psiElement = PsiTreeUtil.getParentOfType(element, KtExpression::class.java, KtNamedDeclaration::class.java) ?: return false - val hasTheSameOffset = psiElement.textRange.endOffset == offset - if (!hasTheSameOffset) return false - psiElement = getTopWithTheSameOffset(psiElement, offset) - _highlightInfo = HighlightInfoLookup(psiElement.textRange, EditorColors.DELETED_TEXT_ATTRIBUTES, 0) - return true - } + private fun createCommand(highlightInfo: HighlightInfoLookup, psiElement: PsiElement): CompletionCommand { + return KotlinDeleteCompletionCommand(highlightInfo, psiElement.text) + } +} - override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) { - val element = getCommandContext(offset, psiFile) ?: return - var psiElement = PsiTreeUtil.getParentOfType(element, KtExpression::class.java, KtNamedDeclaration::class.java) ?: return - psiElement = getTopWithTheSameOffset(psiElement, offset) - WriteCommandAction.runWriteCommandAction(psiFile.project, null, null, { - val parent: SmartPsiElementPointer = SmartPointerManager.createPointer(psiElement.parent ?: psiFile) - psiElement.delete() - PsiDocumentManager.getInstance(psiFile.project).commitDocument(psiFile.fileDocument) - parent.element?.let { - ReformatCodeProcessor(psiFile, arrayOf(it.textRange)).run() - } - }, psiFile) +private fun getTopWithTheSameOffset(psiElement: KtExpression, offset: Int): KtExpression { + var psiElement1 = psiElement + var curElement = psiElement1 + while (curElement.textRange.endOffset == offset) { + psiElement1 = curElement + curElement = PsiTreeUtil.getParentOfType(curElement, KtExpression::class.java, KtNamedDeclaration::class.java) ?: break } + return psiElement1 +} - private fun getTopWithTheSameOffset(psiElement: KtExpression, offset: Int): KtExpression { - var psiElement1 = psiElement - var curElement = psiElement1 - while (curElement.textRange.endOffset == offset) { - psiElement1 = curElement - curElement = PsiTreeUtil.getParentOfType(curElement, KtExpression::class.java, KtNamedDeclaration::class.java) ?: break - } - return psiElement1 - } +private class KotlinDeleteCompletionCommand( + override val highlightInfo: HighlightInfoLookup?, + private val originalText: String, +) : CompletionCommand(), CompletionCommandWithPreview, DumbAware { + override val name: String + get() = "Delete element" + override val i18nName: @Nls String + get() = ActionsBundle.message("action.EditorDelete.text") + override val icon: Icon? + get() = null + + override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) { + val element = getCommandContext(offset, psiFile) ?: return + var psiElement = PsiTreeUtil.getParentOfType(element, KtExpression::class.java, KtNamedDeclaration::class.java) ?: return + psiElement = getTopWithTheSameOffset(psiElement, offset) + WriteCommandAction.runWriteCommandAction(psiFile.project, null, null, { + val parent: SmartPsiElementPointer = SmartPointerManager.createPointer(psiElement.parent ?: psiFile) + psiElement.delete() + PsiDocumentManager.getInstance(psiFile.project).commitDocument(psiFile.fileDocument) + parent.element?.let { + ReformatCodeProcessor(psiFile, arrayOf(it.textRange)).run() + } + }, psiFile) + } + + override fun getPreview(): IntentionPreviewInfo? { + return IntentionPreviewInfo.CustomDiff(KotlinFileType.INSTANCE, originalText, "") + } } \ 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 eb7d6736ec7d..2268cd78ae65 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 @@ -27,8 +27,8 @@ implementationClass="com.intellij.codeInsight.completion.command.commands.PsiElementCommentByLineCompletionCommandProvider"/> - +