From e78808cc18453eb7182dac56528ac5928afacea7 Mon Sep 17 00:00:00 2001 From: Mikhail Pyltsin Date: Fri, 11 Apr 2025 15:21:40 +0200 Subject: [PATCH] [command-completion] IDEA-370147 Reimplement `AI action` for command completion GitOrigin-RevId: b9a19b9f6b598af6e08745c72f5871fef033b17c --- platform/lang-impl/api-dump-experimental.txt | 5 ++ .../DirectIntentionCommandProvider.kt | 76 +++++++------------ .../src/META-INF/LangExtensionPoints.xml | 1 + 3 files changed, 33 insertions(+), 49 deletions(-) diff --git a/platform/lang-impl/api-dump-experimental.txt b/platform/lang-impl/api-dump-experimental.txt index 6fb7da776ebc..8c5c47611643 100644 --- a/platform/lang-impl/api-dump-experimental.txt +++ b/platform/lang-impl/api-dump-experimental.txt @@ -286,6 +286,11 @@ c:com.intellij.codeInsight.actions.VcsFacade - getPriority():java.lang.Integer - getSynonyms():java.util.List - f:setActionId(java.lang.String):V +*: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.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/commands/DirectIntentionCommandProvider.kt b/platform/lang-impl/src/com/intellij/codeInsight/completion/command/commands/DirectIntentionCommandProvider.kt index d1452040a8b6..24f2bee3059d 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 @@ -34,15 +34,14 @@ import com.intellij.openapi.diagnostic.thisLogger import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.colors.CodeInsightColors -import com.intellij.openapi.editor.ex.MarkupModelEx -import com.intellij.openapi.editor.impl.DocumentMarkupModel -import com.intellij.openapi.editor.markup.HighlighterTargetArea +import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.progress.EmptyProgressIndicator import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.jobToIndicator import com.intellij.openapi.progress.runBlockingCancellable import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.IndexNotReadyException +import com.intellij.openapi.project.PossiblyDumbAware import com.intellij.openapi.project.ProjectTypeService import com.intellij.openapi.util.Iconable import com.intellij.openapi.util.Iconable.ICON_FLAG_VISIBILITY @@ -51,7 +50,6 @@ import com.intellij.openapi.util.ProperTextRange import com.intellij.openapi.util.TextRange import com.intellij.profile.codeInspection.ProjectInspectionProfileManager.Companion.getInstance import com.intellij.psi.PsiFile -import com.intellij.psi.PsiFileFactory import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil import com.intellij.psi.util.PsiModificationTracker import com.intellij.psi.util.PsiTreeUtil @@ -316,7 +314,6 @@ internal class DirectIntentionCommandProvider : CommandProvider { TextRange(injectedLanguageManager.injectedToHost(psiFile, 0), injectedLanguageManager.injectedToHost(psiFile, psiFile.fileDocument.textLength))) } - var attemptToAddAi = false val lazyQuickFixUpdater = LazyQuickFixUpdater.getInstance(psiFile.project) for (info: HighlightInfo? in errorHighlightings) { if (info == null) continue @@ -326,20 +323,6 @@ internal class DirectIntentionCommandProvider : CommandProvider { info.updateLazyFixesPsiTimeStamp(PsiModificationTracker.getInstance(psiFile.project).modificationCount) lazyQuickFixUpdater.waitQuickFixesSynchronously(psiFile, editor, info) } - if (!attemptToAddAi && info.severity == HighlightSeverity.ERROR) { - val intentionsInfo: IntentionsInfo? = tryToAddAI(info, psiFile, editor, offset) - if (intentionsInfo != null) { - val intentionsCache = CachedIntentions(psiFile.project, psiFile, editor) - intentionsCache.wrapAndUpdateActions(intentionsInfo, false) - for (descriptor in intentionsCache.errorFixes) { - val command = IntentionCompletionCommand(descriptor, 50, AllIcons.Actions.QuickfixBulb, - HighlightInfoLookup(TextRange(info.startOffset, info.endOffset), - CodeInsightColors.ERRORS_ATTRIBUTES, 100), offset) { null } - result.add(command) - } - } - attemptToAddAi = true - } val fixes: MutableList = ArrayList() ShowIntentionsPass.addAvailableFixesForGroups(info, topLevelEditor, topLevelFile, fixes, -1, offset, false) for (descriptor in fixes) { @@ -362,6 +345,7 @@ internal class DirectIntentionCommandProvider : CommandProvider { result.add(command) } } + result.addAll(errorFixesWithHighlighting(psiFile, errorHighlightings, offset)) } catch (e: Exception) { if (e is ControlFlowException || e is CancellationException) { @@ -374,34 +358,15 @@ internal class DirectIntentionCommandProvider : CommandProvider { } } - /** - * temporary workaround for fix with AI - */ - private fun tryToAddAI(info: HighlightInfo, psiFile: PsiFile, editor: Editor, offset: Int): IntentionsInfo? { - val copyFile = PsiFileFactory.getInstance(editor.project) - .createFileFromText(psiFile.getName(), psiFile.getLanguage(), psiFile.fileDocument.text, true, true, false, psiFile.virtualFile) - val markup = DocumentMarkupModel.forDocument(copyFile.fileDocument, psiFile.project, true) as? MarkupModelEx ?: return null - val textAttributesKey = if (info.forcedTextAttributesKey == null) info.type.getAttributesKey() else info.forcedTextAttributesKey - val highlighter = markup.addRangeHighlighter(textAttributesKey, info.startOffset, info.endOffset, 5000, HighlighterTargetArea.EXACT_RANGE) - highlighter.errorStripeTooltip = info - val dumbService = DumbService.getInstance(psiFile.project) - val intentions = IntentionsInfo() - val copyEditor = MyEditor(copyFile, editor.settings) - copyEditor.caretModel.moveToOffset(offset) - for (extension in IntentionMenuContributor.EP_NAME.extensionList) { - if (extension.javaClass.name != "com.intellij.ml.llm.inlinePromptDetector.fixWithAi.FixWithAiIntentionMenuContributor") continue - ProgressManager.checkCanceled() - try { - if (dumbService.isUsableInCurrentContext(extension)) { - extension.collectActions(copyEditor, copyFile, intentions, -1, offset) - } - } - catch (_: IntentionPreviewUnsupportedOperationException) { - //can collect action on a mock memory editor and produce exceptions - ignore - } + private fun errorFixesWithHighlighting(psiFile: PsiFile, errorHighlightings: List, offset: Int): Collection { + val project = psiFile.project + val dumbService = DumbService.getInstance(project) + val result = mutableListOf() + val highlightInfos = errorHighlightings.filterNotNull() + for (provider in dumbService.filterByDumbAwareness(ErrorFixCommandProvider.EP_NAME.extensionList)) { + result.addAll(provider.getCommands(psiFile, highlightInfos, offset)) } - - return intentions + return result } private fun CoroutineScope.asyncIntentions( @@ -448,9 +413,7 @@ internal class DirectIntentionCommandProvider : CommandProvider { try { ProgressManager.checkCanceled() if (!dumbService.isUsableInCurrentContext(intention) || - !intention.action.isAvailable(originalFile.project, editor, psiFile) && - //todo temporary workaround for AI - intention.action.familyName !in ("AI Actions…") || + !intention.action.isAvailable(originalFile.project, editor, psiFile) || !filter.test(intention.action)) { toRemove.add(intention) } @@ -532,6 +495,21 @@ private fun getInspectionTools(profile: InspectionProfileWrapper, file: PsiFile) return enabled } + +/** + * This class allows language plugins to contribute specialized error fix commands to IntelliJ's completion system. + * These commands are displayed when errors are detected in the code and provide context-specific fixes. + * + */ +interface ErrorFixCommandProvider : PossiblyDumbAware { + + fun getCommands(psiFile: PsiFile, errorHighlightings: List, offset: Int): List + + companion object { + internal val EP_NAME: ExtensionPointName = ExtensionPointName.create("com.intellij.codeInsight.completion.error.intention") + } +} + interface IntentionCommandSkipper { companion object { internal val EP_NAME: LanguageExtension = LanguageExtension("com.intellij.codeInsight.completion.intention.skipper") diff --git a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml index 88db2a7507dd..3f02f3a55b96 100644 --- a/platform/platform-resources/src/META-INF/LangExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/LangExtensionPoints.xml @@ -767,6 +767,7 @@ beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true"> +