[java-completion] IDEA-359174 command completion

- add new actions: getter/setter/find usages, introduce variable
- fix set of actions

GitOrigin-RevId: 9f733d5b38cb8333c3c83e1a0f3d8ea43d7bcbb1
This commit is contained in:
Mikhail Pyltsin
2024-12-03 21:33:19 +00:00
committed by intellij-monorepo-bot
parent 5238e08e3f
commit 94efe9cb4d
11 changed files with 187 additions and 54 deletions
@@ -134,6 +134,8 @@ create.write.only.property.from.usage.full.text=Create write-only property ''{0}
create.getter=Create Getter
# suppress inspection "UnusedProperty"
create.setter=Create Setter
# suppress inspection "UnusedProperty"
create.getter.setter=Create Getter/Setter
create.annotation.family=Add annotation to declaration
create.annotation.text=Annotate as @{0}
defer.final.assignment.with.temp.family=Defer final assignment with temp
@@ -1505,6 +1505,12 @@
<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.CommentCompletionCommand" />
<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" />
<weigher implementationClass="com.intellij.codeInsight.completion.LoggerWeigher" key="completion" id="logger"/>
@@ -1,9 +1,15 @@
// 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.codeInsight.TargetElementUtil
import com.intellij.modcommand.ModHighlight.HighlightInfo
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.psi.PsiElement
import com.intellij.psi.PsiFile
import org.jetbrains.annotations.ApiStatus
@@ -41,4 +47,30 @@ abstract class OldCompletionCommand : CompletionCommand() {
override fun execute(offset: Int, psiFile: PsiFile) {
execute(offset, psiFile, null)
}
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
}
internal fun getTargetContext(offset: Int, editor: Editor): PsiElement? {
try {
val util = TargetElementUtil.getInstance()
return util.findTargetElement(editor, util.getReferenceSearchFlags(), offset)
}
catch (e: IndexNotReadyException) {
return null;
}
}
}
@@ -114,7 +114,8 @@ class CommandCompletionService(
if (highlightInfoContainer == null || actionContainers == null) return null
if (document.immutableCharSequence.hashCode() != highlightInfoContainer.hashcode) return null
if (highlightInfoContainer.offset != offset) return null
val actionContainer = actionContainers.firstOrNull { it.hashcode == highlightInfoContainer.hashcode } ?: return null
val actionContainer = actionContainers.firstOrNull { it.hashcode == highlightInfoContainer.hashcode && it.offset == offset }
?: return null
val allActions = mutableListOf<IntentionActionWithTextCaching>()
allActions.addAll(actionContainer.highlighters.allActions)
val revertMap: MutableMap<IntentionAction, RangeHighlighterEx> = mutableMapOf()
@@ -1,16 +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
import com.intellij.codeInsight.TargetElementUtil
import com.intellij.codeInsight.completion.commands.api.OldCompletionCommand
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.annotations.Nls
@@ -29,13 +25,7 @@ abstract class AbstractActionCompletionCommand(
val action = action ?: return false
if (editor == null) return false
val context = getTargetContext(offset, editor)
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()
val dataContext = dataContext(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)
@@ -44,16 +34,6 @@ abstract class AbstractActionCompletionCommand(
return event.presentation.isEnabled && event.presentation.isVisible
}
private fun getTargetContext(offset: Int, editor: Editor): PsiElement? {
try {
val util = TargetElementUtil.getInstance()
return util.findTargetElement(editor, util.getReferenceSearchFlags(), offset)
}
catch (e: IndexNotReadyException) {
return null;
}
}
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
val action = action ?: return
if (editor == null) return
@@ -61,7 +41,7 @@ abstract class AbstractActionCompletionCommand(
val presentation: Presentation = action.templatePresentation.clone()
val event = AnActionEvent.createEvent(action, dataContext, presentation, ActionPlaces.ACTION_PLACE_QUICK_LIST_POPUP_ACTION, ActionUiKind.NONE, null)
if (ActionUtil.lastUpdateAndCheckDumb(action, event, false)) {
ActionUtil.performActionDumbAwareWithCallbacks(action, event);
ActionUtil.performActionDumbAwareWithCallbacks(action, event)
}
}
@@ -0,0 +1,53 @@
// 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.OldCompletionCommand
import com.intellij.codeInsight.daemon.QuickFixBundle
import com.intellij.codeInsight.intention.QuickFixFactory
import com.intellij.codeInsight.intention.impl.ShowIntentionActionsHandler
import com.intellij.modcommand.ActionContext
import com.intellij.openapi.editor.Editor
import com.intellij.psi.PsiField
import com.intellij.psi.PsiFile
import com.intellij.psi.util.PsiTreeUtil
import javax.swing.Icon
class GenerateGetterSetterHandleCompletionCommand :
BaseGenerateGetterSetterHandleCompletionCommand(true, true, "Create Getter/Setter",
QuickFixBundle.message("create.getter.setter"))
class GenerateSetterHandleCompletionCommand :
BaseGenerateGetterSetterHandleCompletionCommand(false, true, "Create Setter",
QuickFixBundle.message("create.setter"))
class GenerateGetterHandleCompletionCommand :
BaseGenerateGetterSetterHandleCompletionCommand(true, false, "Create Getter",
QuickFixBundle.message("create.getter"))
abstract class BaseGenerateGetterSetterHandleCompletionCommand(
val generateGetter: Boolean,
val generateSetter: Boolean,
override val name: String,
override val i18nName: String,
) : OldCompletionCommand() {
override val icon: Icon? = null
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
val element = getContext(offset, psiFile) ?: return false
val field = PsiTreeUtil.getParentOfType(element, PsiField::class.java) ?: return false
val action = QuickFixFactory.getInstance().createCreateGetterOrSetterFix(generateGetter, generateSetter, field)
val context = ActionContext.from(editor, psiFile).withElement(field)
return action.asModCommandAction()?.getPresentation(context) != null
}
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
val element = getContext(offset, psiFile) ?: return
val field = PsiTreeUtil.getParentOfType(element, PsiField::class.java) ?: return
val action = QuickFixFactory.getInstance().createCreateGetterOrSetterFix(generateGetter, generateSetter, field)
if (editor == null) return
@Suppress("DialogTitleCapitalization")
ShowIntentionActionsHandler.chooseActionAndInvoke(psiFile, editor, action, action.text)
}
}
@@ -1,8 +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
import com.intellij.codeInsight.completion.commands.api.CompletionCommand
import com.intellij.codeInsight.completion.commands.api.CommandProvider
import com.intellij.codeInsight.completion.commands.api.CompletionCommand
import com.intellij.codeInsight.generation.actions.BaseGenerateAction
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
@@ -39,6 +39,7 @@ class GenerateCommandProvider : CommandProvider {
}
}
return generateActions.map { GenerateCompletionCommand(it) }
.filter { it.isApplicable(offset, psiFile, editor) }
}
override fun getId(): String {
@@ -2,6 +2,7 @@
package com.intellij.codeInsight.completion.commands.impl
import com.intellij.codeInsight.completion.commands.api.OldCompletionCommand
import com.intellij.codeInsight.completion.commands.impl.AbstractActionCompletionCommand.Companion.isApplicableToProject
import com.intellij.codeInsight.generation.actions.BaseGenerateAction
import com.intellij.ide.DataManager
import com.intellij.java.JavaBundle
@@ -25,6 +26,9 @@ class GenerateCompletionCommand(private val action: BaseGenerateAction) : OldCom
get() = null
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
if (!isApplicableToProject(offset, psiFile)) {
return false
}
val context = psiFile.findElementAt(offset) ?: return false
if (editor == null) return false
val dataContext = SimpleDataContext.builder()
@@ -1,50 +1,30 @@
// 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.icons.AllIcons
import com.intellij.ide.util.EditSourceUtil
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
import org.jetbrains.annotations.Nls
import javax.swing.Icon
class GoToDeclarationCompletionCommand : CompletionCommand() {
override val name: String
get() = "Go to Declaration"
class GoToDeclarationCompletionCommand : AbstractActionCompletionCommand("GotoDeclarationOnly",
"Go to Declaration",
ActionsBundle.message("action.GotoDeclarationOnly.text"),
null) {
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
return super.isApplicable(offset, psiFile, editor) && hasToShow(offset, psiFile)
}
override val i18nName: @Nls String
get() = ActionsBundle.message("action.GotoDeclaration.text")
override val icon: Icon
get() = AllIcons.Ide.ExternalLinkArrowWhite // Use the appropriate icon
override fun isApplicable(offset: Int, psiFile: PsiFile): Boolean {
private fun hasToShow(offset: Int, psiFile: PsiFile): Boolean {
val context = (getContext(offset, psiFile)) ?: return false
return canNavigateToDeclaration(context)
}
override fun execute(offset: Int, psiFile: PsiFile) {
val element = getContext(offset, psiFile) ?: return
if (element !is PsiIdentifier) {
return
}
val javaRef = PsiTreeUtil.getParentOfType(element, PsiJavaCodeReferenceElement::class.java)
val psiElement = javaRef?.resolve()
if (psiElement != null) {
EditSourceUtil.navigateToPsiElement(psiElement)
}
}
private fun canNavigateToDeclaration(context: PsiElement): Boolean {
if (context !is PsiIdentifier) {
return false;
return false
}
val javaRef = PsiTreeUtil.getParentOfType(context, PsiJavaCodeReferenceElement::class.java)
val psiElement = javaRef?.resolve()
@@ -0,0 +1,50 @@
// 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.OldCompletionCommand
import com.intellij.icons.AllIcons
import com.intellij.lang.ContextAwareActionHandler
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 com.intellij.refactoring.JavaRefactoringActionHandlerFactory
import com.intellij.refactoring.RefactoringBundle
import com.intellij.refactoring.actions.IntroduceVariableAction
import org.jetbrains.annotations.Nls
import javax.swing.Icon
class IntroduceVariableCommand : OldCompletionCommand() {
override val name: String
get() = "Introduce variable"
override val i18nName: @Nls String
get() = RefactoringBundle.message("introduce.variable.title")
override val icon: Icon
get() = AllIcons.Nodes.Variable
override fun isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
if (editor == null) return false
val factory = JavaRefactoringActionHandlerFactory.getInstance()
val variableHandler = factory.createIntroduceVariableHandler()
if (variableHandler is ContextAwareActionHandler) {
return variableHandler.isAvailableForQuickList(editor, psiFile, dataContext(psiFile, editor, getTargetContext(offset, editor)))
}
return false
}
override fun execute(offset: Int, psiFile: PsiFile, editor: Editor?) {
val action = IntroduceVariableAction()
if (editor == null) return
val context = getTargetContext(offset, editor)
val dataContext = dataContext(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)
action.update(event)
ActionUtil.performActionDumbAwareWithCallbacks(action, event)
}
}
@@ -0,0 +1,24 @@
// 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.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 isApplicable(offset: Int, psiFile: PsiFile, editor: Editor?): Boolean {
return super.isApplicable(offset, psiFile, editor) && hasToShow(getContext(offset, 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)
}
}