mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-completion] IDEA-359174 first draft of command completion
- run gutters GitOrigin-RevId: cf7e3d0190149dd6ca9406d867bf8df6bc58e4bf
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ec3e5d135c
commit
a77f2ccd60
@@ -1498,6 +1498,7 @@
|
||||
<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" />
|
||||
|
||||
+45
-45
@@ -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
|
||||
}
|
||||
}
|
||||
+2
@@ -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
|
||||
|
||||
+9
-1
@@ -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<List<CompletionCommand>> = 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<IntentionActionDescriptor>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-7
@@ -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<CompletionCommand> {
|
||||
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)
|
||||
|
||||
+2
@@ -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
|
||||
|
||||
+155
@@ -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<CompletionCommand> {
|
||||
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<CompletionCommand>()
|
||||
val myGuttersRaw: List<AnAction> = collectedActions
|
||||
val dumbService = DumbService.getInstance(project)
|
||||
val group = DefaultActionGroup(ArrayList<AnAction?>(LinkedHashSet<AnAction?>(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<PsiElement?, MutableList<AnAction>> {
|
||||
val runLineMarkerProvider = RunLineMarkerProvider()
|
||||
var currentElement = psiFile.findElementAt(offset)
|
||||
val dumbService = DumbService.getInstance(project)
|
||||
var collectedActions = mutableListOf<AnAction>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user