diff --git a/java/java-impl/resources/META-INF/JavaPlugin.xml b/java/java-impl/resources/META-INF/JavaPlugin.xml
index 283abb0ed833..6b1758815cb8 100644
--- a/java/java-impl/resources/META-INF/JavaPlugin.xml
+++ b/java/java-impl/resources/META-INF/JavaPlugin.xml
@@ -1498,6 +1498,7 @@
+
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/api/CompletionCommand.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/api/CompletionCommand.kt
index a20b94e7f587..eec3d9f02d56 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/api/CompletionCommand.kt
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/api/CompletionCommand.kt
@@ -100,49 +100,49 @@ abstract class ApplicableCompletionCommand : CompletionCommand() {
*/
@RequiresReadLock
abstract fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean
-
- /**
- * 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 dataContext(
- 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
- }
- }
}
+
+/**
+ * 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 dataContext(
+ 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
+ }
+}
\ No newline at end of file
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/AbstractActionCompletionCommand.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/AbstractActionCompletionCommand.kt
index fc7a1d098d48..c1500e9f4111 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/AbstractActionCompletionCommand.kt
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/AbstractActionCompletionCommand.kt
@@ -2,6 +2,8 @@
package com.intellij.codeInsight.completion.commands.impl
import com.intellij.codeInsight.completion.commands.api.ApplicableCompletionCommand
+import com.intellij.codeInsight.completion.commands.api.dataContext
+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
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/DirectIntentionCommandProvider.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/DirectIntentionCommandProvider.kt
index 23cd4442564b..88cddbf5dbb8 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/DirectIntentionCommandProvider.kt
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/DirectIntentionCommandProvider.kt
@@ -11,6 +11,7 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfo.IntentionActionDescrip
import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass.IntentionsInfo
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.impl.CachedIntentions
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
@@ -52,6 +53,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.job
+import java.util.function.Predicate
class DirectIntentionCommandProvider : CommandProvider {
override fun getCommands(
@@ -250,6 +252,7 @@ class DirectIntentionCommandProvider : CommandProvider {
originalFile: PsiFile,
): Deferred> = async {
return@async readAction {
+
val availableIntentions = IntentionManager.getInstance().getAvailableIntentions(mutableListOf(originalFile.language.id))
val actionsToShow = IntentionsInfo()
for (action in availableIntentions) {
@@ -260,12 +263,17 @@ class DirectIntentionCommandProvider : CommandProvider {
val dumbService = DumbService.getInstance(originalFile.project)
val intentionsCache = CachedIntentions(originalFile.project, psiFile, editor)
var toRemove = mutableListOf()
+ val filter = Predicate { action: IntentionAction? ->
+ IntentionActionFilter.EXTENSION_POINT_NAME.extensionList.all { f: IntentionActionFilter -> action != null && f.accept(action, psiFile, editor.caretModel.offset) }
+ }
+
for (intention in actionsToShow.intentionsToShow) {
try {
ProgressManager.checkCanceled()
if (!dumbService.isUsableInCurrentContext(intention) ||
!intention.action.isAvailable(originalFile.project, editor, psiFile) &&
- intention.action.familyName !in ("AI Actions…")) {
+ intention.action.familyName !in ("AI Actions…") ||
+ !filter.test(intention.action)) {
toRemove.add(intention)
}
}
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/GenerateCommandProvider.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/GenerateCommandProvider.kt
index fda8ffa3ac1d..543998f62710 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/GenerateCommandProvider.kt
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/GenerateCommandProvider.kt
@@ -3,10 +3,11 @@ package com.intellij.codeInsight.completion.commands.impl
import com.intellij.codeInsight.completion.commands.api.CommandProvider
import com.intellij.codeInsight.completion.commands.api.CompletionCommand
+import com.intellij.codeInsight.completion.commands.api.dataContext
+import com.intellij.codeInsight.completion.commands.api.getTargetContext
import com.intellij.codeInsight.generation.actions.BaseGenerateAction
import com.intellij.lang.injection.InjectedLanguageManager
import com.intellij.openapi.actionSystem.*
-import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.actionSystem.impl.Utils
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.DumbAware
@@ -29,11 +30,8 @@ class GenerateCommandProvider : CommandProvider, DumbAware {
): List {
if (InjectedLanguageManager.getInstance(psiFile.project).isInjectedFragment(psiFile)) return emptyList()
val element = psiFile.findElementAt(offset)
- val dataContext = SimpleDataContext.builder()
- .add(CommonDataKeys.PROJECT, project)
- .add(CommonDataKeys.EDITOR, editor)
- .add(CommonDataKeys.PSI_ELEMENT, element)
- .build()
+ val context = getTargetContext(offset, editor)
+ val dataContext = dataContext(psiFile, editor, context)
val actionEvent = AnActionEvent.createEvent(dataContext, null, ActionPlaces.UNKNOWN, ActionUiKind.NONE, null)
actionEvent.updateSession = UpdateSession.EMPTY
Utils.initUpdateSession(actionEvent)
@@ -44,7 +42,7 @@ class GenerateCommandProvider : CommandProvider, DumbAware {
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 && session.presentation(o!!).isEnabledAndVisible }
+ .filter { o: AnAction? -> o !is Separator && o != null && session.presentation(o).isEnabledAndVisible }
for (action in activeActions) {
if (action is BaseGenerateAction) {
generateActions.add(action)
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/IntroduceVariableCommand.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/IntroduceVariableCommand.kt
index 12d6a9b2e3d2..4712ee505996 100644
--- a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/IntroduceVariableCommand.kt
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/IntroduceVariableCommand.kt
@@ -3,6 +3,8 @@ package com.intellij.codeInsight.completion.commands.impl
import com.intellij.analysis.AnalysisBundle.message
import com.intellij.codeInsight.completion.commands.api.ApplicableCompletionCommand
+import com.intellij.codeInsight.completion.commands.api.dataContext
+import com.intellij.codeInsight.completion.commands.api.getTargetContext
import com.intellij.icons.AllIcons
import com.intellij.lang.ContextAwareActionHandler
import com.intellij.openapi.actionSystem.ActionPlaces
diff --git a/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/SimpleRunMarkerCommandProvider.kt b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/SimpleRunMarkerCommandProvider.kt
new file mode 100644
index 000000000000..fff0eb29fc08
--- /dev/null
+++ b/java/java-impl/src/com/intellij/codeInsight/completion/commands/impl/SimpleRunMarkerCommandProvider.kt
@@ -0,0 +1,155 @@
+// 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.CommandProvider
+import com.intellij.codeInsight.completion.commands.api.CompletionCommand
+import com.intellij.codeInsight.completion.commands.api.dataContext
+import com.intellij.codeInsight.completion.commands.api.getTargetContext
+import com.intellij.codeInsight.daemon.MergeableLineMarkerInfo
+import com.intellij.codeInsight.daemon.impl.GutterIntentionAction
+import com.intellij.codeInsight.daemon.impl.IntentionActionFilter
+import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass
+import com.intellij.codeInsight.intention.IntentionAction
+import com.intellij.codeInsight.intention.impl.CachedIntentions
+import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
+import com.intellij.execution.lineMarker.LineMarkerActionWrapper
+import com.intellij.execution.lineMarker.RunLineMarkerContributor.Info
+import com.intellij.execution.lineMarker.RunLineMarkerProvider
+import com.intellij.openapi.actionSystem.*
+import com.intellij.openapi.actionSystem.impl.Utils
+import com.intellij.openapi.editor.Editor
+import com.intellij.openapi.keymap.KeymapUtil
+import com.intellij.openapi.progress.runBlockingCancellable
+import com.intellij.openapi.project.DumbService
+import com.intellij.openapi.project.PossiblyDumbAware
+import com.intellij.openapi.project.Project
+import com.intellij.openapi.util.Iconable
+import com.intellij.openapi.util.NlsSafe
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.util.containers.JBIterable.from
+import org.jetbrains.annotations.Nls
+import java.util.function.Predicate
+import javax.swing.Icon
+
+class SimpleRunMarkerCommandProvider : CommandProvider {
+ override fun getCommands(
+ project: Project,
+ editor: Editor,
+ offset: Int,
+ psiFile: PsiFile,
+ originalEditor: Editor,
+ originalOffset: Int,
+ originalFile: PsiFile,
+ ): List {
+ return runBlockingCancellable {
+ var (currentElement, collectedActions) = collectActions(psiFile, offset, project)
+ @Suppress("SENSELESS_COMPARISON")
+ if (currentElement == null) return@runBlockingCancellable emptyList()
+ val filter = Predicate { action: IntentionAction? ->
+ IntentionActionFilter.EXTENSION_POINT_NAME.extensionList.all { f: IntentionActionFilter? ->
+ f != null && action != null && f.accept(action, psiFile, offset)
+ }
+ }
+ val commands = mutableListOf()
+ val myGuttersRaw: List = collectedActions
+ val dumbService = DumbService.getInstance(project)
+ val group = DefaultActionGroup(ArrayList(LinkedHashSet(myGuttersRaw)))
+ val context = getTargetContext(offset, editor)
+ val dataContext = dataContext(psiFile, editor, context)
+ val actionEvent = AnActionEvent.createEvent(dataContext, null, ActionPlaces.UNKNOWN, ActionUiKind.NONE, null)
+ actionEvent.updateSession = UpdateSession.EMPTY
+ Utils.initUpdateSession(actionEvent)
+
+ val session = actionEvent.updateSession
+ val activeActions = from(session.expandedChildren(group))
+ .filter { dumbService.isUsableInCurrentContext(it) }
+ .filter { o: AnAction? -> o !is Separator && o != null && session.presentation(o).isEnabledAndVisible }
+
+ for (action in activeActions) {
+ try {
+ if (action !is LineMarkerActionWrapper) continue
+ val delegate = action.delegate
+ val presentation: Presentation = action.templatePresentation.clone()
+ val runLineMarkerContributorInfo = Info(delegate)
+ val text = runLineMarkerContributorInfo.tooltipProvider.apply(currentElement)
+ if (text?.isEmpty() == true) continue
+ val intentionAction = GutterIntentionAction(action, 0, false)
+ intentionAction.updateFromPresentation(presentation)
+ if (!filter.test(intentionAction)) continue
+ val shortcutText = KeymapUtil.getFirstKeyboardShortcutText(intentionAction.action)
+ commands.add(RunMarkerCompletionCommand(currentElement.textRange.startOffset, text, intentionAction.getIcon(Iconable.ICON_FLAG_VISIBILITY), shortcutText))
+ }
+ catch (_: Exception) {
+
+ }
+ }
+
+ return@runBlockingCancellable commands
+ }
+ }
+
+ override fun getId(): String {
+ return "RunMarkerCommandProvider"
+ }
+}
+
+private fun collectActions(
+ psiFile: PsiFile,
+ offset: Int,
+ project: Project,
+): Pair> {
+ val runLineMarkerProvider = RunLineMarkerProvider()
+ var currentElement = psiFile.findElementAt(offset)
+ val dumbService = DumbService.getInstance(project)
+ var collectedActions = mutableListOf()
+ while (currentElement != null) {
+ for (child in currentElement.children) {
+ val lineMarkerInfo = runLineMarkerProvider.getLineMarkerInfo(child)
+ if (lineMarkerInfo is MergeableLineMarkerInfo) {
+ val r = lineMarkerInfo.createGutterRenderer()
+ val group: ActionGroup? = r.popupMenuActions
+ if (group == null) continue
+ val children = if (group is DefaultActionGroup) group.getChildren(ActionManager.getInstance()) else group.getChildren(null)
+ for (action in children) {
+ if (!dumbService.isUsableInCurrentContext(action)) continue
+ collectedActions.add(action)
+ }
+ }
+ }
+ if (collectedActions.isNotEmpty()) break
+ currentElement = currentElement.parent
+ }
+ return Pair(currentElement, collectedActions)
+}
+
+private class RunMarkerCompletionCommand(
+ private val offsetElement: Int,
+ @NlsSafe override val name: String,
+ override val icon: Icon?,
+ override val additionalInfo: String?,
+) : CompletionCommand(), PossiblyDumbAware {
+ override val i18nName: @Nls String
+ get() = ""
+
+ override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
+ var (_, collectedActions) = collectActions(psiFile, offsetElement, psiFile.project)
+ val cachedIntentions = CachedIntentions(psiFile.project, psiFile, editor)
+ val intentionsInfo = ShowIntentionsPass.IntentionsInfo()
+ intentionsInfo.guttersToShow.addAll(collectedActions)
+ cachedIntentions.wrapAndUpdateActions(intentionsInfo, true)
+ cachedIntentions.wrapAndUpdateGutters()
+ var intentionAction: IntentionAction? = null
+ for (caching in cachedIntentions.gutters) {
+ if (caching.text == name) {
+ intentionAction = caching.action
+ break
+ }
+ }
+ if (intentionAction == null) return
+ if (editor == null) return
+ if (ShowIntentionActionsHandler.availableFor(psiFile, editor, offset, intentionAction)) {
+ ShowIntentionActionsHandler.chooseActionAndInvoke(psiFile, editor, intentionAction, name)
+ }
+ }
+}