[command-completion] IDEA-359174 first draft of command completion

- move to platform part

GitOrigin-RevId: 9dde441fccb6ea470faa08a8df8bad75f60fcad4
This commit is contained in:
Mikhail Pyltsin
2025-02-11 09:31:20 +00:00
committed by intellij-monorepo-bot
parent 59bea5195f
commit 6150820425
59 changed files with 1423 additions and 687 deletions
@@ -292,20 +292,6 @@
<extensionPoint qualifiedName="com.intellij.gotoByName.defaultProvider.ignoreLanguage" dynamic="true"
beanClass="com.intellij.ide.util.gotoByName.IgnoreLanguageInDefaultProvider"/>
<!--todo move to main-->
<extensionPoint qualifiedName="com.intellij.codeInsight.completion.command.factory"
beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
<with attribute="implementationClass" implements="com.intellij.codeInsight.completion.commands.CommandCompletionFactory"/>
</extensionPoint>
<extensionPoint qualifiedName="com.intellij.codeInsight.completion.command"
dynamic="true"
interface="com.intellij.codeInsight.completion.commands.api.CompletionCommand" />
<extensionPoint qualifiedName="com.intellij.codeInsight.completion.command.provider"
dynamic="true"
interface="com.intellij.codeInsight.completion.commands.api.CommandProvider" />
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
@@ -1503,29 +1489,29 @@
implementationClass="com.intellij.codeInsight.completion.JvmLoggerCompletionContributor"/>
<completion.contributor language="JAVA" id="jigsaw"
implementationClass="com.intellij.codeInsight.completion.JigsawCompletionContributor"/>
<nonWriteAccessTypedHandler implementation="com.intellij.codeInsight.completion.commands.core.CommandCompletionNonWriteAccessTypedHandler" id="commandCompletion"/>
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.InlineVariableCompletionCommand" />
<codeInsight.completion.command.provider implementation="com.intellij.codeInsight.completion.commands.impl.ExtensionPointCommandProvider" />
<codeInsight.completion.command.provider implementation="com.intellij.codeInsight.completion.commands.impl.DirectIntentionCommandProvider" order="last"/>
<codeInsight.completion.command.provider implementation="com.intellij.codeInsight.completion.commands.impl.GenerateCommandProvider" />
<codeInsight.completion.command.provider implementation="com.intellij.codeInsight.completion.commands.impl.SimpleRunMarkerCommandProvider" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.GoToDeclarationCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.RenameActionCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.OptimizeImportCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.FormatCodeCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.RecentFilesCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.ProjectViewCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.LineCommentCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.JavaPsiElementCommentCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.IntroduceVariableCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.ShowUsagesActionCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.GenerateGetterSetterHandleCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.GenerateSetterHandleCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.GenerateGetterHandleCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.DeleteCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.CopyFQNCompletionCommand" />
<codeInsight.completion.command implementation="com.intellij.codeInsight.completion.commands.impl.GoToImplementationCompletionCommand" />
<codeInsight.completion.command.factory language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.JavaCommandCompletionFactory" />
<codeInsight.completion.intention.skipper language="JAVA"
implementationClass="com.intellij.codeInsight.completion.commands.JavaCommandCompletionFactory$JavaIntentionCommandSkipper"/>
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.command.commands.SimpleRunMarkerCommandProvider" />
<codeInsight.completion.command.provider language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaGenerateCommandCompletion" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaInlineVariableCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaGoToDeclarationCommandCompletion" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.command.commands.RenameActionCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.command.commands.OptimizeImportCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaFormatCodeCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.command.commands.RecentFilesCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.command.commands.ProjectViewCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.command.commands.LineCommentCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.command.commands.PsiElementCommentCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaIntroduceVariableCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaShowUsagesActionCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.GenerateGetterSetterHandleCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.GenerateSetterHandleCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.GenerateGetterHandleCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaDeleteCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaCopyFQNCompletionCommand" />
<codeInsight.completion.applicable.command language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaGoToImplementationCommandCompletion" />
<weigher implementationClass="com.intellij.codeInsight.completion.LoggerWeigher" key="completion" id="logger"/>
@@ -2790,17 +2776,6 @@
<trigramIndexFilterExcludeExtension implementation="com.intellij.java.impl.find.ngrams.JavaTrigramFilterExcludeExtension"/>
<codeInsight.completion.command.factory language="JAVA" implementationClass="com.intellij.codeInsight.completion.commands.impl.JavaCommandCompletionFactory" />
<completion.contributor language="JAVA" id="commands" order="first, after liveTemplates"
implementationClass="com.intellij.codeInsight.completion.commands.core.CommandCompletionContributor"/>
<completion.contributor language="TEXT" id="commandsPlain" order="first, after liveTemplates"
implementationClass="com.intellij.codeInsight.completion.commands.core.CommandCompletionContributor"/>
<lookup.charFilter implementation="com.intellij.codeInsight.completion.commands.core.CommandCompletionCharFilter" order="first" id="commandCompletion"/>
<lookup.customizer implementation="com.intellij.codeInsight.completion.commands.core.CommandCompletionLookupCustomizer" order="last" id="commandCompletionLookupCustomizer"/>
<registryKey key="java.completion.command.enabled" defaultValue="false"
description="Enable experimental support command completion" restartRequired="true"/>
<registryKey key="java.completion.argument.live.template" defaultValue="false"
description="When completing a method call, start a live template with all arguments"/>
<registryKey key="java.completion.argument.live.template.completion" defaultValue="false"
@@ -2879,7 +2854,5 @@
topic="com.intellij.refactoring.listeners.RefactoringEventListener"/>
<listener class="com.intellij.openapi.roots.impl.LanguageLevelChangedListener"
topic="com.intellij.platform.backend.workspace.WorkspaceModelChangeListener"/>
<listener class="com.intellij.codeInsight.completion.commands.core.CommandCompletionListener"
topic="com.intellij.codeInsight.lookup.LookupManagerListener"/>
</projectListeners>
</idea-plugin>
@@ -0,0 +1,46 @@
// 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;
import com.intellij.codeInsight.completion.command.CommandCompletionFactory;
import com.intellij.codeInsight.completion.command.commands.IntentionCommandSkipper;
import com.intellij.codeInsight.daemon.impl.quickfix.CreateGetterOrSetterFix;
import com.intellij.codeInsight.daemon.impl.quickfix.ExpensivePsiIntentionAction;
import com.intellij.codeInsight.intention.CommonIntentionAction;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ex.QuickFixWrapper;
import com.intellij.modcommand.ModCommandAction;
import com.intellij.modcommand.ModCommandService;
import com.intellij.openapi.project.DumbAware;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
public class JavaCommandCompletionFactory implements CommandCompletionFactory, DumbAware {
@Override
public boolean isApplicable(@NotNull PsiFile psiFile, int offset) {
//todo skip varargs!
if (!(psiFile instanceof PsiJavaFile)) return false;
PsiElement elementAt = psiFile.findElementAt(offset);
if (elementAt == null) return true;
if (!(elementAt.getParent() instanceof PsiParameterList)) return true;
PsiElement prevLeaf = PsiTreeUtil.prevLeaf(elementAt, true);
if (!(prevLeaf instanceof PsiJavaToken javaToken && javaToken.textMatches("."))) return true;
PsiElement prevPrevLeaf = PsiTreeUtil.prevLeaf(prevLeaf, true);
if (PsiTreeUtil.getParentOfType(prevPrevLeaf, PsiTypeElement.class) != null) return false;
return true;
}
public static class JavaIntentionCommandSkipper implements IntentionCommandSkipper {
@Override
public boolean skip(@NotNull CommonIntentionAction action, @NotNull PsiFile psiFile, int offset) {
if (action instanceof ExpensivePsiIntentionAction) return true;
LocalQuickFix fix = QuickFixWrapper.unwrap(action);
if (fix != null) {
ModCommandAction unwrappedAction = ModCommandService.getInstance().unwrap(fix);
if (unwrappedAction instanceof CreateGetterOrSetterFix) return true;
}
return IntentionCommandSkipper.super.skip(action, psiFile, offset);
}
}
}
@@ -1,44 +0,0 @@
// 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.api
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
/**
* Factory interface for creating and managing completion commands in a specific context.
*
* Provides functionality to determine applicability based on the file and offset,
* retrieve a list of command providers, and define suffixes used during code completion.
* Serves as a customization point for enhancing code completion workflows.
*
* Should implement DumbAware to support dumb mode
*/
@ApiStatus.Experimental
interface CommandCompletionFactory {
/**
* Provides the default character suffix. After that, suffix command completion will be enabled
*
* @return The character suffix, which is '.'.
*/
fun suffix(): Char = '.'
/**
* Determines the character suffix to filter only command lookup
*/
fun filterSuffix(): Char? = '.'
/**
* Retrieves a list of command providers responsible for supplying completion commands.
*/
fun commandProviders(project: Project): List<CommandProvider>
/**
* Determines whether the functionality is applicable in the given context.
*
* @param psiFile the PSI file representing the file in which the applicability is being evaluated
* @param offset the position within the file where the applicability should be checked
* @return true if the functionality is applicable at the specified context, false otherwise
*/
fun isApplicable(psiFile: PsiFile, offset: Int): Boolean
}
@@ -1,35 +0,0 @@
// 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.core
import com.intellij.codeInsight.lookup.AutoCompletionPolicy
import com.intellij.codeInsight.lookup.AutoCompletionPolicy.NEVER_AUTOCOMPLETE
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.openapi.util.TextRange
import org.jetbrains.annotations.ApiStatus
import javax.swing.Icon
@ApiStatus.Experimental
class CommandCompletionLookupElement(
lookupElement: LookupElement,
val hostStartOffset: Int,
val suffix: String,
val icon: Icon?,
val highlighting: HighlightInfoLookup?,
) : LookupElementDecorator<LookupElement>(lookupElement) {
override fun isWorthShowingInAutoPopup(): Boolean {
return true
}
override fun getAutoCompletionPolicy(): AutoCompletionPolicy? {
return NEVER_AUTOCOMPLETE
}
}
@ApiStatus.Experimental
data class HighlightInfoLookup(
val range: TextRange,
val attributesKey: TextAttributesKey,
val priority: Int, //higher is on the top
)
@@ -1,44 +0,0 @@
// 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.ide.IdeBundle
import com.intellij.ide.actions.FqnUtil
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
class CopyFQNCompletionCommand : AbstractActionCompletionCommand("CopyReference",
"Copy Reference",
IdeBundle.message("copy.reference"),
null,
-150) {
override fun supportsReadOnly(): Boolean = true
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
if (!super.isApplicable(offset, psiFile, editor)) return false
val element = getContext(offset, psiFile)
val member = PsiTreeUtil.getParentOfType(element, PsiMember::class.java) ?: return false
if (member is PsiClass) {
if (!TextRange(member.textRange.startOffset, member.lBrace?.textRange?.startOffset ?: member.textRange.endOffset)
.contains(offset)
) {
return false
}
}
else if (member is PsiMethod) {
if (!TextRange(member.textRange.startOffset, member.body?.textRange?.startOffset ?: member.textRange.endOffset)
.contains(offset)
) {
return false
}
}
else {
return false
}
return FqnUtil.elementToFqn(element, editor) != null
}
}
@@ -1,27 +0,0 @@
// 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.commands.api.ApplicableCompletionCommand
import com.intellij.codeInsight.completion.commands.api.CommandCompletionProviderContext
import com.intellij.codeInsight.completion.commands.api.CommandProvider
import com.intellij.codeInsight.completion.commands.api.CompletionCommand
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.DumbService
class ExtensionPointCommandProvider : CommandProvider, DumbAware {
override fun getCommands(context: CommandCompletionProviderContext): List<CompletionCommand> {
val completionCommands = DumbService.getDumbAwareExtensions(context.project, ApplicableCompletionCommand.EP_NAME)
return completionCommands.filter {
!(context.isReadOnly && !it.supportsReadOnly()) && it.isApplicable(context.offset, context.psiFile, context.editor)
}
}
override fun getId(): String {
return "ExtensionPointCommandProvider"
}
override fun supportsReadOnly(): Boolean {
return true
}
}
@@ -1,38 +0,0 @@
// 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.actions.ReformatCodeProcessor
import com.intellij.codeInsight.completion.commands.api.ApplicableCompletionCommand
import com.intellij.icons.AllIcons
import com.intellij.idea.ActionsBundle
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiCodeBlock
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiStatement
import com.intellij.psi.util.parents
import org.jetbrains.annotations.Nls
import javax.swing.Icon
class FormatCodeCompletionCommand : ApplicableCompletionCommand() {
override val name: String
get() = "format"
override val i18nName: @Nls String
get() = ActionsBundle.message("action.ReformatCode.text")
override val icon: Icon
get() = AllIcons.Actions.ReformatCode // Use the reformat icon
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
if (InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)) return false
return true
}
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
val element = getContext(offset, psiFile) ?: return
val target = element.parents(true).first { it is PsiMember || it is PsiCodeBlock || it is PsiStatement }
ReformatCodeProcessor(element.containingFile, arrayOf(target.textRange)).run()
}
}
@@ -1,64 +0,0 @@
// 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.commands.api.*
import com.intellij.codeInsight.generation.actions.BaseGenerateAction
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.editor.Document
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.DumbService
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiIdentifier
import com.intellij.util.containers.JBIterable.from
class GenerateCommandProvider : CommandProvider, DumbAware {
override fun getCommands(context: CommandCompletionProviderContext): List<CompletionCommand> {
val psiFile = context.psiFile
val project = context.project
val offset = context.offset
val editor = context.editor
if (InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)) return emptyList()
val element = psiFile.findElementAt(if (offset - 1 >= 0) offset - 1 else offset)
val parent = element?.parent
//skip, probably extension to skip?
if (element == null || parent !is PsiClass) return emptyList()
if (!isOnlySpaceInLine(psiFile.fileDocument, offset) &&
!(element is PsiIdentifier && parent.nameIdentifier == element)) return emptyList()
val context = getTargetContext(offset, editor)
val dataContext = getDataContext(psiFile, editor, context)
val actionEvent = AnActionEvent.createEvent(dataContext, null, ActionPlaces.UNKNOWN, ActionUiKind.NONE, null)
actionEvent.updateSession = UpdateSession.EMPTY
Utils.initUpdateSession(actionEvent)
val generateActions: MutableList<BaseGenerateAction> = ArrayList()
val session = actionEvent.updateSession
val dumbService = DumbService.getInstance(project)
val activeActions = from(session.expandedChildren(ActionManager.getInstance().getAction(IdeActions.GROUP_GENERATE) as ActionGroup))
.filter { dumbService.isUsableInCurrentContext(it) }
.filter { o: AnAction? -> o !is Separator && o != null && session.presentation(o).isEnabledAndVisible }
for (action in activeActions) {
if (action is BaseGenerateAction) {
generateActions.add(action)
}
}
return generateActions.map { GenerateCompletionCommand(it) }
}
private fun isOnlySpaceInLine(document: Document, offset: Int): Boolean {
val lineNumber = document.getLineNumber(offset)
val lineStartOffset = document.getLineStartOffset(lineNumber)
val immutableCharSequence = document.immutableCharSequence
for (i in lineStartOffset..offset) {
if (!immutableCharSequence[i].isWhitespace()) {
return false
}
}
return true
}
override fun getId(): String {
return "GenerateCommandProvider"
}
}
@@ -1,38 +0,0 @@
// 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.commands.api.CompletionCommand
import com.intellij.codeInsight.generation.actions.BaseGenerateAction
import com.intellij.ide.DataManager
import com.intellij.java.JavaBundle
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.ActionUiKind
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.Presentation
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.Nls
import javax.swing.Icon
class GenerateCompletionCommand(private val action: BaseGenerateAction) : CompletionCommand() {
override val name: String
get() = "Generate \'" + action.templateText + "\'"
override val i18nName: @Nls String
get() = JavaBundle.message(
"command.completion.generate.text",
action.templateText)
override val icon: Icon?
get() = null
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
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.UNKNOWN, ActionUiKind.NONE, null)
if (ActionUtil.lastUpdateAndCheckDumb(action, event, false)) {
ActionUtil.performActionDumbAwareWithCallbacks(action, event)
}
}
}
@@ -1,35 +0,0 @@
// 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.idea.ActionsBundle
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiIdentifier
import com.intellij.psi.PsiJavaCodeReferenceElement
import com.intellij.psi.util.PsiTreeUtil
class GoToDeclarationCompletionCommand : AbstractActionCompletionCommand("GotoDeclarationOnly",
"Go to Declaration",
ActionsBundle.message("action.GotoDeclarationOnly.text"),
null) {
override fun supportsReadOnly(): Boolean = true
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
return super.isApplicable(offset, psiFile, editor) && hasToShow(offset, psiFile)
}
private fun hasToShow(offset: Int, psiFile: PsiFile): Boolean {
val context = (getContext(offset, psiFile)) ?: return false
return canNavigateToDeclaration(context)
}
private fun canNavigateToDeclaration(context: PsiElement): Boolean {
if (context !is PsiIdentifier) {
return false
}
val javaRef = PsiTreeUtil.getParentOfType(context, PsiJavaCodeReferenceElement::class.java)
val psiElement = javaRef?.resolve()
return psiElement != null
}
}
@@ -1,7 +1,7 @@
// 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.commands.api.ApplicableCompletionCommand
import com.intellij.codeInsight.completion.command.ApplicableCompletionCommand
import com.intellij.codeInsight.daemon.QuickFixBundle
import com.intellij.codeInsight.intention.QuickFixFactory
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
@@ -15,15 +15,15 @@ import javax.swing.Icon
class GenerateGetterSetterHandleCompletionCommand :
BaseGenerateGetterSetterHandleCompletionCommand(true, true, "Create Getter/Setter",
BaseGenerateGetterSetterHandleCompletionCommand(true, true, "Generate 'Getter/Setter'",
QuickFixBundle.message("create.getter.setter"))
class GenerateSetterHandleCompletionCommand :
BaseGenerateGetterSetterHandleCompletionCommand(false, true, "Create Setter",
BaseGenerateGetterSetterHandleCompletionCommand(false, true, "Generate 'Setter'",
QuickFixBundle.message("create.setter"))
class GenerateGetterHandleCompletionCommand :
BaseGenerateGetterSetterHandleCompletionCommand(true, false, "Create Getter",
BaseGenerateGetterSetterHandleCompletionCommand(true, false, "Generate 'Getter'",
QuickFixBundle.message("create.getter"))
abstract class BaseGenerateGetterSetterHandleCompletionCommand(
@@ -1,29 +0,0 @@
// 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.commands.api.CommandCompletionFactory;
import com.intellij.codeInsight.completion.commands.api.CommandProvider;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiJavaFile;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class JavaCommandCompletionFactory implements CommandCompletionFactory, DumbAware {
@Override
public @NotNull List<@NotNull CommandProvider> commandProviders(@NotNull Project project) {
if (!Registry.is("java.completion.command.enabled")) return List.of();
return DumbService.getDumbAwareExtensions(project, CommandProvider.Companion.getEP_NAME());
}
@Override
public boolean isApplicable(@NotNull PsiFile psiFile, int offset) {
return psiFile instanceof PsiJavaFile;
}
}
@@ -1,24 +1,18 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.idea.ActionsBundle
import com.intellij.openapi.editor.Editor
import com.intellij.codeInsight.completion.command.commands.AbstractCopyFQNCompletionCommand
import com.intellij.ide.actions.FqnUtil
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
class GoToImplementationCompletionCommand: AbstractActionCompletionCommand("GotoImplementation",
"Go to Implementation",
ActionsBundle.message("action.GotoImplementation.text"),
null) {
override fun supportsReadOnly(): Boolean = true
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
if (!super.isApplicable(offset, psiFile, editor)) return false
val element = getContext(offset, psiFile)
class JavaCopyFQNCompletionCommand : AbstractCopyFQNCompletionCommand() {
override fun placeIsApplicable(element: PsiElement, offset: Int): Boolean {
if (FqnUtil.elementToFqn(element, null) == null) return false
val member = PsiTreeUtil.getParentOfType(element, PsiMember::class.java) ?: return false
if (member is PsiClass) {
if (!TextRange(member.textRange.startOffset, member.lBrace?.textRange?.startOffset ?: member.textRange.endOffset)
@@ -2,7 +2,7 @@
package com.intellij.codeInsight.completion.commands.impl
import com.intellij.codeInsight.actions.ReformatCodeProcessor
import com.intellij.codeInsight.completion.commands.api.ApplicableCompletionCommand
import com.intellij.codeInsight.completion.command.ApplicableCompletionCommand
import com.intellij.idea.ActionsBundle
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
@@ -12,7 +12,7 @@ import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.annotations.Nls
import javax.swing.Icon
class DeleteCompletionCommand : ApplicableCompletionCommand(), DumbAware {
class JavaDeleteCompletionCommand : ApplicableCompletionCommand(), DumbAware {
override val name: String
get() = "Delete"
override val i18nName: @Nls String
@@ -0,0 +1,15 @@
// 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.commands.AbstractFormatCodeCompletionCommand
import com.intellij.psi.PsiCodeBlock
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiStatement
import com.intellij.psi.util.parents
class JavaFormatCodeCompletionCommand : AbstractFormatCodeCompletionCommand() {
override fun findTargetToRefactor(element: PsiElement): PsiElement {
return element.parents(true).first { it is PsiMember || it is PsiCodeBlock || it is PsiStatement }
}
}
@@ -0,0 +1,17 @@
// 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.commands.AbstractGenerateCommandProvider
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiIdentifier
class JavaGenerateCommandCompletion : AbstractGenerateCommandProvider() {
override fun generationIsAvailable(element: PsiElement, offset: Int): Boolean {
val parent = element.parent
if (parent !is PsiClass) return false
if (!isOnlySpaceInLine(element.containingFile.fileDocument, offset) &&
!(element is PsiIdentifier && parent.nameIdentifier == element)) return false
return true
}
}
@@ -0,0 +1,19 @@
// 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.commands.AbstractGoToDeclarationCompletionCommand
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiIdentifier
import com.intellij.psi.PsiJavaCodeReferenceElement
import com.intellij.psi.util.PsiTreeUtil
class JavaGoToDeclarationCommandCompletion : AbstractGoToDeclarationCompletionCommand() {
override fun canNavigateToDeclaration(context: PsiElement): Boolean {
if (context !is PsiIdentifier) {
return false
}
val javaRef = PsiTreeUtil.getParentOfType(context, PsiJavaCodeReferenceElement::class.java)
val psiElement = javaRef?.resolve()
return psiElement != null
}
}
@@ -0,0 +1,35 @@
// 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.commands.AbstractGoToImplementationCompletionCommand
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMember
import com.intellij.psi.PsiMethod
import com.intellij.psi.util.PsiTreeUtil
class JavaGoToImplementationCommandCompletion : AbstractGoToImplementationCompletionCommand() {
override fun canGoToImplementation(element: PsiElement, offset: Int): Boolean {
val member = PsiTreeUtil.getParentOfType(element, PsiMember::class.java) ?: return false
if (member is PsiClass) {
if (!TextRange(member.textRange.startOffset, member.lBrace?.textRange?.startOffset ?: member.textRange.endOffset)
.contains(offset)
) {
return false
}
}
else if (member is PsiMethod) {
if (!TextRange(member.textRange.startOffset, member.body?.textRange?.startOffset ?: member.textRange.endOffset)
.contains(offset)
) {
return false
}
}
else {
return false
}
return true
}
}
@@ -1,7 +1,7 @@
// 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.commands.api.ApplicableCompletionCommand
import com.intellij.codeInsight.completion.command.ApplicableCompletionCommand
import com.intellij.icons.AllIcons
import com.intellij.java.JavaBundle
import com.intellij.lang.refactoring.InlineActionHandler
@@ -13,9 +13,9 @@ import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.annotations.Nls
import javax.swing.Icon
class InlineVariableCompletionCommand : ApplicableCompletionCommand() {
class JavaInlineVariableCompletionCommand : ApplicableCompletionCommand() {
override val name: String
get() = "inline"
get() = "Inline"
override val i18nName: @Nls String
get() = JavaBundle.message("command.completion.inline.text")
@@ -1,9 +1,9 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// 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.commands.api.ApplicableCompletionCommand
import com.intellij.codeInsight.completion.commands.api.getDataContext
import com.intellij.codeInsight.completion.commands.api.getTargetContext
import com.intellij.codeInsight.completion.command.ApplicableCompletionCommand
import com.intellij.codeInsight.completion.command.getDataContext
import com.intellij.codeInsight.completion.command.getTargetContext
import com.intellij.icons.AllIcons
import com.intellij.ide.DataManager
import com.intellij.lang.ContextAwareActionHandler
@@ -20,7 +20,7 @@ import com.intellij.refactoring.actions.IntroduceVariableAction
import org.jetbrains.annotations.Nls
import javax.swing.Icon
class IntroduceVariableCommand : ApplicableCompletionCommand() {
class JavaIntroduceVariableCommand : ApplicableCompletionCommand() {
override val name: String
get() = "Introduce variable"
@@ -45,7 +45,7 @@ class IntroduceVariableCommand : ApplicableCompletionCommand() {
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.NONE, null)
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)
@@ -0,0 +1,15 @@
// 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.commands.AbstractShowUsagesActionCompletionCommand
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
class JavaShowUsagesActionCompletionCommand : AbstractShowUsagesActionCompletionCommand() {
override fun hasToShow(element: PsiElement): Boolean {
val namedIdentifierParent = PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner::class.java)
return namedIdentifierParent?.nameIdentifier == element &&
(namedIdentifierParent is PsiVariable || namedIdentifierParent is PsiMethod || namedIdentifierParent is PsiMember)
}
}
@@ -1,28 +0,0 @@
// 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.find.actions.ShowUsagesAction
import com.intellij.idea.ActionsBundle
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.openapi.editor.Editor
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
class ShowUsagesActionCompletionCommand : AbstractActionCompletionCommand(ShowUsagesAction.ID,
"Show usages",
ActionsBundle.message("action.ShowUsages.text"),
null) {
override fun supportsReadOnly(): Boolean = true
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
return super.isApplicable(offset, psiFile, editor) && hasToShow(getContext(offset, psiFile)) &&
!InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)
}
private fun hasToShow(element: PsiElement?): Boolean {
if (element == null) return false
val namedIdentifierParent = PsiTreeUtil.getParentOfType(element, PsiNameIdentifierOwner::class.java)
return namedIdentifierParent?.nameIdentifier == element &&
(namedIdentifierParent is PsiVariable || namedIdentifierParent is PsiMethod || namedIdentifierParent is PsiMember)
}
}
@@ -5,6 +5,7 @@ import com.intellij.codeInsight.completion.LightFixtureCompletionTestCase
import com.intellij.codeInsight.hint.HintManager
import com.intellij.codeInsight.hint.HintManagerImpl
import com.intellij.ide.highlighter.JavaFileType
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.impl.NonBlockingReadActionImpl
import com.intellij.openapi.editor.Editor
@@ -21,7 +22,7 @@ import javax.swing.JComponent
class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
fun testFormatNotCall() {
Registry.get("java.completion.command.enabled").setValue(false, getTestRootDisposable())
Registry.get("ide.completion.command.enabled").setValue(false, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo() {
@@ -35,7 +36,7 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
}
fun testFormat() {
Registry.get("java.completion.command.enabled").setValue(true, getTestRootDisposable())
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo() {
@@ -56,8 +57,191 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
""".trimIndent())
}
fun testCopyFqn() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo.<caret>() {
int y = 10;
}
}
""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("copy ref", ignoreCase = true) })
myFixture.performEditorAction(IdeActions.ACTION_EDITOR_PASTE)
NonBlockingReadActionImpl.waitForAsyncTaskCompletion()
myFixture.checkResult("""
class A {
void foofoo()() {
int y = 10;
}
}
""".trimIndent())
}
fun testGenerateConstructor() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
.<caret>
}
""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("Generate 'Con", ignoreCase = true) })
myFixture.checkResult("""
class A {
public A() {
}
}
""".trimIndent())
}
fun testGenerateConstructorInline() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
Registry.get("ide.completion.command.full.line.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
generate 'con<caret>
}
""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("Generate 'Con", ignoreCase = true) })
myFixture.checkResult("""
class A {
public A() {
}
}
""".trimIndent())
}
fun testOptimizeImport() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
import java.util.List;
.<caret>
class A {
void foo() {
String y = "1";
}
}""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("Optimize im", ignoreCase = true) })
NonBlockingReadActionImpl.waitForAsyncTaskCompletion()
myFixture.checkResult("""
class A {
void foo() {
String y = "1";
}
}""".trimIndent())
}
fun testGenerateGetter() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
String y.<caret>;
}
""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("Generate 'Ge", ignoreCase = true) })
myFixture.checkResult("""
class A {
String y;
public String getY() {
return y;
}
}
""".trimIndent())
}
fun testDeleteLine() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
String y;
public String getY() {
return y;.<caret>
}
}""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("Delete", ignoreCase = true) })
myFixture.checkResult("""
class A {
String y;
public String getY() {
}
}""".trimIndent())
}
fun testInline() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
public String getY() {
String y = "y";
return y.<caret>;
}
}
""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("Inline", ignoreCase = true) })
myFixture.checkResult("""
class A {
public String getY() {
return "y";
}
}
""".trimIndent())
}
fun testCommentElement() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
public String getY() {
return "y";
}
}.<caret>""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("Comment element", ignoreCase = true) })
myFixture.checkResult("""
//class A {
// public String getY() {
// return "y";
// }
//}""".trimIndent())
}
fun testRename() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo() {
String y.<caret> = "1";
System.out.println(y);
}
}""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("Rename", ignoreCase = true) })
myFixture.type('\n')
NonBlockingReadActionImpl.waitForAsyncTaskCompletion()
myFixture.checkResult("""
class A {
void foo() {
String n
umber = "1";
System.out.println(number);
}
}""".trimIndent())
}
fun testCommandsOnlyGoToDeclaration() {
Registry.get("java.completion.command.enabled").setValue(true, getTestRootDisposable())
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo() {
@@ -83,8 +267,43 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
""".trimIndent())
}
fun testCommandsOnlyGoToImplementation() {
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
interface A{
public void a.<caret>();
class B implements A{
@Override
public void a() {
}
}
}
""".trimIndent())
val elements = myFixture.completeBasic()
selectItem(elements.first { element -> element.lookupString.contains("Go to impl", ignoreCase = true) })
NonBlockingReadActionImpl.waitForAsyncTaskCompletion()
myFixture.checkResult("""
interface A{
public void a();
class B implements A{
@Override
public void <caret>a() {
}
}
}
""".trimIndent())
}
fun testRedCode() {
Registry.get("java.completion.command.enabled").setValue(true, getTestRootDisposable())
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo() {
@@ -106,7 +325,7 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
}
fun testComment() {
Registry.get("java.completion.command.enabled").setValue(true, getTestRootDisposable())
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo() {
@@ -128,7 +347,7 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
}
fun testFlipIntention() {
Registry.get("java.completion.command.enabled").setValue(true, getTestRootDisposable())
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.configureByText(JavaFileType.INSTANCE, """
class A {
void foo() {
@@ -150,7 +369,7 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
}
fun testInspection() {
Registry.get("java.completion.command.enabled").setValue(true, getTestRootDisposable())
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
myFixture.enableInspections(SizeReplaceableByIsEmptyInspection())
myFixture.configureByText(JavaFileType.INSTANCE, """
import java.util.List;
@@ -175,7 +394,7 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
}
fun testBinaryNotAllowed() {
Registry.get("java.completion.command.enabled").setValue(false, getTestRootDisposable())
Registry.get("ide.completion.command.enabled").setValue(false, getTestRootDisposable())
val psiClass = JavaPsiFacade.getInstance(project).findClass(JAVA_LANG_CLASS, GlobalSearchScope.allScope(project))
val file = psiClass?.containingFile?.virtualFile
assertNotNull(file)
@@ -193,7 +412,7 @@ class JavaCommandsCompletionTest : LightFixtureCompletionTestCase() {
}
fun testBinaryNotAllowedCalledCompletion() {
Registry.get("java.completion.command.enabled").setValue(true, getTestRootDisposable())
Registry.get("ide.completion.command.enabled").setValue(true, getTestRootDisposable())
val psiClass = JavaPsiFacade.getInstance(project).findClass(JAVA_LANG_CLASS, GlobalSearchScope.allScope(project))
val file = psiClass?.containingFile?.virtualFile
assertNotNull(file)
@@ -1956,11 +1956,7 @@ checkbox.check.for.jdk.updates=Check for JDK updates
intention.family.name.rename.to.ignored=Rename to ignored
tooltip.variable.used=Variable ''{0}'' is used
command.completion.filter.hint=press '.' to show actions
command.completion.generate.text=Generate ''{0}''
command.completion.inline.text=Inline
command.completion.project.tool.text=Open Project tool window
command.completion.recent.files.text=Open recent files popup
advanced.setting.java.show.irrelevant.templates.in.source.roots=Show irrelevant New File templates in Java source roots
java.test.use.wall.time=Use Wall Time
@@ -1971,6 +1967,5 @@ auto.test.on=Auto-test on
disable.auto.test=Disable auto-test
error.no.annotation.class.found=No annotation class found
java.psi.element.comment.completion.command.text=Comment element
preview.api.usage={0} is a preview API and may be removed in a future release
preview.api.usage.reflective={0} is a reflective preview API and may be removed in a future release
@@ -1,3 +1,62 @@
*a:com.intellij.codeInsight.completion.command.ApplicableCompletionCommand
- com.intellij.codeInsight.completion.command.CompletionCommand
- <init>():V
- a:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
- supportsReadOnly():Z
*:com.intellij.codeInsight.completion.command.CommandCompletionFactory
- commandProviders(com.intellij.openapi.project.Project,com.intellij.lang.Language):java.util.List
- createFile(com.intellij.psi.PsiFile,java.lang.String):com.intellij.psi.PsiFile
- filterSuffix():java.lang.Character
- isApplicable(com.intellij.psi.PsiFile,I):Z
- suffix():C
*f:com.intellij.codeInsight.completion.command.CommandCompletionProviderContext
- <init>(com.intellij.openapi.project.Project,com.intellij.openapi.editor.Editor,I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor,I,com.intellij.psi.PsiFile,Z):V
- f:component1():com.intellij.openapi.project.Project
- f:component2():com.intellij.openapi.editor.Editor
- f:component3():I
- f:component4():com.intellij.psi.PsiFile
- f:component5():com.intellij.openapi.editor.Editor
- f:component6():I
- f:component7():com.intellij.psi.PsiFile
- f:component8():Z
- f:copy(com.intellij.openapi.project.Project,com.intellij.openapi.editor.Editor,I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor,I,com.intellij.psi.PsiFile,Z):com.intellij.codeInsight.completion.command.CommandCompletionProviderContext
- bs:copy$default(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext,com.intellij.openapi.project.Project,com.intellij.openapi.editor.Editor,I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor,I,com.intellij.psi.PsiFile,Z,I,java.lang.Object):com.intellij.codeInsight.completion.command.CommandCompletionProviderContext
- equals(java.lang.Object):Z
- f:getEditor():com.intellij.openapi.editor.Editor
- f:getOffset():I
- f:getOriginalEditor():com.intellij.openapi.editor.Editor
- f:getOriginalOffset():I
- f:getOriginalPsiFile():com.intellij.psi.PsiFile
- f:getProject():com.intellij.openapi.project.Project
- f:getPsiFile():com.intellij.psi.PsiFile
- hashCode():I
- f:isReadOnly():Z
*:com.intellij.codeInsight.completion.command.CommandProvider
- a:getCommands(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext):java.util.List
- getId():java.lang.String
- supportsReadOnly():Z
*a:com.intellij.codeInsight.completion.command.CompletionCommand
- <init>():V
- a:execute(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):V
- getAdditionalInfo():java.lang.String
- getContext(I,com.intellij.psi.PsiFile):com.intellij.psi.PsiElement
- getHighlightInfo():com.intellij.codeInsight.completion.command.HighlightInfoLookup
- a:getI18nName():java.lang.String
- a:getIcon():javax.swing.Icon
- a:getName():java.lang.String
- getPriority():java.lang.Integer
*f:com.intellij.codeInsight.completion.command.HighlightInfoLookup
- <init>(com.intellij.openapi.util.TextRange,com.intellij.openapi.editor.colors.TextAttributesKey,I):V
- f:component1():com.intellij.openapi.util.TextRange
- f:component2():com.intellij.openapi.editor.colors.TextAttributesKey
- f:component3():I
- f:copy(com.intellij.openapi.util.TextRange,com.intellij.openapi.editor.colors.TextAttributesKey,I):com.intellij.codeInsight.completion.command.HighlightInfoLookup
- bs:copy$default(com.intellij.codeInsight.completion.command.HighlightInfoLookup,com.intellij.openapi.util.TextRange,com.intellij.openapi.editor.colors.TextAttributesKey,I,I,java.lang.Object):com.intellij.codeInsight.completion.command.HighlightInfoLookup
- equals(java.lang.Object):Z
- f:getAttributesKey():com.intellij.openapi.editor.colors.TextAttributesKey
- f:getPriority():I
- f:getRange():com.intellij.openapi.util.TextRange
- hashCode():I
*:com.intellij.codeInsight.completion.ml.MLRankingIgnorable
- s:wrap(com.intellij.codeInsight.lookup.LookupElement):com.intellij.codeInsight.lookup.LookupElement
*:com.intellij.codeInsight.highlighting.PsiHighlightedReference
@@ -0,0 +1,69 @@
// 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
import com.intellij.lang.Language
import com.intellij.lang.LanguageExtension
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
/**
* Factory interface for creating and managing completion commands in a specific context.
*
* Provides functionality to determine applicability based on the file and offset,
* retrieve a list of command providers, and define suffixes used during code completion.
* Serves as a customization point for enhancing code completion workflows.
*
* Should implement DumbAware to support dumb mode
*/
@ApiStatus.Experimental
interface CommandCompletionFactory {
/**
* Provides the default character suffix. After that, suffix command completion will be enabled
*
* @return The character suffix, which is '.'.
*/
fun suffix(): Char = '.'
/**
* Determines the character suffix to filter only command lookup
*/
fun filterSuffix(): Char? = '.'
/**
* Retrieves a list of command providers responsible for supplying completion commands.
* The default implementation returns a set of providers.
* Language plugins can add command providers via 'com.intellij.codeInsight.completion.command.provider' EP
* or implement [com.intellij.codeInsight.completion.command.ApplicableCompletionCommand]
* and register with 'com.intellij.codeInsight.completion.applicable.command' language extension
*/
fun commandProviders(project: Project, language: Language): List<CommandProvider> {
if (!Registry.`is`("ide.completion.command.enabled")) return mutableListOf<CommandProvider>()
return DumbService.getInstance(project).filterByDumbAwareness(EP_NAME.allForLanguageOrAny(language))
}
/**
* Determines whether the functionality is applicable in the given context.
*
* @param psiFile the PSI file representing the file in which the applicability is being evaluated
* @param offset the position within the file where the applicability should be checked
* @return true if the functionality is applicable at the specified context, false otherwise
*/
fun isApplicable(psiFile: PsiFile, offset: Int): Boolean {
return true
}
/**
* Creates a new file based on the provided original file and text content.
*
* @param originalFile The base file from which the new file will be created. It must be of type `PsiFile`.
* @param text The text content to be used for the newly created file.
* @return new file, containing text or null if creation file with [com.intellij.psi.PsiFileFactory.createFileFromText] is available
*
*/
fun createFile(originalFile: PsiFile, text: String): PsiFile? = null
}
private val EP_NAME = LanguageExtension<CommandProvider>("com.intellij.codeInsight.completion.command.provider")
@@ -1,8 +1,7 @@
// 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.api
package com.intellij.codeInsight.completion.command
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus.Experimental
@@ -16,9 +15,6 @@ import org.jetbrains.annotations.ApiStatus.Experimental
*/
@Experimental
interface CommandProvider {
companion object {
val EP_NAME = ExtensionPointName<CommandProvider>("com.intellij.codeInsight.completion.command.provider")
}
/**
@@ -31,7 +27,7 @@ interface CommandProvider {
*/
fun getCommands(context: CommandCompletionProviderContext): List<CompletionCommand>
fun getId(): String
fun getId(): String = javaClass.name
/**
* Indicates whether the implementation supports non-written files.
@@ -1,15 +1,9 @@
// 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.api
// 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
import com.intellij.codeInsight.TargetElementUtil
import com.intellij.codeInsight.completion.commands.core.HighlightInfoLookup
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.editor.colors.TextAttributesKey
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.util.concurrency.annotations.RequiresEdt
@@ -87,10 +81,6 @@ abstract class CompletionCommand {
*/
@ApiStatus.Experimental
abstract class ApplicableCompletionCommand : CompletionCommand() {
companion object {
val EP_NAME = ExtensionPointName<ApplicableCompletionCommand>("com.intellij.codeInsight.completion.command")
}
/**
* Determines whether the command is applicable based on the given context.
@@ -112,47 +102,9 @@ abstract class ApplicableCompletionCommand : CompletionCommand() {
open fun supportsReadOnly(): Boolean = false
}
/**
* Constructs a [DataContext] instance using the specified parameters, including the PSI file,
* editor, and optionally a context element. The resulting data context is populated with
* relevant keys and values to be used within the context of the provided elements.
*
* @param psiFile The [PsiFile] associated with the current context.
* @param editor The [Editor] instance corresponding to the current context.
* @param context An optional [PsiElement] representing the context; can be null.
* @return A [DataContext] instance containing the provided context information.
*/
internal fun getDataContext(
psiFile: PsiFile,
editor: Editor,
context: PsiElement?,
): DataContext {
val dataContext = SimpleDataContext.builder()
.add(CommonDataKeys.PROJECT, psiFile.project)
.add(CommonDataKeys.EDITOR, editor)
.add(CommonDataKeys.PSI_ELEMENT, context)
.add(CommonDataKeys.PSI_FILE, psiFile)
.add(LangDataKeys.CONTEXT_LANGUAGES, arrayOf(psiFile.language))
.build()
return dataContext
}
/**
* Retrieves the PSI element located at the specified offset within the editor's context.
* This method attempts to find a target PSI element based on the editor's state and the offset,
* allowing interaction with the underlying code structure.
*
* @param offset The offset in the editor's document where the target element is to be located.
* @param editor The editor instance in which the method searches for the target element.
* @return The PSI element found at the specified offset, or null if no suitable element is found
* or if the index is not ready.
*/
internal fun getTargetContext(offset: Int, editor: Editor): PsiElement? {
try {
val util = TargetElementUtil.getInstance()
return util.findTargetElement(editor, util.getReferenceSearchFlags(), offset)
}
catch (_: IndexNotReadyException) {
return null
}
}
@ApiStatus.Experimental
data class HighlightInfoLookup(
val range: TextRange,
val attributesKey: TextAttributesKey,
val priority: Int, //higher is on the top
)
@@ -607,3 +607,8 @@ jvm.inspections.dependency.configure.button.text=Configure dependency rules
checking.code.highlightings.in.background=Loading Code Highlighting
title.checking.code.highlightings.in.background.task=Loading code passes
documentation.download.button.label=Download documentation
command.completion.filter.hint=press ''{0}'' to show actions
command.completion.project.tool.text=Open Project tool window
command.completion.recent.files.text=Open recent files popup
command.completion.generate.text=Generate ''{0}''
command.completion.psi.element.comment.text=Comment element
@@ -143,6 +143,95 @@ c:com.intellij.codeInsight.actions.VcsFacade
*f:com.intellij.codeInsight.codeVision.CodeVisionProvider$Companion
- sf:EP_NAME:java.lang.String
- f:getProvidersExtensionPoint():com.intellij.openapi.extensions.ExtensionPointName
*a:com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- com.intellij.codeInsight.completion.command.ApplicableCompletionCommand
- com.intellij.openapi.project.DumbAware
- <init>(java.lang.String,java.lang.String,java.lang.String,javax.swing.Icon,java.lang.Integer):V
- b:<init>(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.AbstractCopyFQNCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- f:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
- a:placeIsApplicable(com.intellij.psi.PsiElement,I):Z
- f:supportsReadOnly():Z
*a:com.intellij.codeInsight.completion.command.commands.AbstractFormatCodeCompletionCommand
- com.intellij.codeInsight.completion.command.ApplicableCompletionCommand
- <init>():V
- f:execute(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):V
- a:findTargetToRefactor(com.intellij.psi.PsiElement):com.intellij.psi.PsiElement
- f:getI18nName():java.lang.String
- f:getIcon():javax.swing.Icon
- f:getName():java.lang.String
- f:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
*a:com.intellij.codeInsight.completion.command.commands.AbstractGenerateCommandProvider
- com.intellij.codeInsight.completion.command.CommandProvider
- com.intellij.openapi.project.DumbAware
- <init>():V
- a:generationIsAvailable(com.intellij.psi.PsiElement,I):Z
- f:getCommands(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext):java.util.List
- pf:isOnlySpaceInLine(com.intellij.openapi.editor.Document,I):Z
*a:com.intellij.codeInsight.completion.command.commands.AbstractGoToDeclarationCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- a:canNavigateToDeclaration(com.intellij.psi.PsiElement):Z
- f:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
- f:supportsReadOnly():Z
*a:com.intellij.codeInsight.completion.command.commands.AbstractGoToImplementationCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- a:canGoToImplementation(com.intellij.psi.PsiElement,I):Z
- f:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
- f:supportsReadOnly():Z
*a:com.intellij.codeInsight.completion.command.commands.AbstractShowUsagesActionCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- a:hasToShow(com.intellij.psi.PsiElement):Z
- f:isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
- f:supportsReadOnly():Z
*f:com.intellij.codeInsight.completion.command.commands.DirectIntentionCommandProvider
- com.intellij.codeInsight.completion.command.CommandProvider
- <init>():V
- getCommands(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext):java.util.List
*:com.intellij.codeInsight.completion.command.commands.IntentionCommandSkipper
- *sf:Companion:com.intellij.codeInsight.completion.command.commands.IntentionCommandSkipper$Companion
- skip(com.intellij.codeInsight.intention.CommonIntentionAction,com.intellij.psi.PsiFile,I):Z
*f:com.intellij.codeInsight.completion.command.commands.IntentionCommandSkipper$Companion
*f:com.intellij.codeInsight.completion.command.commands.LineCommentCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- execute(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):V
*f:com.intellij.codeInsight.completion.command.commands.OptimizeImportCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
*f:com.intellij.codeInsight.completion.command.commands.ProjectViewCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
- supportsReadOnly():Z
*f:com.intellij.codeInsight.completion.command.commands.RecentFilesCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
- supportsReadOnly():Z
*f:com.intellij.codeInsight.completion.command.commands.RenameActionCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
*f:com.intellij.codeInsight.completion.command.commands.SimpleRunMarkerCommandProvider
- com.intellij.codeInsight.completion.command.CommandProvider
- <init>():V
- getCommands(com.intellij.codeInsight.completion.command.CommandCompletionProviderContext):java.util.List
*:com.intellij.codeInsight.daemon.impl.focusMode.FocusModeProvider
- a:calcFocusZones(com.intellij.psi.PsiFile):java.util.List
*:com.intellij.codeInsight.editorActions.TypingActionsExtension
@@ -2133,6 +2133,11 @@ f:com.intellij.codeInsight.completion.addingPolicy.PassDirectlyPolicy
- <init>():V
- addAllElements(com.intellij.codeInsight.completion.CompletionResultSet,java.lang.Iterable):V
- addElement(com.intellij.codeInsight.completion.CompletionResultSet,com.intellij.codeInsight.lookup.LookupElement):V
f:com.intellij.codeInsight.completion.command.commands.PsiElementCommentCompletionCommand
- com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
- <init>():V
- execute(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):V
- isApplicable(I,com.intellij.psi.PsiFile,com.intellij.openapi.editor.Editor):Z
c:com.intellij.codeInsight.completion.impl.BetterPrefixMatcher
- com.intellij.codeInsight.completion.PrefixMatcher
- <init>(com.intellij.codeInsight.completion.PrefixMatcher,I):V
@@ -1,9 +1,6 @@
// 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
// 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
import com.intellij.codeInsight.completion.commands.api.ApplicableCompletionCommand
import com.intellij.codeInsight.completion.commands.api.getDataContext
import com.intellij.codeInsight.completion.commands.api.getTargetContext
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.ex.ActionUtil
@@ -13,9 +10,17 @@ import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.DumbService
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import javax.swing.Icon
/**
* 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.
*/
@ApiStatus.Experimental
abstract class AbstractActionCompletionCommand(
var actionId: String,
override val name: String,
@@ -37,11 +42,11 @@ abstract class AbstractActionCompletionCommand(
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
val action = action ?: return false
if (editor == null) return false
if (!DumbService.getInstance(psiFile.project).isUsableInCurrentContext(action)) 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.NONE, null)
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
}
@@ -53,20 +58,28 @@ abstract class AbstractActionCompletionCommand(
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.NONE, null)
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)
}
}
companion object {
internal 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 ch = psiFile.fileDocument.immutableCharSequence[offset - 1]
if (!ch.isLetterOrDigit() && ch != ']' && ch != ')') return true
return false
/**
* 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
}
}
@@ -1,10 +1,14 @@
// 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.core
// 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
import com.intellij.openapi.components.*
import org.jetbrains.annotations.ApiStatus
/**
* Service responsible for managing and persisting command completion settings at the application level.
*
*/
@Service(Service.Level.APP)
@ApiStatus.Experimental
@ApiStatus.Internal
@@ -20,6 +24,8 @@ class ApplicationCommandCompletionService : PersistentStateComponent<AppCommandC
}
}
@ApiStatus.Experimental
@ApiStatus.Internal
class AppCommandCompletionSettings(
var showCounts: Int = 0,
)
@@ -1,5 +1,5 @@
// 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.core
// 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
import com.intellij.codeInsight.completion.CompletionContributor
import com.intellij.codeInsight.completion.CompletionType
@@ -0,0 +1,91 @@
// 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
import com.intellij.codeInsight.TargetElementUtil
import com.intellij.codeInsight.lookup.AutoCompletionPolicy
import com.intellij.codeInsight.lookup.AutoCompletionPolicy.NEVER_AUTOCOMPLETE
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.LangDataKeys
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.ApiStatus.Experimental
import org.jetbrains.annotations.ApiStatus.Internal
import javax.swing.Icon
/**
* Represents a specialized lookup element intended for usage in command completion scenarios.
*/
@ApiStatus.Internal
@ApiStatus.Experimental
internal class CommandCompletionLookupElement(
lookupElement: LookupElement,
val hostStartOffset: Int,
val suffix: String,
val icon: Icon?,
val highlighting: HighlightInfoLookup?,
) : LookupElementDecorator<LookupElement>(lookupElement) {
override fun isWorthShowingInAutoPopup(): Boolean {
return true
}
override fun getAutoCompletionPolicy(): AutoCompletionPolicy? {
return NEVER_AUTOCOMPLETE
}
}
/**
* Constructs a [DataContext] instance using the specified parameters, including the PSI file,
* editor, and optionally a context element. The resulting data context is populated with
* relevant keys and values to be used within the context of the provided elements.
*
* @param psiFile The [PsiFile] associated with the current context.
* @param editor The [Editor] instance corresponding to the current context.
* @param context An optional [PsiElement] representing the context; can be null.
* @return A [DataContext] instance containing the provided context information.
*/
@Internal
@Experimental
fun getDataContext(
psiFile: PsiFile,
editor: Editor,
context: PsiElement?,
): DataContext {
val dataContext = SimpleDataContext.builder()
.add(CommonDataKeys.PROJECT, psiFile.project)
.add(CommonDataKeys.EDITOR, editor)
.add(CommonDataKeys.PSI_ELEMENT, context)
.add(CommonDataKeys.PSI_FILE, psiFile)
.add(LangDataKeys.CONTEXT_LANGUAGES, arrayOf(psiFile.language))
.build()
return dataContext
}
/**
* Retrieves the PSI element located at the specified offset within the editor's context.
* This method attempts to find a target PSI element based on the editor's state and the offset,
* allowing interaction with the underlying code structure.
*
* @param offset The offset in the editor's document where the target element is to be located.
* @param editor The editor instance in which the method searches for the target element.
* @return The PSI element found at the specified offset, or null if no suitable element is found
* or if the index is not ready.
*/
@Internal
@Experimental
fun getTargetContext(offset: Int, editor: Editor): PsiElement? {
try {
val util = TargetElementUtil.getInstance()
return util.findTargetElement(editor, util.getReferenceSearchFlags(), offset)
}
catch (_: IndexNotReadyException) {
return null
}
}
@@ -1,5 +1,5 @@
// 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.core
// 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
import com.intellij.codeInsight.completion.CodeCompletionHandlerBase
import com.intellij.codeInsight.completion.CompletionType
@@ -20,7 +20,7 @@ import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.registry.Registry.Companion.`is`
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.wm.IdeFocusManager
import com.intellij.psi.PsiDocumentManager
@@ -32,9 +32,17 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.jetbrains.annotations.ApiStatus
/**
* Handles non-write access typed events for command-based code completion functionality.
* This class extends the `NonWriteAccessTypedHandler` to provide custom behavior for handling typed characters
* and triggering command completions without modifying the editor's document directly.
*
*/
@ApiStatus.Experimental
@ApiStatus.Internal
class CommandCompletionNonWriteAccessTypedHandler : NonWriteAccessTypedHandler {
override fun isApplicable(editor: Editor, charTyped: Char, dataContext: DataContext): Boolean {
if (!`is`("java.completion.command.enabled")) return false
if (!Registry.`is`("ide.completion.command.enabled")) return false
val project = editor.project ?: return false
val commandCompletionService = project.getService(CommandCompletionService::class.java)
if (commandCompletionService == null) return false
@@ -56,7 +64,7 @@ class CommandCompletionNonWriteAccessTypedHandler : NonWriteAccessTypedHandler {
}
override fun handle(editor: Editor, charTyped: Char, dataContext: DataContext) {
if (!`is`("java.completion.command.enabled")) return
if (!Registry.`is`("ide.completion.command.enabled")) return
val accessCommandCompletionService = editor.project?.getService(NonWriteAccessCommandCompletionService::class.java)
if (accessCommandCompletionService == null) return
accessCommandCompletionService.insertNewEditor(editor)
@@ -70,12 +78,11 @@ internal val ORIGINAL_EDITOR = Key.create<Pair<Editor, Int>>("completion.command
@ApiStatus.Experimental
@Service(Service.Level.PROJECT)
class NonWriteAccessCommandCompletionService(
private val project: Project,
val coroutineScope: CoroutineScope,
) {
fun insertNewEditor(editor: Editor) {
if (!`is`("java.completion.command.enabled")) return
if (!Registry.`is`("ide.completion.command.enabled")) return
if (editor !is EditorImpl) return
val project = editor.project ?: return
val offset = editor.caretModel.offset
@@ -1,10 +1,7 @@
// 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.core
// 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
import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.completion.commands.api.CommandCompletionFactory
import com.intellij.codeInsight.completion.commands.api.CommandCompletionProviderContext
import com.intellij.codeInsight.completion.commands.api.CompletionCommand
import com.intellij.codeInsight.completion.impl.CamelHumpMatcher
import com.intellij.codeInsight.completion.ml.MLWeigherUtil
import com.intellij.codeInsight.lookup.LookupElement
@@ -36,6 +33,15 @@ import com.intellij.util.ProcessingContext
import com.intellij.util.Processor
import org.jetbrains.annotations.ApiStatus
/**
* Internal provider for handling command completion in IntelliJ-based editors.
*
* This class extends the `CompletionProvider` to provide custom completion suggestions
* in contexts where command completions are applicable. It is primarily used for adding
* completions for specific command-based scenarios in supported languages or files.
*
*/
@ApiStatus.Internal
@ApiStatus.Experimental
internal class CommandCompletionProvider : CompletionProvider<CompletionParameters?>() {
@@ -48,7 +54,7 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
context: ProcessingContext,
resultSet: CompletionResultSet,
) {
if (!Registry.`is`("java.completion.command.enabled")) return
if (!Registry.`is`("ide.completion.command.enabled")) return
resultSet.runRemainingContributors(parameters) {
resultSet.passResult(it)
}
@@ -84,13 +90,14 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
if (!dumbService.isUsableInCurrentContext(commandCompletionFactory)) return
if (editor.document.textLength != offset &&
StringUtil.isJavaIdentifierPart(editor.document.immutableCharSequence[offset])) {
StringUtil.isJavaIdentifierPart(editor.document.immutableCharSequence[offset])
) {
return
}
val commandCompletionType = findCommandCompletionType(commandCompletionFactory, isReadOnly, offset, parameters.editor) ?: return
val adjustedParameters = try {
adjustParameters(commandCompletionType, editor, originalFile, offset, isReadOnly) ?: return
adjustParameters(commandCompletionFactory, commandCompletionType, editor, originalFile, offset, isReadOnly) ?: return
}
catch (_: Exception) {
return
@@ -101,17 +108,19 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
val copyEditor = MyEditor(adjustedParameters.copyFile, editor.settings)
copyEditor.caretModel.moveToOffset(adjustedParameters.offset)
val withPrefixMatcher = resultSet.withPrefixMatcher(CamelHumpMatcher(resultSet.prefixMatcher.prefix, false, true))
val prefix = commandCompletionType.pattern
val withPrefixMatcher = resultSet.withPrefixMatcher(CamelHumpMatcher(prefix, false, true))
.withRelevanceSorter(createSorter(parameters))
withPrefixMatcher.restartCompletionOnPrefixChange(StandardPatterns.string().with(object : PatternCondition<String>("add filter for command completion") {
override fun accepts(t: String, context: ProcessingContext?): Boolean {
return !isReadOnly && commandCompletionType.suffix.toString() + t ==
commandCompletionFactory.suffix() + commandCompletionFactory.filterSuffix().toString() ||
isReadOnly && commandCompletionType.suffix.toString() + t == commandCompletionFactory.filterSuffix().toString()
}
}))
withPrefixMatcher.restartCompletionOnPrefixChange(
StandardPatterns.string().with(object : PatternCondition<String>("add filter for command completion") {
override fun accepts(t: String, context: ProcessingContext?): Boolean {
return !isReadOnly && commandCompletionType.suffix + t ==
commandCompletionFactory.suffix() + commandCompletionFactory.filterSuffix().toString() ||
isReadOnly && commandCompletionType.suffix.toString() + t == commandCompletionFactory.filterSuffix().toString()
}
}))
// Fetch commands applicable to the position
processCommandsForContext(commandCompletionFactory,
@@ -179,13 +188,24 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
val indexOf = findActualIndex(suffix, text, offset)
if (offset - indexOf < 0) return null
if (indexOf == 1) {
return InvocationCommandType.PartialSuffix(text.substring(offset - indexOf + 1, offset), text.substring(offset - indexOf, offset - indexOf + 1))
//one point
return InvocationCommandType.PartialSuffix(text.substring(offset - indexOf + 1, offset),
text.substring(offset - indexOf, offset - indexOf + 1))
}
//two points
else if (offset - indexOf + 2 <= text.length && text.substring(offset - indexOf, offset - indexOf + 2) == suffix) {
return InvocationCommandType.FullSuffix(text.substring(offset - indexOf + 2, offset), text.substring(offset - indexOf, offset - indexOf + 2))
return InvocationCommandType.FullSuffix(text.substring(offset - indexOf + 2, offset),
text.substring(offset - indexOf, offset - indexOf + 2))
}
if (indexOf > 0 && text.substring(offset - indexOf, offset - indexOf + 2).contains(factory.suffix())) {
//force call with one point
return InvocationCommandType.PartialSuffix(text.substring(offset - indexOf + 1, offset),
text.substring(offset - indexOf, offset - indexOf + 1))
}
if (!Registry.`is`("ide.completion.command.full.line.enabled")) return null
if (indexOf > 0) {
return InvocationCommandType.PartialSuffix(text.substring(offset - indexOf + 1, offset), text.substring(offset - indexOf, offset - indexOf + 1))
//full empty line
return InvocationCommandType.FullLine(text.substring(offset - indexOf, offset), "")
}
return null
}
@@ -204,10 +224,11 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
) {
val element = copyFile.findElementAt(offset - 1)
if (element == null) return
for (provider in commandCompletionFactory.commandProviders(project)) {
for (provider in commandCompletionFactory.commandProviders(project, element.language)) {
try {
if(isReadOnly && !provider.supportsReadOnly()) continue
val commands = provider.getCommands(CommandCompletionProviderContext(project, copyEditor, offset, copyFile, originalEditor, originalOffset, originalFile, isReadOnly))
if (isReadOnly && !provider.supportsReadOnly()) continue
val commands = provider.getCommands(
CommandCompletionProviderContext(project, copyEditor, offset, copyFile, originalEditor, originalOffset, originalFile, isReadOnly))
processor.process(commands)
}
catch (e: Exception) {
@@ -224,6 +245,7 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
private data class AdjustedCompletionParameters(val copyFile: PsiFile, val offset: Int, val hostAdjustedOffset: Int)
private fun adjustParameters(
commandCompletionFactory: CommandCompletionFactory,
commandCompletionType: InvocationCommandType,
editor: Editor,
originalFile: PsiFile,
@@ -240,14 +262,38 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
var adjustedOffset = hostOffset - commandCompletionType.suffix.length - commandCompletionType.pattern.length
if (adjustedOffset <= 0) return null
val hostAdjustedOffset = adjustedOffset
val adjustedText = originalDocument.getText(TextRange(0, adjustedOffset)) + originalDocument.getText(TextRange(hostOffset, originalDocument.textLength))
val adjustedText = originalDocument.getText(TextRange(0, adjustedOffset)) + originalDocument.getText(
TextRange(hostOffset, originalDocument.textLength))
var file =
if (isNonWritten) {
originalFile.copy() as PsiFile
}else{
PsiFileFactory.getInstance(topEditor.project)
.createFileFromText(topFile.getName(), topFile.getLanguage(), adjustedText, true, true, false, topFile.virtualFile)
val createdFile = originalFile.copy() as PsiFile
if (createdFile is PsiFileImpl) {
createdFile.setOriginalFile(topFile)
}
createdFile
}
else {
if (topFile == originalFile) {
var createdFile = commandCompletionFactory.createFile(originalFile, adjustedText)
if (createdFile == null) {
createdFile = PsiFileFactory.getInstance(topEditor.project)
.createFileFromText(topFile.getName(), topFile.getLanguage(), adjustedText, true, true, false, topFile.virtualFile)
if (createdFile is PsiFileImpl) {
createdFile.setOriginalFile(topFile)
}
}
createdFile
}
else {
val createdFile = PsiFileFactory.getInstance(topEditor.project)
.createFileFromText(topFile.getName(), topFile.getLanguage(), adjustedText, true, true, false, topFile.virtualFile)
if (createdFile is PsiFileImpl) {
createdFile.setOriginalFile(topFile)
}
createdFile
}
}
if (file is PsiFileImpl) {
@@ -266,7 +312,8 @@ internal class CommandCompletionProvider : CompletionProvider<CompletionParamete
class CommandCompletionUnsupportedOperationException
: UnsupportedOperationException("It's unexpected to invoke this method on a command completion calculating.")
private class MyEditor(psiFileCopy: PsiFile, private val settings: EditorSettings) : ImaginaryEditor(psiFileCopy.project, psiFileCopy.viewProvider.document!!) {
private class MyEditor(psiFileCopy: PsiFile, private val settings: EditorSettings) : ImaginaryEditor(psiFileCopy.project,
psiFileCopy.viewProvider.document!!) {
override fun notImplemented(): RuntimeException = throw CommandCompletionUnsupportedOperationException()
override fun isViewer(): Boolean = false
@@ -296,6 +343,7 @@ internal sealed interface InvocationCommandType {
data class PartialSuffix(override val pattern: String, override val suffix: String) : InvocationCommandType
data class FullSuffix(override val pattern: String, override val suffix: String) : InvocationCommandType
data class FullLine(override val pattern: String, override val suffix: String) : InvocationCommandType
}
internal fun findActualIndex(suffix: String, text: CharSequence, offset: Int): Int {
@@ -315,7 +363,8 @@ internal fun findActualIndex(suffix: String, text: CharSequence, offset: Int): I
val currentSuffix = text[offset - shift]
if (currentSuffixFiltered == suffix || currentSuffix == suffix[0]) {
if (suffix.length == 2 && suffix.first() == suffix.last() &&
text.substring(offset - shift - 1, offset - shift + suffix.length - 1) == suffix) {
text.substring(offset - shift - 1, offset - shift + suffix.length - 1) == suffix
) {
indexOf = shift + 1
}
else {
@@ -328,5 +377,19 @@ internal fun findActualIndex(suffix: String, text: CharSequence, offset: Int): I
}
}
}
if (indexOf == 0) {
var currentIndex = 1
while (offset - currentIndex >= 0 && (text[offset - currentIndex].isLetter() ||
text[offset - currentIndex] == ' ' ||
text[offset - currentIndex] == '\'')
) {
currentIndex++
}
if (currentIndex <= 1 || text[offset - currentIndex] != '\n') return indexOf
while (currentIndex >= 0 && text[offset - currentIndex].isWhitespace()) {
currentIndex--
}
if (currentIndex >= 0) indexOf = currentIndex
}
return indexOf
}
@@ -1,7 +1,7 @@
// 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.core
// 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
import com.intellij.codeInsight.completion.commands.api.CommandCompletionFactory
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.codeInsight.daemon.impl.HintRenderer
import com.intellij.codeInsight.editorLineStripeHint.EditorLineStripeTextRenderer
import com.intellij.codeInsight.highlighting.HighlightManager
@@ -12,7 +12,6 @@ import com.intellij.codeInsight.lookup.CharFilter.CUSTOM_DEFAULT_CHAR_FILTERS
import com.intellij.codeInsight.lookup.impl.LookupCustomizer
import com.intellij.codeInsight.lookup.impl.LookupImpl
import com.intellij.codeInsight.template.impl.TemplateColors
import com.intellij.java.JavaBundle
import com.intellij.lang.Language
import com.intellij.lang.LanguageExtension
import com.intellij.lang.injection.InjectedLanguageManager
@@ -29,7 +28,6 @@ import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.editor.markup.HighlighterLayer
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.*
import com.intellij.openapi.util.registry.Registry
import com.intellij.psi.PsiDocumentManager
@@ -43,11 +41,17 @@ import org.jetbrains.annotations.ApiStatus
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.min
/**
* Service class responsible for managing and providing functionality for command completion within a project.
*
* This class allows filtering and enhancing the behavior of code completion for commands, assisting in the
* integration of additional matchers, hint rendering, and custom lookup arrangements. It relies on language-specific
* completion factories to customize behavior according to individual requirements.
*/
@ApiStatus.Internal
@ApiStatus.Experimental
@Service(Service.Level.PROJECT)
internal class CommandCompletionService(
private val project: Project,
val coroutineScope: CoroutineScope,
) : Disposable {
@@ -126,7 +130,7 @@ internal class CommandCompletionService(
val state = applicationCommandCompletionService.state
if (state.showCounts > 5) return
state.showCounts += 1
val inlineElement: Inlay<HintRenderer?> = editor.inlayModel.addInlineElement(endOffset, true, EditorLineStripeTextRenderer(" " + JavaBundle.message("command.completion.filter.hint")))
val inlineElement: Inlay<HintRenderer?> = editor.inlayModel.addInlineElement(endOffset, true, EditorLineStripeTextRenderer(" " + CodeInsightBundle.message("command.completion.filter.hint", completionFactory.filterSuffix())))
?: return
Disposer.register(lookup, inlineElement)
Disposer.register(lookup) { lookup.removeUserData(INSTALLED_HINT) }
@@ -156,7 +160,7 @@ private const val PROMPT_LAYER = HighlighterLayer.ERROR + 10
private class CommandCompletionListener : LookupManagerListener {
override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
if (!Registry.`is`("java.completion.command.enabled")) return
if (!Registry.`is`("ide.completion.command.enabled")) return
var editor = newLookup?.editor ?: return
val originalEditor = editor.getUserData(ORIGINAL_EDITOR)
var psiFile = newLookup.psiFile ?: return
@@ -253,7 +257,8 @@ private class CommandCompletionHighlightingListener(
private fun update(lookup: LookupImpl, item: CommandCompletionLookupElement) {
val installed = ConcurrencyUtil.computeIfAbsent(lookup, INSTALLED_LISTENER_KEY) { AtomicBoolean(false) }
val startOffset = lookup.lookupOriginalStart - findActualIndex(item.suffix, editor.document.immutableCharSequence, lookup.lookupOriginalStart)
val startOffset = lookup.lookupOriginalStart - findActualIndex(item.suffix, editor.document.immutableCharSequence,
lookup.lookupOriginalStart)
val lookupEditor = InjectedLanguageEditorUtil.getTopLevelEditor(lookup.editor)
val endOffset = lookupEditor.caretModel.offset
if (!installed.get()) {
@@ -295,7 +300,9 @@ private class CommandCompletionHighlightingListener(
val previousHighlighting = lookup.removeUserData(LOOKUP_HIGHLIGHTING)
previousHighlighting?.forEach { t -> highlightManager.removeSegmentHighlighter(editor, t) }
val startOffset = lookup.lookupOriginalStart -
if (nonWrittenFiles) 0 else findActualIndex(element.suffix, editor.document.immutableCharSequence, lookup.lookupOriginalStart)
if (nonWrittenFiles) 0
else findActualIndex(element.suffix, editor.document.immutableCharSequence,
lookup.lookupOriginalStart)
val highlightInfo = element.highlighting ?: return
val rangeHighlighters = mutableListOf<RangeHighlighter>()
val endOffset = min(highlightInfo.range.endOffset, startOffset)
@@ -323,9 +330,17 @@ private class CommandCompletionHighlightingListener(
}
}
/**
* A custom implementation of [CharFilter] that provides specific behavior for handling character inputs
* during completion, particularly for commands.
* This implementation works in conjunction with the `CommandCompletionService`.
* This filter evaluates whether a given character should modify the current completion prefix,
* select an item, or hide the lookup based on various conditions, such as the presence of
* specific data in the current lookup or the state of the caret/editor.
*/
private class CommandCompletionCharFilter : CharFilter() {
override fun acceptChar(c: Char, prefixLength: Int, lookup: Lookup?): Result? {
if (!Registry.`is`("java.completion.command.enabled")) return null
if (!Registry.`is`("ide.completion.command.enabled")) return null
if (lookup !is LookupImpl) return null
val completionService = lookup.project.service<CommandCompletionService>()
val installedHint = lookup.removeUserData(INSTALLED_HINT)
@@ -351,9 +366,13 @@ private class CommandCompletionCharFilter : CharFilter() {
}
}
/**
* A private implementation of the `LookupCustomizer` interface that modifies a lookup instance
* to insert additional flags.
*/
private class CommandCompletionLookupCustomizer : LookupCustomizer {
override fun customizeLookup(lookupImpl: LookupImpl) {
if (!Registry.`is`("java.completion.command.enabled")) return
if (!Registry.`is`("ide.completion.command.enabled")) return
val project = lookupImpl.project
val service = project.service<CommandCompletionService>()
val editor = lookupImpl.editor
@@ -1,9 +1,8 @@
// 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.core
// 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
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.codeInsight.completion.commands.api.CompletionCommand
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.injected.editor.DocumentWindow
import com.intellij.lang.injection.InjectedLanguageManager
@@ -17,12 +16,18 @@ import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil
import org.jetbrains.annotations.ApiStatus
/**
* A handler for managing the insertion of commands during code completion.
* This class manages specific behaviors that occur when a command from the lookup
* list is selected and inserted into the editor.
*/
@ApiStatus.Internal
@ApiStatus.Experimental
internal class CommandInsertHandler(private val completionCommand: CompletionCommand) : InsertHandler<LookupElement?> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
var editor = context.editor
val originalEditor = editor.getUserData(ORIGINAL_EDITOR)
var startOffset = 0
var startOffset: Int
var psiFile = context.file
if (originalEditor != null) {
startOffset = originalEditor.second
@@ -0,0 +1,39 @@
// 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.command.commands
import com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
import com.intellij.ide.IdeBundle
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
/**
* Represents an abstract command for copying Fully Qualified Names (FQN) as part of the code completion process.
* This command provides functionality to determine its applicability based on a specific element within the PSI
* file and the current editor context.
*/
@ApiStatus.Experimental
abstract class AbstractCopyFQNCompletionCommand : AbstractActionCompletionCommand("CopyReference",
"Copy Reference",
IdeBundle.message("copy.reference"),
null,
-150) {
final override fun supportsReadOnly(): Boolean = true
final override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
if (!super.isApplicable(offset, psiFile, editor)) return false
val element = getContext(offset, psiFile)
if (element == null) return false
return placeIsApplicable(element, offset)
}
/**
* Determines whether a specific context within a PSI element is applicable for the command execution.
*
* @param element The PSI element to evaluate, or null if no context is applicable.
* @param offset The position in the document where the applicability is being checked.
* @return true if the place is applicable based on the provided element and offset, false otherwise.
*/
abstract fun placeIsApplicable(element: PsiElement, offset: Int): Boolean
}
@@ -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.actions.ReformatCodeProcessor
import com.intellij.codeInsight.completion.command.ApplicableCompletionCommand
import com.intellij.icons.AllIcons
import com.intellij.idea.ActionsBundle
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import javax.swing.Icon
/**
* An abstract command designed to provide a context-specific code formatting completion action.
* This class handles the completion logic for reformatting code based on the PSI (Program Structure Interface)
* context and specific applicability conditions.
*/
@ApiStatus.Experimental
abstract class AbstractFormatCodeCompletionCommand : ApplicableCompletionCommand() {
final override val name: String
get() = "Format"
final override val i18nName: @Nls String
get() = ActionsBundle.message("action.ReformatCode.text")
final override val icon: Icon
get() = AllIcons.Actions.ReformatCode // Use the reformat icon
final override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
if (InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)) return false
return true
}
final override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
val element = getContext(offset, psiFile) ?: return
val target = findTargetToRefactor(element)
ReformatCodeProcessor(element.containingFile, arrayOf(target.textRange)).run()
}
/**
* Finds the appropriate parent element for refactoring based on the given PSI element.
*/
abstract fun findTargetToRefactor(element: PsiElement): PsiElement
}
@@ -0,0 +1,97 @@
// 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.CodeInsightBundle
import com.intellij.codeInsight.actions.CodeInsightAction
import com.intellij.codeInsight.completion.command.*
import com.intellij.ide.DataManager
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.DumbService
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.util.containers.JBIterable
import org.jetbrains.annotations.ApiStatus.Experimental
import org.jetbrains.annotations.Nls
import javax.swing.Icon
/**
* Abstract base class that provides a framework for generating completion commands
* in a code completion system.
*/
@Experimental
abstract class AbstractGenerateCommandProvider : CommandProvider, DumbAware {
final override fun getCommands(context: CommandCompletionProviderContext): List<CompletionCommand> {
val psiFile = context.psiFile
val project = context.project
val offset = context.offset
val editor = context.editor
if (InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)) return emptyList()
val element = psiFile.findElementAt(if (offset - 1 >= 0) offset - 1 else offset)
if (element == null) return emptyList()
if (!generationIsAvailable(element, offset)) return emptyList()
val context = getTargetContext(offset, editor)
val dataContext = getDataContext(psiFile, editor, context)
val actionEvent = AnActionEvent.createEvent(dataContext, null, ActionPlaces.UNKNOWN, ActionUiKind.Companion.NONE, null)
actionEvent.updateSession = UpdateSession.EMPTY
Utils.initUpdateSession(actionEvent)
val generateActions: MutableList<CodeInsightAction> = ArrayList()
val session = actionEvent.updateSession
val dumbService = DumbService.Companion.getInstance(project)
val activeActions = JBIterable.from(
session.expandedChildren(ActionManager.getInstance().getAction(IdeActions.GROUP_GENERATE) as ActionGroup))
.filter { dumbService.isUsableInCurrentContext(it) }
.filter { o: AnAction? -> o !is Separator && o != null && session.presentation(o).isEnabledAndVisible }
for (action in activeActions) {
if (action is CodeInsightAction) {
generateActions.add(action)
}
}
return generateActions.map { GenerateCompletionCommand(it) }
}
protected fun isOnlySpaceInLine(document: Document, offset: Int): Boolean {
val lineNumber = document.getLineNumber(offset)
val lineStartOffset = document.getLineStartOffset(lineNumber)
val immutableCharSequence = document.immutableCharSequence
for (i in lineStartOffset..offset) {
if (!immutableCharSequence[i].isWhitespace()) {
return false
}
}
return true
}
/**
* Determines if generation actions are available for the provided PSI element.
*
* @param element A PSI element for which the availability of generation actions is to be checked.
* @param offset A current offset.
* @return `true` if generation actions are available for the specified element; `false` otherwise.
*/
abstract fun generationIsAvailable(element: PsiElement, offset: Int): Boolean
}
internal class GenerateCompletionCommand(private val action: CodeInsightAction) : CompletionCommand() {
override val name: String
get() = "Generate \'" + action.templateText + "\'"
override val i18nName: @Nls String
get() = CodeInsightBundle.message("command.completion.generate.text", action.templateText)
override val icon: Icon?
get() = null
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
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.UNKNOWN, ActionUiKind.NONE, null)
if (ActionUtil.lastUpdateAndCheckDumb(action, event, false)) {
ActionUtil.performActionDumbAwareWithCallbacks(action, event)
}
}
}
@@ -0,0 +1,38 @@
// 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.AbstractActionCompletionCommand
import com.intellij.idea.ActionsBundle
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus.Experimental
/**
* Represents an abstract completion command specifically designed for navigating to declarations
* in the code editor.
*/
@Experimental
abstract class AbstractGoToDeclarationCompletionCommand : AbstractActionCompletionCommand("GotoDeclarationOnly",
"Go to Declaration",
ActionsBundle.message("action.GotoDeclarationOnly.text"),
null) {
final override fun supportsReadOnly(): Boolean = true
final override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
return super.isApplicable(offset, psiFile, editor) && hasToShow(offset, psiFile)
}
private fun hasToShow(offset: Int, psiFile: PsiFile): Boolean {
val context = (getContext(offset, psiFile)) ?: return false
return canNavigateToDeclaration(context)
}
/**
* Determines whether it is possible to navigate to a declaration for a given context element in the code.
*
* @param context the PsiElement representing the context in which the navigation is being evaluated
* @return true if navigation to the declaration is possible, false otherwise
*/
abstract fun canNavigateToDeclaration(context: PsiElement): Boolean
}
@@ -0,0 +1,36 @@
// 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.AbstractActionCompletionCommand
import com.intellij.idea.ActionsBundle
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
/**
* Represents an abstract command to navigate to the implementation(s)
* of a given element within a code editor during code completion processes.
*/
@ApiStatus.Experimental
abstract class AbstractGoToImplementationCompletionCommand : AbstractActionCompletionCommand("GotoImplementation",
"Go to Implementation",
ActionsBundle.message("action.GotoImplementation.text"),
null) {
final override fun supportsReadOnly(): Boolean = true
final override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
if (!super.isApplicable(offset, psiFile, editor)) return false
val element = getContext(offset, psiFile) ?: return false
return canGoToImplementation(element, offset)
}
/**
* Determines if navigation to the implementation of the specified element is possible.
*
* @param element the PSI element for which the implementation is to be checked; can be null
* @param offset the offset within the element's document, typically representing the caret's position
* @return true if navigation to the implementation is possible, otherwise false
*/
abstract fun canGoToImplementation(element: PsiElement, offset: Int): Boolean
}
@@ -0,0 +1,38 @@
// 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.AbstractActionCompletionCommand
import com.intellij.find.actions.ShowUsagesAction
import com.intellij.idea.ActionsBundle
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus.Experimental
/**
* Abstract base class for defining commands that are intended to execute the "Show Usages" action
* specifically within the context of code completion.
*/
@Experimental
abstract class AbstractShowUsagesActionCompletionCommand : AbstractActionCompletionCommand(ShowUsagesAction.ID,
"Show usages",
ActionsBundle.message("action.ShowUsages.text"),
null) {
final override fun supportsReadOnly(): Boolean = true
final override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
val element = getContext(offset, psiFile) ?: return false
return super.isApplicable(offset, psiFile, editor) && hasToShow(element) &&
!InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)
}
/**
* Determines whether a given PSI element should be used to show its usages
* in the context of the command execution.
*
* @param element the PSI element being checked; may be null
* @return true if the element should be shown, false otherwise
*/
abstract fun hasToShow(element: PsiElement): Boolean
}
@@ -1,9 +1,9 @@
// 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
package com.intellij.codeInsight.completion.command.commands
import com.intellij.analysis.AnalysisBundle.message
import com.intellij.codeInsight.completion.commands.api.CompletionCommand
import com.intellij.codeInsight.completion.commands.core.HighlightInfoLookup
import com.intellij.codeInsight.completion.command.CompletionCommand
import com.intellij.codeInsight.completion.command.HighlightInfoLookup
import com.intellij.codeInsight.daemon.impl.DaemonProgressIndicator
import com.intellij.codeInsight.daemon.impl.HighlightInfo.IntentionActionDescriptor
import com.intellij.codeInsight.daemon.impl.HighlightVisitorBasedInspection.runAnnotatorsInGeneralHighlighting
@@ -18,9 +18,11 @@ import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil
import kotlinx.coroutines.job
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import javax.swing.Icon
@ApiStatus.Experimental
internal class DirectErrorFixCompletionCommand(
override val name: @Nls String,
override val priority: Int?,
@@ -58,7 +60,6 @@ internal class DirectErrorFixCompletionCommand(
for (fix in fixes) {
if (fix.action.text == name) {
return@jobToIndicator fix.action
break
}
}
}
@@ -1,9 +1,9 @@
// 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
// 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.analysis.AnalysisBundle.message
import com.intellij.codeInsight.completion.commands.api.CompletionCommand
import com.intellij.codeInsight.completion.commands.core.HighlightInfoLookup
import com.intellij.codeInsight.completion.command.CompletionCommand
import com.intellij.codeInsight.completion.command.HighlightInfoLookup
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler.availableFor
@@ -25,10 +25,12 @@ import com.intellij.psi.PsiFile
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil
import com.intellij.psi.util.PsiTreeUtil
import kotlinx.coroutines.job
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import javax.swing.Icon
class DirectInspectionFixCompletionCommand(
@ApiStatus.Experimental
internal class DirectInspectionFixCompletionCommand(
private val inspectionId: String,
override val name: @Nls String,
override val priority: Int?,
@@ -47,7 +49,7 @@ class DirectInspectionFixCompletionCommand(
val topLevelOffset = injectedLanguageManager.injectedToHost(psiFile, offset)
val isInjected = topLevelFile != psiFile
var action: IntentionAction? = runWithModalProgressBlocking(psiFile.project, message("scanning.scope.progress.title")) {
val action: IntentionAction? = runWithModalProgressBlocking(psiFile.project, message("scanning.scope.progress.title")) {
val profileToUse = ProjectInspectionProfileManager.Companion.getInstance(psiFile.project).getCurrentProfile()
val inspectionWrapper = InspectionProfileWrapper(profileToUse)
var inspectionTool = inspectionWrapper.inspectionProfile.getInspectionTool(inspectionId, psiFile.project)
@@ -1,20 +1,12 @@
// 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
// 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.commands.api.CommandCompletionProviderContext
import com.intellij.codeInsight.completion.commands.api.CommandProvider
import com.intellij.codeInsight.completion.commands.api.CompletionCommand
import com.intellij.codeInsight.completion.commands.core.CommandCompletionUnsupportedOperationException
import com.intellij.codeInsight.completion.commands.core.HighlightInfoLookup
import com.intellij.codeInsight.completion.command.*
import com.intellij.codeInsight.daemon.HighlightDisplayKey
import com.intellij.codeInsight.daemon.impl.*
import com.intellij.codeInsight.daemon.impl.HighlightInfo.IntentionActionDescriptor
import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass.IntentionsInfo
import com.intellij.codeInsight.daemon.impl.quickfix.CreateGetterOrSetterFix
import com.intellij.codeInsight.daemon.impl.quickfix.ExpensivePsiIntentionAction
import com.intellij.codeInsight.intention.EmptyIntentionAction
import com.intellij.codeInsight.intention.IntentionAction
import com.intellij.codeInsight.intention.IntentionManager
import com.intellij.codeInsight.intention.*
import com.intellij.codeInsight.intention.impl.CachedIntentions
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
import com.intellij.codeInsight.intention.impl.preview.IntentionPreviewUnsupportedOperationException
@@ -28,10 +20,10 @@ import com.intellij.codeInspection.ex.LocalInspectionToolWrapper
import com.intellij.codeInspection.ex.QuickFixWrapper
import com.intellij.icons.AllIcons
import com.intellij.lang.Language
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.modcommand.ModCommandService
import com.intellij.openapi.application.readAction
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
@@ -43,8 +35,11 @@ import com.intellij.openapi.progress.runBlockingCancellable
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.openapi.project.ProjectTypeService
import com.intellij.openapi.util.Iconable
import com.intellij.openapi.util.Iconable.ICON_FLAG_VISIBILITY
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.TextRange
import com.intellij.openapi.util.registry.Registry
import com.intellij.profile.codeInspection.ProjectInspectionProfileManager.Companion.getInstance
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageEditorUtil
@@ -53,10 +48,17 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.job
import org.jetbrains.annotations.ApiStatus
import java.util.function.Predicate
/**
* Provides supplying completion commands
* based on intentions, errors, and inspections within the given context.
*/
@ApiStatus.Experimental
class DirectIntentionCommandProvider : CommandProvider {
override fun getCommands(context: CommandCompletionProviderContext): List<CompletionCommand> {
if (!Registry.`is`("ide.completion.command.enabled")) return emptyList()
return runBlockingCancellable {
val originalEditor = context.originalEditor
val psiFile = context.psiFile
@@ -79,7 +81,7 @@ class DirectIntentionCommandProvider : CommandProvider {
}
val asyncInspections = if (cachedInspectionCommand == null) asyncInspectionHighlighting(psiFile, editor, offset) else null
val asyncIntentions = asyncIntentions(editor, psiFile, originalPsiFile)
val asyncIntentions = asyncIntentions(editor, psiFile, originalPsiFile, offset)
val errors = asyncErrorHighlighting?.await()
errors?.let {
@@ -128,7 +130,7 @@ class DirectIntentionCommandProvider : CommandProvider {
offset: Int,
): Deferred<List<CompletionCommand>> = async {
return@async readAction {
val intentionCommandSkipper = IntentionCommandSkipper.EP_NAME.forLanguage(psiFile.language)
val injectedLanguageManager = InjectedLanguageManager.getInstance(psiFile.project)
val topLevelFile = injectedLanguageManager.getTopLevelFile(psiFile)
val topLevelEditor = InjectedLanguageEditorUtil.getTopLevelEditor(editor)
@@ -175,13 +177,8 @@ class DirectIntentionCommandProvider : CommandProvider {
val level = ProblemDescriptorUtil.highlightTypeFromDescriptor(descriptor, severity, severityRegistrar)
for (i in 0..fixes.size - 1) {
val action = QuickFixWrapper.wrap(descriptor, i)
if (action is EmptyIntentionAction || action is ExpensivePsiIntentionAction) continue
val fix = QuickFixWrapper.unwrap(action)
if (fix != null) {
val unwrappedAction = ModCommandService.getInstance().unwrap(fix)
//skip, probably create extensions
if (unwrappedAction is CreateGetterOrSetterFix) continue
}
if (action is EmptyIntentionAction) continue
if (intentionCommandSkipper != null && intentionCommandSkipper.skip(action, psiFile, offset)) continue
if (!isInjected && !ShowIntentionActionsHandler.availableFor(topLevelFile, topLevelEditor, topLevelOffset, action)) continue
if (isInjected && !ShowIntentionActionsHandler.availableFor(psiFile, editor, offset, action)) continue
val priority = if (level.getSeverity(null) == INFORMATION) 70 else 80
@@ -245,14 +242,19 @@ class DirectIntentionCommandProvider : CommandProvider {
editor: Editor,
psiFile: PsiFile,
originalFile: PsiFile,
offset: Int,
): Deferred<List<CompletionCommand>> = async {
return@async readAction {
val intentionCommandSkipper = IntentionCommandSkipper.EP_NAME.forLanguage(psiFile.language)
val availableIntentions = IntentionManager.getInstance().getAvailableIntentions(mutableListOf(originalFile.language.id))
val actionsToShow = IntentionsInfo()
for (action in availableIntentions) {
val intentionAction = IntentionActionDelegate.unwrap(action)
val descriptor =
IntentionActionDescriptor(action, null, null, null, null, null, null, null)
IntentionActionDescriptor(action, null, null,
if (intentionAction is Iconable)
intentionAction.getIcon(ICON_FLAG_VISIBILITY)
else null, null, null, null, null)
actionsToShow.intentionsToShow.add(descriptor)
}
val dumbService = DumbService.getInstance(originalFile.project)
@@ -267,6 +269,7 @@ class DirectIntentionCommandProvider : CommandProvider {
ProgressManager.checkCanceled()
if (!dumbService.isUsableInCurrentContext(intention) ||
!intention.action.isAvailable(originalFile.project, editor, psiFile) &&
//todo temporary workaround for AI
intention.action.familyName !in ("AI Actions…") ||
!filter.test(intention.action)) {
toRemove.add(intention)
@@ -286,18 +289,15 @@ class DirectIntentionCommandProvider : CommandProvider {
intentionsCache.wrapAndUpdateActions(actionsToShow, false)
val result = mutableListOf<CompletionCommand>()
for (intention in intentionsCache.intentions) {
if (intention.action is EmptyIntentionAction || intention.action is ExpensivePsiIntentionAction) continue
if (intention.action is EmptyIntentionAction ||
intentionCommandSkipper != null && intentionCommandSkipper.skip(intention.action, psiFile, offset)) continue
val intentionCommand =
IntentionCompletionCommand(intention, 50, AllIcons.Actions.IntentionBulbGrey, null)
IntentionCompletionCommand(intention, 50, intention.icon ?: AllIcons.Actions.IntentionBulbGrey, null)
result.add(intentionCommand)
}
return@readAction result
}
}
override fun getId(): String {
return "DirectIntentionCommandProvider"
}
}
internal fun getLineRange(psiFile: PsiFile, offset: Int): TextRange {
@@ -343,3 +343,13 @@ private fun getInspectionTools(profile: InspectionProfileWrapper, file: PsiFile)
}
return enabled
}
@ApiStatus.Experimental
interface IntentionCommandSkipper {
companion object {
internal val EP_NAME: LanguageExtension<IntentionCommandSkipper> = LanguageExtension<IntentionCommandSkipper>("com.intellij.codeInsight.completion.intention.skipper")
}
fun skip(action: CommonIntentionAction, psiFile: PsiFile, offset: Int): Boolean = false
}
@@ -0,0 +1,35 @@
// 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.lang.LanguageExtension
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.util.registry.Registry
import org.jetbrains.annotations.ApiStatus
/**
* An implementation of the CommandProvider interface that supplies completion commands
* registered through extension points ('com.intellij.codeInsight.completion.applicable.command') for specific programming languages.
*/
@ApiStatus.Experimental
internal class ExtensionPointCommandProvider : CommandProvider, DumbAware {
override fun getCommands(context: CommandCompletionProviderContext): List<CompletionCommand> {
if (!Registry.`is`("ide.completion.command.enabled")) 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<ApplicableCompletionCommand> = LanguageExtension<ApplicableCompletionCommand>("com.intellij.codeInsight.completion.applicable.command")
@@ -1,16 +1,18 @@
// 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
// 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.commands.api.CompletionCommand
import com.intellij.codeInsight.completion.commands.core.HighlightInfoLookup
import com.intellij.codeInsight.completion.command.CompletionCommand
import com.intellij.codeInsight.completion.command.HighlightInfoLookup
import com.intellij.codeInsight.intention.impl.IntentionActionWithTextCaching
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import javax.swing.Icon
class IntentionCompletionCommand(
@ApiStatus.Experimental
internal class IntentionCompletionCommand(
private val intentionAction: IntentionActionWithTextCaching,
override val priority: Int?,
override val icon: Icon?,
@@ -1,10 +1,17 @@
// 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
// 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.AbstractActionCompletionCommand
import com.intellij.idea.ActionsBundle
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus.Experimental
/**
* A command that executes the "Comment by Line Comment" action
* within the context of code completion.
*/
@Experimental
class LineCommentCompletionCommand : AbstractActionCompletionCommand("CommentByLineComment",
"Comment line",
ActionsBundle.actionText("CommentByLineComment"),
@@ -1,11 +1,17 @@
// 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
// 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.AbstractActionCompletionCommand
import com.intellij.idea.ActionsBundle
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus.Experimental
/**
* A command that triggers the "Optimize Imports" action within the context of code completion.
*/
@Experimental
class OptimizeImportCompletionCommand : AbstractActionCompletionCommand("OptimizeImports",
"Optimize imports",
ActionsBundle.message("action.OptimizeImports.text"),
@@ -1,14 +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
// 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.CodeInsightBundle
import com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
import com.intellij.ide.actions.ActivateToolWindowAction.Manager
import com.intellij.java.JavaBundle
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
/**
* A completion command, which triggers the "Project" tool window in the IDE.
*/
@ApiStatus.Experimental
class ProjectViewCompletionCommand : AbstractActionCompletionCommand(Manager.getActionIdForToolWindow("Project"),
"Project tool",
JavaBundle.message("command.completion.project.tool.text"),
CodeInsightBundle.message("command.completion.project.tool.text"),
null,
-150) {
override fun supportsReadOnly(): Boolean = true
@@ -1,15 +1,16 @@
// 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
// 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.java.JavaBundle
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
class JavaPsiElementCommentCompletionCommand : AbstractActionCompletionCommand("CommentByLineComment",
"Comment element",
JavaBundle.message("java.psi.element.comment.completion.command.text"),
null) {
class PsiElementCommentCompletionCommand : AbstractActionCompletionCommand("CommentByLineComment",
"Comment element",
CodeInsightBundle.message("command.completion.psi.element.comment.text"),
null) {
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
if (!super.isApplicable(offset, psiFile, editor)) return false
@@ -1,13 +1,19 @@
// 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
// 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.java.JavaBundle
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
/**
* This command enables triggering the "Recent Files" action during code completion.
*/
@ApiStatus.Experimental
class RecentFilesCompletionCommand : AbstractActionCompletionCommand("RecentFiles",
"Recent files",
JavaBundle.message("command.completion.recent.files.text"),
CodeInsightBundle.message("command.completion.recent.files.text"),
null,
-150) {
override fun supportsReadOnly(): Boolean = true
@@ -1,16 +1,19 @@
// 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
// 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.commands.api.getTargetContext
import com.intellij.codeInsight.completion.command.AbstractActionCompletionCommand
import com.intellij.codeInsight.completion.command.getTargetContext
import com.intellij.idea.ActionsBundle
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiNamedElement
import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental
class RenameActionCompletionCommand : AbstractActionCompletionCommand(IdeActions.ACTION_RENAME,
"rename",
"Rename",
ActionsBundle.message("action.RenameElement.text"),
null) {
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
@@ -1,7 +1,8 @@
// 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
// 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.commands.api.*
import com.intellij.analysis.AnalysisBundle
import com.intellij.codeInsight.completion.command.*
import com.intellij.codeInsight.daemon.MergeableLineMarkerInfo
import com.intellij.codeInsight.daemon.impl.GutterIntentionAction
import com.intellij.codeInsight.daemon.impl.IntentionActionFilter
@@ -23,13 +24,19 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Iconable
import com.intellij.openapi.util.NlsSafe
import com.intellij.openapi.util.text.StringUtil
import com.intellij.platform.ide.progress.runWithModalProgressBlocking
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.util.containers.JBIterable.from
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.annotations.Nls
import java.util.function.Predicate
import javax.swing.Icon
/**
* A provider for run marker-related completion commands in the code editor.
*/
@ApiStatus.Experimental
class SimpleRunMarkerCommandProvider : CommandProvider {
override fun getCommands(context: CommandCompletionProviderContext): List<CompletionCommand> {
return runBlockingCancellable {
@@ -88,10 +95,6 @@ class SimpleRunMarkerCommandProvider : CommandProvider {
return@runBlockingCancellable commands
}
}
override fun getId(): String {
return "RunMarkerCommandProvider"
}
}
private fun collectActions(
@@ -137,7 +140,9 @@ private class RunMarkerCompletionCommand(
get() = ""
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
val (_, collectedActions) = collectActions(psiFile, offsetElement, psiFile.project)
val (_, collectedActions) = runWithModalProgressBlocking(psiFile.project, AnalysisBundle.message("scanning.scope.progress.title")) {
collectActions(psiFile, offsetElement, psiFile.project)
}
val cachedIntentions = CachedIntentions(psiFile.project, psiFile, editor)
val intentionsInfo = ShowIntentionsPass.IntentionsInfo()
intentionsInfo.guttersToShow.addAll(collectedActions)
@@ -748,5 +748,22 @@
qualifiedName="com.intellij.microservices.featuresAvailabilityProvider" dynamic="true"/>
<extensionPoint interface="com.intellij.microservices.oas.serialization.OasSerializationCompatibilityProvider"
qualifiedName="com.intellij.microservices.oasSerializationCompatibilityProvider" dynamic="true"/>
<extensionPoint qualifiedName="com.intellij.codeInsight.completion.command.factory"
beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
<with attribute="implementationClass" implements="com.intellij.codeInsight.completion.command.CommandCompletionFactory"/>
</extensionPoint>
<extensionPoint qualifiedName="com.intellij.codeInsight.completion.command.provider"
beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
<with attribute="implementationClass" implements="com.intellij.codeInsight.completion.command.CommandProvider"/>
</extensionPoint>
<extensionPoint qualifiedName="com.intellij.codeInsight.completion.applicable.command"
beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
<with attribute="implementationClass" implements="com.intellij.codeInsight.completion.command.ApplicableCompletionCommand"/>
</extensionPoint>
<extensionPoint qualifiedName="com.intellij.codeInsight.completion.intention.skipper"
beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
<with attribute="implementationClass" implements="com.intellij.codeInsight.completion.command.commands.IntentionCommandSkipper"/>
</extensionPoint>
</extensionPoints>
</idea-plugin>
@@ -726,7 +726,8 @@
id="liveTemplates" order="first"/>
<completion.contributor id="reference" language="any" order="last"
implementationClass="com.intellij.codeInsight.completion.ReferenceBasedCompletionContributor"/>
<completion.contributor language="any" id="commands" order="first, after liveTemplates"
implementationClass="com.intellij.codeInsight.completion.command.CommandCompletionContributor"/>
<applicationService serviceImplementation="com.intellij.execution.console.ConsoleFoldingSettings"/>
<applicationSettings service="com.intellij.execution.console.ConsoleFoldingSettings"/>
@@ -736,6 +737,13 @@
<lookup.charFilter implementation="com.intellij.codeInsight.completion.DefaultCharFilter" order="last" id="default"/>
<lookup.charFilter implementation="com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceCharFilter" id="fileRef"
order="before completion"/>
<lookup.charFilter implementation="com.intellij.codeInsight.completion.command.CommandCompletionCharFilter" order="first" id="commandCompletion"/>
<lookup.customizer implementation="com.intellij.codeInsight.completion.command.CommandCompletionLookupCustomizer" order="last" id="commandCompletionLookupCustomizer"/>
<codeInsight.completion.command.provider language="" implementationClass="com.intellij.codeInsight.completion.command.commands.ExtensionPointCommandProvider" />
<codeInsight.completion.command.provider language="" implementationClass="com.intellij.codeInsight.completion.command.commands.DirectIntentionCommandProvider" order="last"/>
<nonWriteAccessTypedHandler implementation="com.intellij.codeInsight.completion.command.CommandCompletionNonWriteAccessTypedHandler" id="commandCompletion"/>
<searchEverywhereClassifier implementation="com.intellij.ide.actions.DefaultSearchEverywhereClassifier"/>
<gotoFileContributor implementation="com.intellij.ide.util.gotoByName.DefaultFileNavigationContributor"/>
@@ -1684,6 +1692,11 @@
<registryKey defaultValue="2000"
description="Amount in milliseconds to wait for completion contributors till decide whether to insert the single item"
key="ide.completion.auto.insert.item.timeout"/>
<registryKey key="ide.completion.command.enabled" defaultValue="false"
description="Enable experimental support command completion" restartRequired="true"/>
<registryKey key="ide.completion.command.full.line.enabled" defaultValue="false"
description="Enable experimental support command completion" restartRequired="true"/>
<indexableEntityProvider implementation="com.intellij.util.indexing.roots.LibraryPropertyEntityIndexableEntityProvider"/>
<indexableEntityProvider implementation="com.intellij.util.indexing.roots.CustomSourceRootPropertyIndexableEntityProvider"/>
<indexableEntityProvider implementation="com.intellij.util.indexing.roots.ModuleDependencyEntitiesIndexableEntityProvider"/>
@@ -1862,5 +1875,7 @@
topic="com.intellij.platform.backend.workspace.WorkspaceModelChangeListener"/>
<listener class="com.intellij.workspaceModel.ide.impl.legacyBridge.module.ModuleBridgeCleaner"
topic="com.intellij.platform.backend.workspace.WorkspaceModelChangeListener"/>
<listener class="com.intellij.codeInsight.completion.command.CommandCompletionListener"
topic="com.intellij.codeInsight.lookup.LookupManagerListener"/>
</projectListeners>
</idea-plugin>
</idea-plugin>