mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-05-06 05:10:22 +07:00
[mod-commands] Basic support for templates and member chooser
GitOrigin-RevId: e7df218ea71800e71409de20474f43eb758e41de
This commit is contained in:
committed by
intellij-monorepo-bot
parent
95f104864a
commit
4c7609bdd7
@@ -1,13 +1,17 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.refactoring.changeSignature;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.DefineParamsDefaultValueAction;
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix;
|
||||
import com.intellij.codeInsight.template.Template;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderImpl;
|
||||
import com.intellij.codeInsight.template.impl.TextExpression;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
@@ -279,7 +283,7 @@ class DetectedJavaChangeInfo extends JavaChangeInfoImpl {
|
||||
int i = ArrayUtil.find(parameters, info);
|
||||
return expressions[i];
|
||||
}).toArray(PsiExpression[]::new);
|
||||
DefineParamsDefaultValueAction.startTemplate(project, editor, toBeDefault, delegate);
|
||||
startTemplate(project, editor, toBeDefault, delegate);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -312,4 +316,24 @@ class DetectedJavaChangeInfo extends JavaChangeInfoImpl {
|
||||
};
|
||||
dialog.showAndGet();
|
||||
}
|
||||
|
||||
private static void startTemplate(@NotNull Project project,
|
||||
Editor editor,
|
||||
PsiExpression[] argsToBeDelegated,
|
||||
PsiMethod delegateMethod) {
|
||||
TemplateBuilderImpl builder = new TemplateBuilderImpl(delegateMethod);
|
||||
RangeMarker rangeMarker = editor.getDocument().createRangeMarker(delegateMethod.getTextRange());
|
||||
for (final PsiExpression exprToBeDefault : argsToBeDelegated) {
|
||||
builder.replaceElement(exprToBeDefault, new TextExpression(exprToBeDefault.getText()));
|
||||
}
|
||||
Template template = builder.buildTemplate();
|
||||
editor.getCaretModel().moveToOffset(rangeMarker.getStartOffset());
|
||||
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());
|
||||
editor.getDocument().deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
|
||||
|
||||
rangeMarker.dispose();
|
||||
|
||||
CreateFromUsageBaseFix.startTemplate(editor, template, project);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.intention.FileModifier;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderImpl;
|
||||
import com.intellij.codeInsight.template.TemplateManager;
|
||||
import com.intellij.codeInsight.template.impl.TextExpression;
|
||||
import com.intellij.codeInspection.PsiUpdateModCommandAction;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.ModTemplateBuilder;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
@@ -30,30 +24,28 @@ import java.util.TreeSet;
|
||||
/**
|
||||
* @author Dmitry Batkovich
|
||||
*/
|
||||
public final class AddMissingRequiredAnnotationParametersFix implements IntentionAction {
|
||||
public final class AddMissingRequiredAnnotationParametersFix extends PsiUpdateModCommandAction<PsiAnnotation> {
|
||||
private static final Logger LOG = Logger.getInstance(AddMissingRequiredAnnotationParametersFix.class);
|
||||
|
||||
private final PsiAnnotation myAnnotation;
|
||||
private final PsiMethod[] myAnnotationMethods;
|
||||
private final Collection<String> myMissedElements;
|
||||
|
||||
public AddMissingRequiredAnnotationParametersFix(final PsiAnnotation annotation,
|
||||
final PsiMethod[] annotationMethods,
|
||||
final Collection<String> missedElements) {
|
||||
super(annotation);
|
||||
if (missedElements.isEmpty()) {
|
||||
throw new IllegalArgumentException("missedElements can't be empty");
|
||||
}
|
||||
myAnnotation = annotation;
|
||||
myAnnotationMethods = annotationMethods;
|
||||
myMissedElements = missedElements;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getText() {
|
||||
return myMissedElements.size() == 1
|
||||
protected @NotNull Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiAnnotation element) {
|
||||
return Presentation.of(myMissedElements.size() == 1
|
||||
? QuickFixBundle.message("add.missing.annotation.single.parameter.fix", ContainerUtil.getFirstItem(myMissedElements))
|
||||
: QuickFixBundle.message("add.missing.annotation.parameters.fix", StringUtil.join(myMissedElements, ", "));
|
||||
: QuickFixBundle.message("add.missing.annotation.parameters.fix", StringUtil.join(myMissedElements, ", ")));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -63,13 +55,8 @@ public final class AddMissingRequiredAnnotationParametersFix implements Intentio
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) {
|
||||
return myAnnotation.isValid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException {
|
||||
final PsiNameValuePair[] addedParameters = myAnnotation.getParameterList().getAttributes();
|
||||
protected void invoke(@NotNull ActionContext context, @NotNull PsiAnnotation annotation, @NotNull ModPsiUpdater updater) {
|
||||
final PsiNameValuePair[] addedParameters = annotation.getParameterList().getAttributes();
|
||||
|
||||
final Object2IntMap<String> annotationsOrderMap = getAnnotationsOrderMap();
|
||||
final SortedSet<Pair<String, PsiAnnotationMemberValue>> newParameters =
|
||||
@@ -78,7 +65,7 @@ public final class AddMissingRequiredAnnotationParametersFix implements Intentio
|
||||
final boolean order = isAlreadyAddedOrdered(annotationsOrderMap, addedParameters);
|
||||
if (order) {
|
||||
if (addedParameters.length != 0) {
|
||||
final PsiAnnotationParameterList parameterList = myAnnotation.getParameterList();
|
||||
final PsiAnnotationParameterList parameterList = annotation.getParameterList();
|
||||
parameterList.deleteChildRange(addedParameters[0], addedParameters[addedParameters.length - 1]);
|
||||
for (final PsiNameValuePair addedParameter : addedParameters) {
|
||||
String name = addedParameter.getName();
|
||||
@@ -95,7 +82,7 @@ public final class AddMissingRequiredAnnotationParametersFix implements Intentio
|
||||
}
|
||||
}
|
||||
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
|
||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(context.project());
|
||||
for (PsiMethod method : myAnnotationMethods) {
|
||||
if (myMissedElements.contains(method.getName())) {
|
||||
PsiType type = method.getReturnType();
|
||||
@@ -112,33 +99,14 @@ public final class AddMissingRequiredAnnotationParametersFix implements Intentio
|
||||
}
|
||||
}
|
||||
|
||||
TemplateBuilderImpl builder = null;
|
||||
ModTemplateBuilder builder = updater.templateBuilder();
|
||||
for (final Pair<String, PsiAnnotationMemberValue> newParameter : newParameters) {
|
||||
final PsiAnnotationMemberValue value =
|
||||
myAnnotation.setDeclaredAttributeValue(newParameter.getFirst(), newParameter.getSecond());
|
||||
annotation.setDeclaredAttributeValue(newParameter.getFirst(), newParameter.getSecond());
|
||||
if (myMissedElements.contains(newParameter.getFirst())) {
|
||||
if (builder == null) {
|
||||
builder = new TemplateBuilderImpl(myAnnotation.getParameterList());
|
||||
}
|
||||
builder.replaceElement(value, new TextExpression(newParameter.getSecond().getText()), true);
|
||||
builder.field(value, new TextExpression(newParameter.getSecond().getText()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.isPhysical()) return;
|
||||
|
||||
editor.getCaretModel().moveToOffset(myAnnotation.getParameterList().getTextRange().getStartOffset());
|
||||
final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
|
||||
final Document document = documentManager.getDocument(file);
|
||||
if (document == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
documentManager.doPostponedOperationsAndUnblockDocument(document);
|
||||
TemplateManager.getInstance(project).startTemplate(editor, builder.buildInlineTemplate(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private Object2IntMap<String> getAnnotationsOrderMap() {
|
||||
@@ -163,10 +131,4 @@ public final class AddMissingRequiredAnnotationParametersFix implements Intentio
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull FileModifier getFileModifierForPreview(@NotNull PsiFile target) {
|
||||
return new AddMissingRequiredAnnotationParametersFix(PsiTreeUtil.findSameElementInCopy(myAnnotation, target), myAnnotationMethods,
|
||||
myMissedElements);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +1,33 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.actions.IntentionActionWithFixAllOption;
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.codeInsight.lookup.ExpressionLookupItem;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.template.PsiElementResult;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderFactory;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderImpl;
|
||||
import com.intellij.codeInsight.template.impl.ConstantNode;
|
||||
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
|
||||
import com.intellij.codeInspection.PsiUpdateModCommandAction;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.modcommand.ModTemplateBuilder;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiTypesUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.psi.util.TypeConversionUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AddVariableInitializerFix extends LocalQuickFixAndIntentionActionOnPsiElement
|
||||
implements IntentionActionWithFixAllOption {
|
||||
public class AddVariableInitializerFix extends PsiUpdateModCommandAction<PsiVariable> {
|
||||
private static final Logger LOG = Logger.getInstance(AddVariableInitializerFix.class);
|
||||
|
||||
public AddVariableInitializerFix(@NotNull PsiVariable variable) {
|
||||
super(variable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getText() {
|
||||
PsiVariable variable = ObjectUtils.tryCast(myStartElement.getElement(), PsiVariable.class);
|
||||
return variable == null ? getFamilyName() : JavaBundle.message("quickfix.add.variable.text", variable.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getFamilyName() {
|
||||
@@ -52,55 +35,23 @@ public class AddVariableInitializerFix extends LocalQuickFixAndIntentionActionOn
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project,
|
||||
@NotNull PsiFile file,
|
||||
@Nullable Editor editor,
|
||||
@NotNull PsiElement startElement,
|
||||
@NotNull PsiElement endElement) {
|
||||
PsiVariable variable = ObjectUtils.tryCast(startElement, PsiVariable.class);
|
||||
return variable != null && variable.isValid() &&
|
||||
BaseIntentionAction.canModify(variable) &&
|
||||
!variable.hasInitializer() &&
|
||||
!(variable instanceof PsiParameter);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElement getElementToMakeWritable(@NotNull PsiFile file) {
|
||||
return file;
|
||||
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiVariable variable) {
|
||||
if (variable.hasInitializer() || variable instanceof PsiParameter) return null;
|
||||
return Presentation.of(JavaBundle.message("quickfix.add.variable.text", variable.getName())).withFixAllOption(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project,
|
||||
@NotNull PsiFile file,
|
||||
@Nullable Editor editor,
|
||||
@NotNull PsiElement startElement,
|
||||
@NotNull PsiElement endElement) {
|
||||
PsiVariable variable = ObjectUtils.tryCast(startElement, PsiVariable.class);
|
||||
if (variable == null) return;
|
||||
protected void invoke(@NotNull ActionContext context, @NotNull PsiVariable variable, @NotNull ModPsiUpdater updater) {
|
||||
final LookupElement[] suggestedInitializers = suggestInitializer(variable);
|
||||
LOG.assertTrue(suggestedInitializers.length > 0);
|
||||
LOG.assertTrue(suggestedInitializers[0] instanceof ExpressionLookupItem);
|
||||
final PsiExpression initializer = (PsiExpression)suggestedInitializers[0].getObject();
|
||||
LookupElement firstLookupElement = suggestedInitializers[0];
|
||||
LOG.assertTrue(firstLookupElement instanceof ExpressionLookupItem);
|
||||
final PsiExpression initializer = (PsiExpression)firstLookupElement.getObject();
|
||||
variable.setInitializer(initializer);
|
||||
Document document = Objects.requireNonNull(file.getViewProvider().getDocument());
|
||||
PsiDocumentManager.getInstance(initializer.getProject()).doPostponedOperationsAndUnblockDocument(document);
|
||||
runAssignmentTemplate(Collections.singletonList(variable.getInitializer()), suggestedInitializers, editor);
|
||||
}
|
||||
|
||||
static void runAssignmentTemplate(@NotNull final List<? extends PsiExpression> initializers,
|
||||
final LookupElement @NotNull [] suggestedInitializers,
|
||||
@Nullable Editor editor) {
|
||||
if (editor == null) return;
|
||||
LOG.assertTrue(!initializers.isEmpty());
|
||||
final PsiExpression initializer = Objects.requireNonNull(ContainerUtil.getFirstItem(initializers));
|
||||
PsiElement context = initializers.size() == 1 ? initializer : PsiTreeUtil.findCommonParent(initializers);
|
||||
if (context == null) return;
|
||||
final TemplateBuilderImpl builder = (TemplateBuilderImpl)TemplateBuilderFactory.getInstance().createTemplateBuilder(context);
|
||||
for (PsiExpression e : initializers) {
|
||||
builder.replaceElement(e, new ConstantNode(new PsiElementResult(suggestedInitializers[0].getPsiElement())).withLookupItems(suggestedInitializers));
|
||||
}
|
||||
builder.run(editor, false);
|
||||
ModTemplateBuilder builder = updater.templateBuilder();
|
||||
builder.field(Objects.requireNonNull(variable.getInitializer()),
|
||||
new ConstantNode(new PsiElementResult(firstLookupElement.getPsiElement())).withLookupItems(
|
||||
suggestedInitializers));
|
||||
}
|
||||
|
||||
static LookupElement @NotNull [] suggestInitializer(final PsiVariable variable) {
|
||||
|
||||
@@ -2,27 +2,17 @@
|
||||
package com.intellij.codeInsight.daemon.impl.quickfix;
|
||||
|
||||
import com.intellij.codeInsight.daemon.QuickFixBundle;
|
||||
import com.intellij.codeInsight.generation.ClassMember;
|
||||
import com.intellij.codeInsight.generation.RecordConstructorMember;
|
||||
import com.intellij.codeInsight.hint.HintManager;
|
||||
import com.intellij.codeInsight.intention.LowPriorityAction;
|
||||
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
|
||||
import com.intellij.codeInsight.intention.PriorityAction;
|
||||
import com.intellij.codeInsight.intention.impl.ParameterClassMember;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewUtils;
|
||||
import com.intellij.codeInsight.template.Template;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderImpl;
|
||||
import com.intellij.codeInsight.template.impl.TextExpression;
|
||||
import com.intellij.codeInspection.ModCommands;
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.ide.util.MemberChooser;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.modcommand.*;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Iconable;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
@@ -30,24 +20,20 @@ import com.intellij.psi.util.JavaElementKind;
|
||||
import com.intellij.psi.util.JavaPsiRecordUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.ui.ExperimentalUI;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.CommonJavaRefactoringUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.siyeh.ig.psiutils.TypeUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.*;
|
||||
|
||||
public class DefineParamsDefaultValueAction extends PsiElementBaseIntentionAction implements Iconable, LowPriorityAction {
|
||||
public class DefineParamsDefaultValueAction extends PsiBasedModCommandAction<PsiElement> {
|
||||
private static final Logger LOG = Logger.getInstance(DefineParamsDefaultValueAction.class);
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return false;
|
||||
public DefineParamsDefaultValueAction() {
|
||||
super(PsiElement.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -57,166 +43,107 @@ public class DefineParamsDefaultValueAction extends PsiElementBaseIntentionActio
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon(int flags) {
|
||||
return ExperimentalUI.isNewUI() ? null : AllIcons.Actions.RefactoringBulb;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
|
||||
if (!JavaLanguage.INSTANCE.equals(element.getLanguage())) {
|
||||
return false;
|
||||
}
|
||||
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiElement element) {
|
||||
if (!JavaLanguage.INSTANCE.equals(element.getLanguage())) return null;
|
||||
final PsiElement parent = PsiTreeUtil.getParentOfType(element, PsiMethod.class, PsiCodeBlock.class);
|
||||
if (!(parent instanceof PsiMethod method)) {
|
||||
return false;
|
||||
}
|
||||
if (!(parent instanceof PsiMethod method)) return null;
|
||||
final PsiParameterList parameterList = method.getParameterList();
|
||||
if (parameterList.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
if (parameterList.isEmpty()) return null;
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null || (containingClass.isInterface() && !PsiUtil.isLanguageLevel8OrHigher(method))) {
|
||||
return false;
|
||||
}
|
||||
if (containingClass == null || (containingClass.isInterface() && !PsiUtil.isLanguageLevel8OrHigher(method))) return null;
|
||||
if (containingClass.isAnnotationType()) {
|
||||
// Method with parameters in annotation is a compilation error; there's no sense to create overload
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
setText(QuickFixBundle.message("generate.overloaded.method.or.constructor.with.default.parameter.values",
|
||||
JavaElementKind.fromElement(method).lessDescriptive().object()));
|
||||
return true;
|
||||
return Presentation.of(QuickFixBundle.message("generate.overloaded.method.or.constructor.with.default.parameter.values",
|
||||
JavaElementKind.fromElement(method).lessDescriptive().object()))
|
||||
.withIcon(AllIcons.Actions.RefactoringBulb)
|
||||
.withPriority(PriorityAction.Priority.LOW);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull final Project project, final Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
|
||||
final PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
|
||||
protected @NotNull ModCommand perform(@NotNull ActionContext context, @NotNull PsiElement element) {
|
||||
PsiMethod method = PsiTreeUtil.getParentOfType(element, PsiMethod.class);
|
||||
assert method != null;
|
||||
PsiParameterList parameterList = method.getParameterList();
|
||||
final PsiParameter[] parameters = getParams(element, parameterList);
|
||||
if (parameters == null || parameters.length == 0) return;
|
||||
PsiParameter[] parameters = parameterList.getParameters();
|
||||
if (parameters.length == 1) {
|
||||
return ModCommands.psiUpdate(method, (m, updater) -> invoke(context.project(), m, updater,
|
||||
updater.getWritable(m).getParameterList().getParameters()));
|
||||
}
|
||||
List<ParameterClassMember> members = ContainerUtil.map(parameters, ParameterClassMember::new);
|
||||
PsiParameter selectedParam = PsiTreeUtil.getParentOfType(element, PsiParameter.class);
|
||||
int idx = selectedParam != null ? ArrayUtil.find(parameters, selectedParam) : -1;
|
||||
List<ParameterClassMember> defaultSelection = idx >= 0 ? List.of(members.get(idx)) : members;
|
||||
return new ModChooseMember(
|
||||
QuickFixBundle.message("choose.default.value.parameters.popup.title"),
|
||||
members, defaultSelection, ModChooseMember.SelectionMode.MULTIPLE,
|
||||
sel -> ModCommands.psiUpdate(context, updater -> {
|
||||
invoke(context.project(), updater.getWritable(element), updater,
|
||||
ContainerUtil.map2Array(sel, PsiParameter.EMPTY_ARRAY,
|
||||
s -> updater.getWritable(((ParameterClassMember)s).getParameter())));
|
||||
}));
|
||||
}
|
||||
|
||||
private static void invoke(@NotNull Project project, @NotNull PsiElement element,
|
||||
@NotNull ModPsiUpdater updater, @NotNull PsiParameter @NotNull [] parameters) {
|
||||
final PsiMethod method = PsiTreeUtil.getNonStrictParentOfType(element, PsiMethod.class);
|
||||
assert method != null;
|
||||
PsiParameterList parameterList = method.getParameterList();
|
||||
if (parameters.length == 0) return;
|
||||
final PsiMethod methodPrototype = generateMethodPrototype(method, parameters);
|
||||
final PsiClass containingClass = method.getContainingClass();
|
||||
if (containingClass == null) return;
|
||||
final PsiMethod existingMethod = containingClass.findMethodBySignature(methodPrototype, false);
|
||||
if (existingMethod != null) {
|
||||
if (containingClass.isPhysical()) {
|
||||
editor.getCaretModel().moveToOffset(existingMethod.getTextOffset());
|
||||
HintManager.getInstance().showErrorHint(editor,
|
||||
JavaBundle.message("default.param.value.warning", existingMethod.isConstructor() ? 0 : 1));
|
||||
}
|
||||
updater.moveTo(existingMethod.getTextOffset());
|
||||
updater.message(JavaBundle.message("default.param.value.warning",
|
||||
existingMethod.isConstructor() ? 0 : 1));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IntentionPreviewUtils.prepareElementForWrite(element)) return;
|
||||
final PsiMethod prototype = (PsiMethod)containingClass.addBefore(methodPrototype, method);
|
||||
CommonJavaRefactoringUtil.fixJavadocsForParams(prototype, Set.of(prototype.getParameterList().getParameters()));
|
||||
|
||||
Runnable runnable = () -> {
|
||||
final PsiMethod prototype = (PsiMethod)containingClass.addBefore(methodPrototype, method);
|
||||
CommonJavaRefactoringUtil.fixJavadocsForParams(prototype, Set.of(prototype.getParameterList().getParameters()));
|
||||
|
||||
|
||||
PsiCodeBlock body = prototype.getBody();
|
||||
final String callArgs =
|
||||
"(" + StringUtil.join(parameterList.getParameters(), psiParameter -> {
|
||||
if (ArrayUtil.find(parameters, psiParameter) > -1) return TypeUtils.getDefaultValue(psiParameter.getType());
|
||||
return psiParameter.getName();
|
||||
}, ",") + ");";
|
||||
final String methodCall;
|
||||
if (method.getReturnType() == null) {
|
||||
methodCall = "this";
|
||||
} else if (!PsiTypes.voidType().equals(method.getReturnType())) {
|
||||
methodCall = "return " + method.getName();
|
||||
} else {
|
||||
methodCall = method.getName();
|
||||
}
|
||||
LOG.assertTrue(body != null);
|
||||
body.add(JavaPsiFacade.getElementFactory(project).createStatementFromText(methodCall + callArgs, method));
|
||||
body = (PsiCodeBlock)CodeStyleManager.getInstance(project).reformat(body);
|
||||
final PsiStatement stmt = body.getStatements()[0];
|
||||
final PsiExpression expr;
|
||||
if (stmt instanceof PsiReturnStatement) {
|
||||
expr = ((PsiReturnStatement)stmt).getReturnValue();
|
||||
} else if (stmt instanceof PsiExpressionStatement) {
|
||||
expr = ((PsiExpressionStatement)stmt).getExpression();
|
||||
}
|
||||
else {
|
||||
expr = null;
|
||||
}
|
||||
if (expr instanceof PsiMethodCallExpression) {
|
||||
PsiExpression[] args = ((PsiMethodCallExpression)expr).getArgumentList().getExpressions();
|
||||
PsiExpression[] toDefaults = ContainerUtil.map2Array(parameters, PsiExpression.class, (parameter -> args[parameterList.getParameterIndex(parameter)]));
|
||||
startTemplate(project, editor, toDefaults, prototype);
|
||||
}
|
||||
};
|
||||
if (startInWriteAction() || !containingClass.isPhysical()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
ApplicationManager.getApplication().runWriteAction(runnable);
|
||||
PsiCodeBlock body = prototype.getBody();
|
||||
final String callArgs =
|
||||
"(" + StringUtil.join(parameterList.getParameters(), psiParameter -> {
|
||||
if (ArrayUtil.find(parameters, psiParameter) > -1) return TypeUtils.getDefaultValue(psiParameter.getType());
|
||||
return psiParameter.getName();
|
||||
}, ",") + ");";
|
||||
final String methodCall;
|
||||
if (method.getReturnType() == null) {
|
||||
methodCall = "this";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project,
|
||||
@NotNull Editor editor,
|
||||
@NotNull PsiFile file) {
|
||||
invoke(project, editor, file);
|
||||
return IntentionPreviewInfo.DIFF;
|
||||
}
|
||||
|
||||
public static void startTemplate(@NotNull Project project,
|
||||
Editor editor,
|
||||
PsiExpression[] argsToBeDelegated,
|
||||
PsiMethod delegateMethod) {
|
||||
TemplateBuilderImpl builder = new TemplateBuilderImpl(delegateMethod);
|
||||
RangeMarker rangeMarker = editor.getDocument().createRangeMarker(delegateMethod.getTextRange());
|
||||
for (final PsiExpression exprToBeDefault : argsToBeDelegated) {
|
||||
builder.replaceElement(exprToBeDefault, new TextExpression(exprToBeDefault.getText()));
|
||||
}
|
||||
Template template = builder.buildTemplate();
|
||||
editor.getCaretModel().moveToOffset(rangeMarker.getStartOffset());
|
||||
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());
|
||||
editor.getDocument().deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
|
||||
|
||||
rangeMarker.dispose();
|
||||
|
||||
CreateFromUsageBaseFix.startTemplate(editor, template, project);
|
||||
}
|
||||
|
||||
private static PsiParameter @Nullable [] getParams(@NotNull PsiElement element, @NotNull PsiParameterList parameterList) {
|
||||
final PsiParameter[] parameters = parameterList.getParameters();
|
||||
if (parameters.length == 1 || !parameterList.isPhysical()) {
|
||||
return parameters;
|
||||
}
|
||||
final ParameterClassMember[] members = new ParameterClassMember[parameters.length];
|
||||
for (int i = 0; i < members.length; i++) {
|
||||
members[i] = new ParameterClassMember(parameters[i]);
|
||||
}
|
||||
final PsiParameter selectedParam = PsiTreeUtil.getParentOfType(element, PsiParameter.class);
|
||||
final int idx = selectedParam != null ? ArrayUtil.find(parameters, selectedParam) : -1;
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
return idx >= 0 ? new PsiParameter[] {selectedParam} : null;
|
||||
}
|
||||
final MemberChooser<ParameterClassMember> chooser =
|
||||
new MemberChooser<>(members, false, true, parameterList.getProject());
|
||||
if (idx >= 0) {
|
||||
chooser.selectElements(new ClassMember[] {members[idx]});
|
||||
else if (!PsiTypes.voidType().equals(method.getReturnType())) {
|
||||
methodCall = "return " + method.getName();
|
||||
}
|
||||
else {
|
||||
chooser.selectElements(members);
|
||||
methodCall = method.getName();
|
||||
}
|
||||
chooser.setTitle(QuickFixBundle.message("choose.default.value.parameters.popup.title"));
|
||||
chooser.setCopyJavadocVisible(false);
|
||||
if (chooser.showAndGet()) {
|
||||
final List<ParameterClassMember> elements = chooser.getSelectedElements();
|
||||
if (elements != null) {
|
||||
PsiParameter[] params = new PsiParameter[elements.size()];
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
params[i] = elements.get(i).getParameter();
|
||||
}
|
||||
return params;
|
||||
LOG.assertTrue(body != null);
|
||||
body.add(JavaPsiFacade.getElementFactory(project).createStatementFromText(methodCall + callArgs, method));
|
||||
body = (PsiCodeBlock)CodeStyleManager.getInstance(project).reformat(body);
|
||||
final PsiStatement stmt = body.getStatements()[0];
|
||||
final PsiExpression expr;
|
||||
if (stmt instanceof PsiReturnStatement returnStatement) {
|
||||
expr = returnStatement.getReturnValue();
|
||||
}
|
||||
else if (stmt instanceof PsiExpressionStatement exprStatement) {
|
||||
expr = exprStatement.getExpression();
|
||||
}
|
||||
else {
|
||||
expr = null;
|
||||
}
|
||||
if (expr instanceof PsiMethodCallExpression call) {
|
||||
PsiExpression[] args = call.getArgumentList().getExpressions();
|
||||
PsiExpression[] toDefaults =
|
||||
ContainerUtil.map2Array(parameters, PsiExpression.class, (parameter -> args[parameterList.getParameterIndex(parameter)]));
|
||||
ModTemplateBuilder builder = updater.templateBuilder();
|
||||
for (final PsiExpression exprToBeDefault : toDefaults) {
|
||||
builder.field(exprToBeDefault, new TextExpression(exprToBeDefault.getText()));
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static PsiMethod generateMethodPrototype(PsiMethod method, PsiParameter... params) {
|
||||
|
||||
@@ -6,6 +6,10 @@ import com.intellij.codeInsight.generation.PsiMethodMember;
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.codeInsight.template.PsiElementResult;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderFactory;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderImpl;
|
||||
import com.intellij.codeInsight.template.impl.ConstantNode;
|
||||
import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement;
|
||||
import com.intellij.ide.util.MemberChooser;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
@@ -120,7 +124,22 @@ public class InitializeFinalFieldInConstructorFix extends LocalQuickFixAndIntent
|
||||
Document doc = Objects.requireNonNull(field.getContainingFile().getViewProvider().getDocument());
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(doc);
|
||||
List<PsiExpression> rExpressions = ContainerUtil.mapNotNull(rExprPointers, SmartPsiElementPointer::getElement);
|
||||
AddVariableInitializerFix.runAssignmentTemplate(rExpressions, suggestedInitializers, editor);
|
||||
runAssignmentTemplate(rExpressions, suggestedInitializers, editor);
|
||||
}
|
||||
|
||||
private static void runAssignmentTemplate(@NotNull final List<? extends PsiExpression> initializers,
|
||||
final LookupElement @NotNull [] suggestedInitializers,
|
||||
@Nullable Editor editor) {
|
||||
if (editor == null) return;
|
||||
LOG.assertTrue(!initializers.isEmpty());
|
||||
final PsiExpression initializer = Objects.requireNonNull(ContainerUtil.getFirstItem(initializers));
|
||||
PsiElement context = initializers.size() == 1 ? initializer : PsiTreeUtil.findCommonParent(initializers);
|
||||
if (context == null) return;
|
||||
final TemplateBuilderImpl builder = (TemplateBuilderImpl)TemplateBuilderFactory.getInstance().createTemplateBuilder(context);
|
||||
for (PsiExpression e : initializers) {
|
||||
builder.replaceElement(e, new ConstantNode(new PsiElementResult(suggestedInitializers[0].getPsiElement())).withLookupItems(suggestedInitializers));
|
||||
}
|
||||
builder.run(editor, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.intention.impl;
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightUtilCore;
|
||||
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
|
||||
import com.intellij.codeInsight.template.TemplateBuilder;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderFactory;
|
||||
import com.intellij.codeInsight.template.impl.ConstantNode;
|
||||
import com.intellij.codeInspection.PsiUpdateModCommandAction;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.lang.java.JavaLanguage;
|
||||
import com.intellij.lang.surroundWith.SurroundDescriptor;
|
||||
import com.intellij.lang.surroundWith.Surrounder;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.modcommand.ModCommand;
|
||||
import com.intellij.modcommand.ModCommandExecutor;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
@@ -25,7 +25,6 @@ import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.refactoring.ui.TypeSelectorManagerImpl;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.siyeh.ig.psiutils.CommentTracker;
|
||||
import com.siyeh.ig.psiutils.VariableNameGenerator;
|
||||
@@ -36,33 +35,33 @@ import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull Project project, Editor editor, @NotNull PsiElement element) {
|
||||
return element.getLanguage().isKindOf(JavaLanguage.INSTANCE) &&
|
||||
PsiUtil.getLanguageLevel(element).isAtLeast(LanguageLevel.JDK_1_7) &&
|
||||
(findVariable(element) != null || findExpression(element) != null);
|
||||
public class SurroundAutoCloseableAction extends PsiUpdateModCommandAction<PsiElement> {
|
||||
public SurroundAutoCloseableAction() {
|
||||
super(PsiElement.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
|
||||
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiElement element) {
|
||||
boolean available = element.getLanguage().isKindOf(JavaLanguage.INSTANCE) &&
|
||||
PsiUtil.getLanguageLevel(element).isAtLeast(LanguageLevel.JDK_1_7) &&
|
||||
(findVariable(element) != null || findExpression(element) != null);
|
||||
return available ? Presentation.of(getFamilyName()) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void invoke(@NotNull ActionContext context, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
PsiLocalVariable variable = findVariable(element);
|
||||
if (variable != null) {
|
||||
WriteCommandAction.runWriteCommandAction(project, getFamilyName(), null, () -> processVariable(project, editor, variable), element.getContainingFile());
|
||||
processVariable(context.project(), updater, variable);
|
||||
}
|
||||
else {
|
||||
PsiExpression expression = findExpression(element);
|
||||
if (expression != null) {
|
||||
processExpression(project, editor, expression);
|
||||
processExpression(context.project(), updater, expression);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean startInWriteAction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static PsiLocalVariable findVariable(PsiElement element) {
|
||||
PsiLocalVariable variable = PsiTreeUtil.getNonStrictParentOfType(element, PsiLocalVariable.class);
|
||||
|
||||
@@ -123,7 +122,7 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
|
||||
PsiTreeUtil.findChildOfType(expression, PsiErrorElement.class) == null;
|
||||
}
|
||||
|
||||
private static void processVariable(Project project, Editor editor, PsiLocalVariable variable) {
|
||||
private static void processVariable(Project project, ModPsiUpdater updater, PsiLocalVariable variable) {
|
||||
PsiExpression initializer = Objects.requireNonNull(variable.getInitializer());
|
||||
PsiElement declaration = variable.getParent();
|
||||
PsiElement codeBlock = declaration.getParent();
|
||||
@@ -160,7 +159,7 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
|
||||
if (tryBlock != null) {
|
||||
PsiJavaToken brace = tryBlock.getLBrace();
|
||||
if (brace != null) {
|
||||
editor.getCaretModel().moveToOffset(brace.getTextOffset() + 1);
|
||||
updater.moveTo(brace.getTextOffset() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -225,7 +224,7 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
|
||||
return toFormat;
|
||||
}
|
||||
|
||||
private void processExpression(final Project project, final Editor editor, PsiExpression expression) {
|
||||
private static void processExpression(final Project project, ModPsiUpdater updater, PsiExpression expression) {
|
||||
PsiType type = Objects.requireNonNull(expression.getType());
|
||||
final PsiType[] types = Stream.of(new TypeSelectorManagerImpl(project, type, expression, PsiExpression.EMPTY_ARRAY).getTypesForAll())
|
||||
.filter(SurroundAutoCloseableAction::rightType)
|
||||
@@ -237,29 +236,24 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
|
||||
CommentTracker commentTracker = new CommentTracker();
|
||||
final List<String> names = new VariableNameGenerator(expression, VariableKind.LOCAL_VARIABLE).byType(type).byExpression(expression)
|
||||
.generateAll(true);
|
||||
WriteCommandAction.runWriteCommandAction(project, getFamilyName(), null, () -> {
|
||||
String text = "try (" + type.getCanonicalText(true) + " " + names.get(0) + " = " + commentTracker.text(expression) + ") {}";
|
||||
PsiTryStatement tryStatement = (PsiTryStatement)commentTracker.replaceAndRestoreComments(statement, text);
|
||||
String text = "try (" + type.getCanonicalText(true) + " " + names.get(0) + " = " + commentTracker.text(expression) + ") {}";
|
||||
PsiTryStatement tryStatement = (PsiTryStatement)commentTracker.replaceAndRestoreComments(statement, text);
|
||||
|
||||
tryStatement = (PsiTryStatement)CodeStyleManager.getInstance(project).reformat(tryStatement);
|
||||
tryStatement = (PsiTryStatement)CodeStyleManager.getInstance(project).reformat(tryStatement);
|
||||
|
||||
tryStatement = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(tryStatement);
|
||||
tryStatement = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(tryStatement);
|
||||
|
||||
PsiResourceList resourceList = tryStatement.getResourceList();
|
||||
if (resourceList != null && resourceList.isPhysical()) {
|
||||
final PsiResourceVariable var = (PsiResourceVariable)resourceList.iterator().next();
|
||||
final PsiIdentifier id = var.getNameIdentifier();
|
||||
PsiExpression initializer = var.getInitializer();
|
||||
if (id != null && initializer != null) {
|
||||
|
||||
TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(var);
|
||||
builder.replaceElement(id, new ConstantNode(var.getName()).withLookupStrings(names));
|
||||
|
||||
builder.replaceElement(var.getTypeElement(), typeExpression);
|
||||
builder.run(editor, true);
|
||||
}
|
||||
PsiResourceList resourceList = tryStatement.getResourceList();
|
||||
if (resourceList != null) {
|
||||
final PsiResourceVariable var = (PsiResourceVariable)resourceList.iterator().next();
|
||||
final PsiIdentifier id = var.getNameIdentifier();
|
||||
PsiExpression initializer = var.getInitializer();
|
||||
if (id != null && initializer != null) {
|
||||
updater.templateBuilder()
|
||||
.field(id, new ConstantNode(var.getName()).withLookupStrings(names))
|
||||
.field(var.getTypeElement(), typeExpression);
|
||||
}
|
||||
}, expression.getContainingFile());
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -268,12 +262,6 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
|
||||
return JavaBundle.message("intention.surround.resource.with.ARM.block");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getText() {
|
||||
return getFamilyName();
|
||||
}
|
||||
|
||||
public static class Template implements SurroundDescriptor, Surrounder {
|
||||
private final Surrounder[] mySurrounders = {this};
|
||||
|
||||
@@ -312,9 +300,10 @@ public class SurroundAutoCloseableAction extends PsiElementBaseIntentionAction {
|
||||
@Override
|
||||
public TextRange surroundElements(@NotNull Project project, @NotNull Editor editor, PsiElement @NotNull [] elements) {
|
||||
if (elements.length == 1) {
|
||||
new SurroundAutoCloseableAction().invoke(project, editor, elements[0]);
|
||||
ActionContext context = ActionContext.from(editor, elements[0].getContainingFile());
|
||||
ModCommand command = new SurroundAutoCloseableAction().perform(context, elements[0]);
|
||||
ModCommandExecutor.getInstance().executeInteractively(context, command, editor);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -582,7 +582,7 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
|
||||
@NotNull
|
||||
@Override
|
||||
public IntentionAction createAddVariableInitializerFix(@NotNull PsiVariable variable) {
|
||||
return new AddVariableInitializerFix(variable);
|
||||
return new AddVariableInitializerFix(variable).asIntention();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -823,7 +823,7 @@ public final class QuickFixFactoryImpl extends QuickFixFactory {
|
||||
public IntentionAction createAddMissingRequiredAnnotationParametersFix(@NotNull final PsiAnnotation annotation,
|
||||
final PsiMethod @NotNull [] annotationMethods,
|
||||
@NotNull final Collection<String> missedElements) {
|
||||
return new AddMissingRequiredAnnotationParametersFix(annotation, annotationMethods, missedElements);
|
||||
return new AddMissingRequiredAnnotationParametersFix(annotation, annotationMethods, missedElements).asIntention();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection;
|
||||
|
||||
import com.intellij.codeInsight.template.TemplateBuilder;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderFactory;
|
||||
import com.intellij.codeInsight.template.impl.ConstantNode;
|
||||
import com.intellij.java.JavaBundle;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.CodeStyleManager;
|
||||
@@ -20,7 +14,7 @@ import com.intellij.util.ObjectUtils;
|
||||
import com.siyeh.ig.psiutils.ParenthesesUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ReplaceWithTernaryOperatorFix implements LocalQuickFix {
|
||||
public class ReplaceWithTernaryOperatorFix extends PsiUpdateModCommandQuickFix {
|
||||
private final String myText;
|
||||
|
||||
@Override
|
||||
@@ -40,8 +34,7 @@ public class ReplaceWithTernaryOperatorFix implements LocalQuickFix {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiElement element = descriptor.getPsiElement();
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement element, @NotNull ModPsiUpdater updater) {
|
||||
while (true) {
|
||||
PsiElement parent = element.getParent();
|
||||
if (parent instanceof PsiCallExpression || parent instanceof PsiJavaCodeReferenceElement) {
|
||||
@@ -55,29 +48,16 @@ public class ReplaceWithTernaryOperatorFix implements LocalQuickFix {
|
||||
return;
|
||||
}
|
||||
|
||||
final PsiFile file = expression.getContainingFile();
|
||||
PsiConditionalExpression conditionalExpression =
|
||||
replaceWithConditionalExpression(project, myText + "!=null", expression, suggestDefaultValue(expression));
|
||||
|
||||
selectElseBranch(file, conditionalExpression);
|
||||
selectElseBranch(conditionalExpression, updater);
|
||||
}
|
||||
|
||||
static void selectElseBranch(PsiFile file, PsiConditionalExpression conditionalExpression) {
|
||||
if (!file.isPhysical()) return;
|
||||
private static void selectElseBranch(PsiConditionalExpression conditionalExpression, @NotNull ModPsiUpdater updater) {
|
||||
PsiExpression elseExpression = conditionalExpression.getElseExpression();
|
||||
if (elseExpression != null) {
|
||||
Project project = file.getProject();
|
||||
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
|
||||
if (editor != null) {
|
||||
Document document = editor.getDocument();
|
||||
PsiFile topLevelFile = InjectedLanguageManager.getInstance(project).getTopLevelFile(file);
|
||||
if (topLevelFile != null && document == topLevelFile.getViewProvider().getDocument()) {
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document);
|
||||
TemplateBuilder builder = TemplateBuilderFactory.getInstance().createTemplateBuilder(elseExpression);
|
||||
builder.replaceElement(elseExpression, new ConstantNode(elseExpression.getText()));
|
||||
builder.run(editor, true);
|
||||
}
|
||||
}
|
||||
updater.templateBuilder().field(elseExpression, elseExpression.getText());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +91,7 @@ public class ReplaceWithTernaryOperatorFix implements LocalQuickFix {
|
||||
return PsiTypesUtil.getDefaultValueOfType(type);
|
||||
}
|
||||
|
||||
public static class ReplaceMethodRefWithTernaryOperatorFix implements LocalQuickFix {
|
||||
public static class ReplaceMethodRefWithTernaryOperatorFix extends PsiUpdateModCommandQuickFix {
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFamilyName() {
|
||||
@@ -119,8 +99,8 @@ public class ReplaceWithTernaryOperatorFix implements LocalQuickFix {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) {
|
||||
PsiMethodReferenceExpression element = ObjectUtils.tryCast(descriptor.getPsiElement(), PsiMethodReferenceExpression.class);
|
||||
protected void applyFix(@NotNull Project project, @NotNull PsiElement startElement, @NotNull ModPsiUpdater updater) {
|
||||
PsiMethodReferenceExpression element = ObjectUtils.tryCast(startElement, PsiMethodReferenceExpression.class);
|
||||
if (element == null) return;
|
||||
PsiLambdaExpression lambda =
|
||||
LambdaRefactoringUtil.convertMethodReferenceToLambda(element, false, true);
|
||||
@@ -130,11 +110,9 @@ public class ReplaceWithTernaryOperatorFix implements LocalQuickFix {
|
||||
PsiParameter parameter = ArrayUtil.getFirstElement(lambda.getParameterList().getParameters());
|
||||
if (parameter == null) return;
|
||||
String text = parameter.getName();
|
||||
final PsiFile file = expression.getContainingFile();
|
||||
PsiConditionalExpression conditionalExpression = replaceWithConditionalExpression(project, text + "!=null", expression,
|
||||
suggestDefaultValue(expression));
|
||||
|
||||
selectElseBranch(file, conditionalExpression);
|
||||
selectElseBranch(conditionalExpression, updater);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInspection.nullable;
|
||||
|
||||
import com.intellij.codeInsight.Nullability;
|
||||
@@ -83,7 +83,7 @@ public class NotNullFieldNotInitializedInspection extends AbstractBaseJavaLocalI
|
||||
}
|
||||
if (isOnTheFly) {
|
||||
fixes.add(new InitializeFinalFieldInConstructorFix(field));
|
||||
fixes.add(new AddVariableInitializerFix(field));
|
||||
fixes.add(new AddVariableInitializerFix(field).asQuickFix());
|
||||
}
|
||||
|
||||
reportProblem(holder, anchor, message, fixes);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// "Initialize variable 'X'" "true-preview"
|
||||
interface Foo {
|
||||
char X = 0;
|
||||
char X = 0 .Y;
|
||||
int a = X;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded method with default parameter values" "true"
|
||||
class Test {
|
||||
void foo() {
|
||||
foo(<selection>0<caret></selection>);
|
||||
}
|
||||
foo(<selection>0<caret></selection>);
|
||||
}
|
||||
|
||||
void foo(int ii){
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded method with default parameter values" "true"
|
||||
abstract class Test {
|
||||
int foo(boolean... args) {
|
||||
return foo(<selection>0<caret></selection>, args);
|
||||
}
|
||||
return foo(<selection>0<caret></selection>, args);
|
||||
}
|
||||
|
||||
abstract int foo(int ii, boolean... args);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded method with default parameter values" "true"
|
||||
class Test {
|
||||
int foo() {
|
||||
return foo(0);
|
||||
}
|
||||
return foo(0);
|
||||
}
|
||||
|
||||
int foo(int ii){
|
||||
//comment1
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded constructor with default parameter values" "true"
|
||||
class Test {
|
||||
Test() {
|
||||
this(<selection>0<caret></selection>);
|
||||
}
|
||||
this(<selection>0<caret></selection>);
|
||||
}
|
||||
|
||||
Test(int ii){}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded method with default parameter values" "true"
|
||||
interface Test {
|
||||
default void foo() {
|
||||
foo(0);
|
||||
}
|
||||
foo(0);
|
||||
}
|
||||
|
||||
void foo(int ii);
|
||||
}
|
||||
@@ -3,9 +3,9 @@ class Test {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void foo() {
|
||||
foo(0);
|
||||
}
|
||||
void foo() {
|
||||
foo(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param i
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded constructor with default parameter values" "true"
|
||||
record Test(int x) {
|
||||
Test() {
|
||||
this(0);
|
||||
}
|
||||
this(0);
|
||||
}
|
||||
|
||||
public Test {
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded method with default parameter values" "true"
|
||||
class Test {
|
||||
int foo() {
|
||||
return foo(<selection>0<caret></selection>);
|
||||
}
|
||||
return foo(<selection>0<caret></selection>);
|
||||
}
|
||||
|
||||
int foo(int ii){
|
||||
return 1;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded method with default parameter values" "true"
|
||||
interface Test {
|
||||
static void foo() {
|
||||
foo(0);
|
||||
}
|
||||
foo(0);
|
||||
}
|
||||
|
||||
static void foo(int ii) {}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded method with default parameter values" "true"
|
||||
class Test {
|
||||
<T> int foo(boolean... args) {
|
||||
return foo(<selection>null<caret></selection>, args);
|
||||
}
|
||||
return foo(<selection>null<caret></selection>, args);
|
||||
}
|
||||
|
||||
<T> int foo(T ii, boolean... args){
|
||||
return 1;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// "Generate overloaded method with default parameter values" "true"
|
||||
class Test {
|
||||
int foo(boolean... args) {
|
||||
return foo(<selection>0<caret></selection>, args);
|
||||
}
|
||||
return foo(<selection>0<caret></selection>, args);
|
||||
}
|
||||
|
||||
int foo(int ii, boolean... args){
|
||||
return 1;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeInsight;
|
||||
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
@@ -206,12 +206,12 @@ public class IntentionPreviewTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
IntentionAction action = myFixture.findSingleIntention("Generate overloaded method with default parameter values");
|
||||
assertPreviewText(action, """
|
||||
public class Test {
|
||||
void test() {
|
||||
test(0, null);
|
||||
void test(String b) {
|
||||
test(0, b);
|
||||
}
|
||||
|
||||
|
||||
void test(int a, String b) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
@@ -183,6 +183,9 @@ public final class IntentionPreviewUtils {
|
||||
else if (command instanceof ModChooseAction target) {
|
||||
return getChoosePreview(context, target);
|
||||
}
|
||||
else if (command instanceof ModChooseMember target) {
|
||||
return getModCommandPreview(target.nextCommand().apply(target.defaultSelection()), context);
|
||||
}
|
||||
else if (command instanceof ModShowConflicts showConflicts) {
|
||||
return getModCommandPreview(showConflicts.nextStep(), context);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.modcommand;
|
||||
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface MemberChooserElement {
|
||||
@NlsContexts.Label @NotNull String getText();
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.modcommand;
|
||||
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A command that allows to select arbitrary number of elements. In batch mode, it's assumed that default selection is selected.
|
||||
*
|
||||
* @param title user-readable title to display in UI
|
||||
* @param elements all elements to select from
|
||||
* @param defaultSelection default selection
|
||||
* @param mode selection mode
|
||||
* @param nextCommand a function to compute the subsequent command based on the selection; will be executed in read-action
|
||||
*/
|
||||
public record ModChooseMember(@NotNull @NlsContexts.PopupTitle String title,
|
||||
@NotNull List<? extends @NotNull MemberChooserElement> elements,
|
||||
@NotNull List<? extends @NotNull MemberChooserElement> defaultSelection,
|
||||
@NotNull SelectionMode mode,
|
||||
@NotNull Function<@NotNull List<? extends @NotNull MemberChooserElement>, ? extends @NotNull ModCommand> nextCommand)
|
||||
implements ModCommand {
|
||||
|
||||
/**
|
||||
* Selection mode
|
||||
*/
|
||||
public enum SelectionMode {
|
||||
/**
|
||||
* Selecting exactly one element is allowed
|
||||
*/
|
||||
SINGLE,
|
||||
/**
|
||||
* Selecting one or zero elements is allowed
|
||||
*/
|
||||
SINGLE_OR_EMPTY,
|
||||
/**
|
||||
* Selecting any non-zero amount of elements is allowed
|
||||
*/
|
||||
MULTIPLE,
|
||||
/**
|
||||
* Selecting any mount of elements (including zero) is allowed
|
||||
*/
|
||||
MULTIPLE_OR_EMPTY
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ import java.util.Set;
|
||||
* All inheritors are records, so the whole state is declarative and readable.
|
||||
*/
|
||||
public sealed interface ModCommand
|
||||
permits ModChooseAction, ModCompositeCommand, ModCopyToClipboard, ModCreateFile, ModDeleteFile, ModDisplayMessage, ModHighlight,
|
||||
ModNavigate, ModNothing, ModRenameSymbol, ModShowConflicts, ModUpdateFileText {
|
||||
permits ModChooseAction, ModChooseMember, ModCompositeCommand, ModCopyToClipboard, ModCreateFile, ModDeleteFile, ModDisplayMessage,
|
||||
ModHighlight, ModNavigate, ModNothing, ModRenameSymbol, ModShowConflicts, ModStartTemplate, ModUpdateFileText {
|
||||
|
||||
/**
|
||||
* @return true if the command does nothing
|
||||
|
||||
@@ -65,6 +65,11 @@ public interface ModPsiUpdater extends ModPsiNavigator {
|
||||
*/
|
||||
void rename(@NotNull PsiNameIdentifierOwner element, @NotNull List<@NotNull String> suggestedNames);
|
||||
|
||||
/**
|
||||
* @return a builder that allows you to create a template
|
||||
*/
|
||||
@NotNull ModTemplateBuilder templateBuilder();
|
||||
|
||||
/**
|
||||
* Cancels any changes done previously, displaying an error message with the given text instead.
|
||||
* The subsequent updates will be ignored.
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.modcommand;
|
||||
|
||||
import com.intellij.codeInsight.template.Expression;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* A command to start template editing in the editor. In batch mode, or if the corresponding editor cannot be opened,
|
||||
* fields will just be initialized with expression default values.
|
||||
*
|
||||
* @param file file where to edit template.
|
||||
* @param fields template fields
|
||||
* @param templateFinishFunction
|
||||
*/
|
||||
public record ModStartTemplate(@NotNull VirtualFile file, @NotNull List<@NotNull TemplateField> fields,
|
||||
@NotNull Function<? super @NotNull PsiFile, ? extends @NotNull ModCommand> templateFinishFunction)
|
||||
implements ModCommand {
|
||||
|
||||
/**
|
||||
* Template field
|
||||
*/
|
||||
public sealed interface TemplateField {
|
||||
/**
|
||||
* @return field range inside the file
|
||||
*/
|
||||
@NotNull TextRange range();
|
||||
|
||||
/**
|
||||
* @param range new range
|
||||
* @return an equivalent template field but with updated range
|
||||
*/
|
||||
@NotNull TemplateField withRange(@NotNull TextRange range);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expression-based template field
|
||||
*
|
||||
* @param range field range inside the file
|
||||
* @param expression expression for the field
|
||||
*/
|
||||
public record ExpressionField(@NotNull TextRange range, @NotNull Expression expression) implements TemplateField {
|
||||
@Override
|
||||
@NotNull
|
||||
public TemplateField withRange(@NotNull TextRange range) {
|
||||
return new ExpressionField(range, expression);
|
||||
}
|
||||
}
|
||||
|
||||
public record DependantVariableField(@NotNull TextRange range, @NotNull String varName,
|
||||
@NotNull String dependantVariableName,
|
||||
boolean alwaysStopAt) implements TemplateField {
|
||||
@Override
|
||||
public @NotNull TemplateField withRange(@NotNull TextRange range) {
|
||||
return new DependantVariableField(range, varName, dependantVariableName, alwaysStopAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.modcommand;
|
||||
|
||||
import com.intellij.codeInsight.template.Expression;
|
||||
import com.intellij.codeInsight.template.impl.ConstantNode;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A builder to create a template. Should be used together with {@link ModPsiUpdater}.
|
||||
*/
|
||||
public interface ModTemplateBuilder {
|
||||
/**
|
||||
* Add a new expression field
|
||||
*
|
||||
* @param element element to replace with an expression
|
||||
* @param expression expression to use
|
||||
* @return this builder
|
||||
*/
|
||||
@NotNull ModTemplateBuilder field(@NotNull PsiElement element, @NotNull Expression expression);
|
||||
|
||||
/**
|
||||
* Add a new simple text field
|
||||
*
|
||||
* @param element element to replace with a constant text
|
||||
* @param value text to display by default
|
||||
* @return this builder
|
||||
*/
|
||||
default @NotNull ModTemplateBuilder field(@NotNull PsiElement element, @NotNull String value) {
|
||||
return field(element, new ConstantNode(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new dependent variable field
|
||||
*
|
||||
* @param element element to replace with a field
|
||||
* @param varName variable name
|
||||
* @param dependantVariableName dependant variable name
|
||||
* @param alwaysStopAt whether to always stop at this field
|
||||
* @return this builder
|
||||
*/
|
||||
@NotNull ModTemplateBuilder field(@NotNull PsiElement element, @NotNull String varName, @NotNull String dependantVariableName,
|
||||
boolean alwaysStopAt);
|
||||
}
|
||||
@@ -589,3 +589,4 @@ double.shift=Double Shift
|
||||
executor.error.files.are.marked.as.readonly=Files are marked as readonly
|
||||
executor.error.some.actions.failed=Some quick-fixes haven't completed successfully:
|
||||
executor.one.of.actions={0} of {1}: {2}
|
||||
command.title.finishing.template=Finishing Template
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
/*
|
||||
* Copyright 2000-2009 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.generation;
|
||||
|
||||
public interface ClassMember extends MemberChooserObject {
|
||||
import com.intellij.modcommand.MemberChooserElement;
|
||||
import com.intellij.ui.SimpleColoredComponent;
|
||||
import com.intellij.ui.SimpleTextAttributes;
|
||||
import com.intellij.ui.render.RenderingUtil;
|
||||
import com.intellij.ui.speedSearch.SpeedSearchUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public interface ClassMember extends MemberChooserObject, MemberChooserElement {
|
||||
ClassMember[] EMPTY_ARRAY = new ClassMember[0];
|
||||
|
||||
/**
|
||||
@@ -23,4 +18,29 @@ public interface ClassMember extends MemberChooserObject {
|
||||
*/
|
||||
MemberChooserObject getParentNodeDelegate();
|
||||
|
||||
/**
|
||||
* Adapt {@link MemberChooserElement} to {@link ClassMember}
|
||||
* @param element element to adapt
|
||||
* @return input element if it implements {@link ClassMember}, or adapter otherwise
|
||||
*/
|
||||
static @NotNull ClassMember from(@NotNull MemberChooserElement element) {
|
||||
if (element instanceof ClassMember member) return member;
|
||||
return new ClassMember() {
|
||||
@Override
|
||||
public MemberChooserObject getParentNodeDelegate() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderTreeNode(SimpleColoredComponent component, JTree tree) {
|
||||
SimpleTextAttributes attributes = new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, RenderingUtil.getForeground(tree));
|
||||
SpeedSearchUtil.appendFragmentsForSpeedSearch(tree, getText(), attributes, false, component);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getText() {
|
||||
return element.getText();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.intellij.lang.impl.modcommand;
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
|
||||
import com.intellij.codeInsight.daemon.impl.ShowIntentionsPass;
|
||||
import com.intellij.codeInsight.generation.ClassMember;
|
||||
import com.intellij.codeInsight.highlighting.HighlightManager;
|
||||
import com.intellij.codeInsight.hint.HintManager;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
@@ -15,8 +16,10 @@ import com.intellij.codeInsight.template.*;
|
||||
import com.intellij.diff.comparison.ComparisonManager;
|
||||
import com.intellij.diff.comparison.ComparisonPolicy;
|
||||
import com.intellij.diff.fragments.DiffFragment;
|
||||
import com.intellij.ide.util.MemberChooser;
|
||||
import com.intellij.lang.LangBundle;
|
||||
import com.intellij.modcommand.*;
|
||||
import com.intellij.modcommand.ModChooseMember.SelectionMode;
|
||||
import com.intellij.modcommand.ModCommandAction.ActionContext;
|
||||
import com.intellij.modcommand.ModUpdateFileText.Fragment;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
@@ -47,6 +50,7 @@ import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import com.intellij.util.concurrency.annotations.RequiresEdt;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import one.util.streamex.IntStreamEx;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -58,6 +62,8 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static java.util.Objects.requireNonNullElse;
|
||||
|
||||
public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
@RequiresEdt
|
||||
@Override
|
||||
@@ -104,8 +110,15 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
if (command instanceof ModChooseAction chooser) {
|
||||
return executeChooseInBatch(context, chooser);
|
||||
}
|
||||
if (command instanceof ModChooseMember member) {
|
||||
ModCommand nextCommand = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
() -> ReadAction.nonBlocking(() -> member.nextCommand().apply(member.defaultSelection())).executeSynchronously(),
|
||||
member.title(), true, context.project());
|
||||
executeInBatch(context, nextCommand);
|
||||
}
|
||||
if (command instanceof ModNavigate || command instanceof ModHighlight ||
|
||||
command instanceof ModCopyToClipboard || command instanceof ModRenameSymbol) {
|
||||
command instanceof ModCopyToClipboard || command instanceof ModRenameSymbol ||
|
||||
command instanceof ModStartTemplate) {
|
||||
return Result.INTERACTIVE;
|
||||
}
|
||||
if (command instanceof ModShowConflicts showConflicts) {
|
||||
@@ -130,21 +143,12 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
|
||||
String name = chooser.title();
|
||||
ModCommand next = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
() -> ReadAction.nonBlocking((Callable<? extends ModCommand>)() -> {
|
||||
() -> ReadAction.nonBlocking(() -> {
|
||||
if (action.getPresentation(context) == null) return null;
|
||||
return action.perform(context);
|
||||
}).executeSynchronously(), name, true, context.project());
|
||||
if (next == null) return Result.ABORT;
|
||||
var nextStep = new Runnable() {
|
||||
BatchExecutionResult myResult = Result.NOTHING;
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
myResult = executeInBatch(context, next);
|
||||
}
|
||||
};
|
||||
CommandProcessor.getInstance().executeCommand(context.project(), nextStep, name, null);
|
||||
return nextStep.myResult;
|
||||
return executeInBatch(context, next);
|
||||
}
|
||||
|
||||
private boolean doExecuteInteractively(@NotNull ActionContext context, @NotNull ModCommand command, @Nullable Editor editor) {
|
||||
@@ -170,11 +174,14 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
if (command instanceof ModChooseAction chooser) {
|
||||
return executeChoose(context, chooser, editor);
|
||||
}
|
||||
if (command instanceof ModChooseMember chooser) {
|
||||
return executeChooseMember(context, chooser, editor);
|
||||
}
|
||||
if (command instanceof ModDisplayMessage message) {
|
||||
return executeMessage(project, message);
|
||||
}
|
||||
if (command instanceof ModRenameSymbol rename) {
|
||||
return executeRename(project, rename);
|
||||
return executeRename(project, rename, editor);
|
||||
}
|
||||
if (command instanceof ModCreateFile create) {
|
||||
return executeCreate(project, create);
|
||||
@@ -185,9 +192,78 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
if (command instanceof ModShowConflicts showConflicts) {
|
||||
return executeShowConflicts(context, showConflicts, editor);
|
||||
}
|
||||
if (command instanceof ModStartTemplate startTemplate) {
|
||||
return executeStartTemplate(context, startTemplate, editor);
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown command: " + command);
|
||||
}
|
||||
|
||||
private boolean executeChooseMember(@NotNull ActionContext context, @NotNull ModChooseMember modChooser, @Nullable Editor editor) {
|
||||
List<? extends @NotNull MemberChooserElement> result;
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
result = modChooser.defaultSelection();
|
||||
}
|
||||
else {
|
||||
ClassMember[] members = ContainerUtil.map2Array(modChooser.elements(), ClassMember.EMPTY_ARRAY, ClassMember::from);
|
||||
SelectionMode mode = modChooser.mode();
|
||||
boolean allowEmptySelection = mode == SelectionMode.SINGLE_OR_EMPTY ||
|
||||
mode == SelectionMode.MULTIPLE_OR_EMPTY;
|
||||
boolean allowMultiSelection = mode == SelectionMode.MULTIPLE ||
|
||||
mode == SelectionMode.MULTIPLE_OR_EMPTY;
|
||||
MemberChooser<ClassMember> chooser = new MemberChooser<>(members, allowEmptySelection, allowMultiSelection, context.project());
|
||||
ClassMember[] selected = IntStreamEx.ofIndices(modChooser.elements(), modChooser.defaultSelection()::contains)
|
||||
.elements(members).toArray(ClassMember.EMPTY_ARRAY);
|
||||
chooser.selectElements(selected);
|
||||
chooser.setTitle(modChooser.title());
|
||||
chooser.setCopyJavadocVisible(false);
|
||||
if (!chooser.showAndGet()) return false;
|
||||
List<ClassMember> elements = chooser.getSelectedElements();
|
||||
result = elements == null ? List.of() :
|
||||
IntStreamEx.ofIndices(members, elements::contains).elements(modChooser.elements()).toList();
|
||||
}
|
||||
ModCommand nextCommand = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
() -> ReadAction.nonBlocking(() -> modChooser.nextCommand().apply(result)).executeSynchronously(),
|
||||
modChooser.title(), true, context.project());
|
||||
executeInteractively(context, nextCommand, editor);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeStartTemplate(@NotNull ActionContext context, @NotNull ModStartTemplate template, @Nullable Editor editor) {
|
||||
VirtualFile file = actualize(template.file());
|
||||
if (file == null) return false;
|
||||
Editor finalEditor = getEditor(context.project(), editor, file);
|
||||
if (finalEditor == null) return false;
|
||||
PsiFile psiFile = PsiManagerEx.getInstanceEx(context.project()).findFile(file);
|
||||
if (psiFile == null) return false;
|
||||
String name = requireNonNullElse(CommandProcessor.getInstance().getCurrentCommandName(),
|
||||
LangBundle.message("command.title.finishing.template"));
|
||||
WriteAction.run(() -> {
|
||||
TemplateBuilderImpl builder = new TemplateBuilderImpl(psiFile);
|
||||
for (ModStartTemplate.TemplateField field : template.fields()) {
|
||||
if (field instanceof ModStartTemplate.ExpressionField expr) {
|
||||
builder.replaceElement(psiFile, expr.range(), expr.expression());
|
||||
}
|
||||
else if (field instanceof ModStartTemplate.DependantVariableField variableField) {
|
||||
builder.replaceElement(psiFile, variableField.range(), variableField.varName(),
|
||||
variableField.dependantVariableName(), variableField.alwaysStopAt());
|
||||
}
|
||||
}
|
||||
|
||||
final Template tmpl = builder.buildInlineTemplate();
|
||||
finalEditor.getCaretModel().moveToOffset(0);
|
||||
TemplateManager.getInstance(context.project()).startTemplate(finalEditor, tmpl, new TemplateEditingAdapter() {
|
||||
@Override
|
||||
public void templateFinished(@NotNull Template tmpl, boolean brokenOff) {
|
||||
ModCommand next = ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
|
||||
return ReadAction.nonBlocking(() -> template.templateFinishFunction().apply(psiFile)).executeSynchronously();
|
||||
}, name, true, context.project());
|
||||
CommandProcessor.getInstance().executeCommand(context.project(), () -> executeInteractively(context, next, editor), name, null);
|
||||
}
|
||||
});
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeShowConflicts(@NotNull ActionContext context, @NotNull ModShowConflicts conflicts, @Nullable Editor editor) {
|
||||
MultiMap<PsiElement, String> conflictData = new MultiMap<>();
|
||||
conflicts.conflicts().forEach((e, c) -> conflictData.put(e, c.messages()));
|
||||
@@ -237,7 +313,7 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean executeRename(Project project, ModRenameSymbol rename) {
|
||||
private static boolean executeRename(@NotNull Project project, @NotNull ModRenameSymbol rename, @Nullable Editor editor) {
|
||||
VirtualFile file = actualize(rename.file());
|
||||
if (file == null) return false;
|
||||
PsiFile psiFile = PsiManagerEx.getInstanceEx(project).findFile(file);
|
||||
@@ -245,9 +321,8 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
PsiNameIdentifierOwner element =
|
||||
PsiTreeUtil.getNonStrictParentOfType(psiFile.findElementAt(rename.symbolRange().getStartOffset()), PsiNameIdentifierOwner.class);
|
||||
if (element == null) return false;
|
||||
final FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
|
||||
final Editor editor = fileEditorManager.getSelectedTextEditor();
|
||||
if (editor == null || !editor.getVirtualFile().equals(file)) return false;
|
||||
Editor finalEditor = getEditor(project, editor, file);
|
||||
if (finalEditor == null) return false;
|
||||
PsiElement nameIdentifier = element.getNameIdentifier();
|
||||
if (nameIdentifier == null) return false;
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
@@ -271,7 +346,7 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
return new RenameData(references, scope, owner);
|
||||
}
|
||||
}
|
||||
ReadAction.nonBlocking(() -> RenameData.create(pointer)).expireWhen(() -> editor.isDisposed())
|
||||
ReadAction.nonBlocking(() -> RenameData.create(pointer)).expireWhen(() -> finalEditor.isDisposed())
|
||||
.finishOnUiThread(ModalityState.defaultModalityState(), renameData -> {
|
||||
final TextRange textRange = renameData.scope().getTextRange();
|
||||
final int startOffset = textRange.getStartOffset();
|
||||
@@ -284,15 +359,22 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
}
|
||||
CommandProcessor.getInstance().executeCommand(project, () -> {
|
||||
final Template template = WriteAction.compute(builder::buildInlineTemplate);
|
||||
editor.getCaretModel().moveToOffset(startOffset);
|
||||
finalEditor.getCaretModel().moveToOffset(startOffset);
|
||||
final TemplateManager templateManager = TemplateManager.getInstance(project);
|
||||
templateManager.startTemplate(editor, template);
|
||||
templateManager.startTemplate(finalEditor, template);
|
||||
}, LangBundle.message("action.rename.text"), null);
|
||||
})
|
||||
.submit(AppExecutorUtil.getAppExecutorService());
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Editor getEditor(@NotNull Project project, @Nullable Editor editor, VirtualFile file) {
|
||||
Editor finalEditor = editor == null || !editor.getVirtualFile().equals(file) ? getEditor(project, file) : editor;
|
||||
if (finalEditor == null) return null;
|
||||
return finalEditor;
|
||||
}
|
||||
|
||||
static class NameExpression extends Expression {
|
||||
private final String myOrig;
|
||||
private final LookupElement[] cachedLookupElements;
|
||||
@@ -386,7 +468,7 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
return ReadAction.nonBlocking(supplier).expireWhen(context.project()::isDisposed).executeSynchronously();
|
||||
}, name, true, context.project());
|
||||
if (next == null) return;
|
||||
CommandProcessor.getInstance().executeCommand(context.project(), () -> executeInteractively(context, next, editor), name, null);
|
||||
executeInteractively(context, next, editor);
|
||||
}
|
||||
|
||||
private static VirtualFile actualize(@NotNull VirtualFile file) {
|
||||
@@ -426,7 +508,7 @@ public class ModCommandExecutorImpl implements ModCommandExecutor {
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Editor getEditor(@NotNull Project project, VirtualFile file) {
|
||||
private static @Nullable Editor getEditor(@NotNull Project project, VirtualFile file) {
|
||||
return FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, file), true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.lang.impl.modcommand;
|
||||
|
||||
import com.intellij.codeInsight.template.Expression;
|
||||
import com.intellij.codeInspection.ModCommands;
|
||||
import com.intellij.lang.Language;
|
||||
import com.intellij.lang.injection.InjectedLanguageManager;
|
||||
@@ -212,6 +213,7 @@ final class PsiUpdateImpl {
|
||||
private int myCaretOffset;
|
||||
private @NotNull TextRange mySelection;
|
||||
private final List<ModHighlight.HighlightInfo> myHighlightInfos = new ArrayList<>();
|
||||
private final List<ModStartTemplate.TemplateField> myTemplateFields = new ArrayList<>();
|
||||
private @Nullable ModRenameSymbol myRenameSymbol;
|
||||
private boolean myPositionUpdated = false;
|
||||
private @NlsContexts.Tooltip String myErrorMessage;
|
||||
@@ -337,6 +339,39 @@ final class PsiUpdateImpl {
|
||||
myHighlightInfos.add(new ModHighlight.HighlightInfo(range, attributesKey, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ModTemplateBuilder templateBuilder() {
|
||||
if (!myTemplateFields.isEmpty()) {
|
||||
throw new IllegalStateException("Template was already created");
|
||||
}
|
||||
return new ModTemplateBuilder() {
|
||||
@Override
|
||||
public @NotNull ModTemplateBuilder field(@NotNull PsiElement element, @NotNull Expression expression) {
|
||||
TextRange elementRange = getRange(element);
|
||||
if (elementRange == null) {
|
||||
throw new IllegalStateException("Unable to restore element for template");
|
||||
}
|
||||
TextRange range = mapRange(elementRange);
|
||||
myTemplateFields.add(new ModStartTemplate.ExpressionField(range, expression));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ModTemplateBuilder field(@NotNull PsiElement element,
|
||||
@NotNull String varName,
|
||||
@NotNull String dependantVariableName,
|
||||
boolean alwaysStopAt) {
|
||||
TextRange elementRange = getRange(element);
|
||||
if (elementRange == null) {
|
||||
throw new IllegalStateException("Unable to restore element for template");
|
||||
}
|
||||
TextRange range = mapRange(elementRange);
|
||||
myTemplateFields.add(new ModStartTemplate.DependantVariableField(range, varName, dependantVariableName, alwaysStopAt));
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveTo(int offset) {
|
||||
myPositionUpdated = true;
|
||||
@@ -434,6 +469,7 @@ final class PsiUpdateImpl {
|
||||
myCaretOffset = updateOffset(event, myCaretOffset, myCaretOffset == mySelection.getStartOffset() && mySelection.getLength() > 0);
|
||||
mySelection = updateRange(event, mySelection);
|
||||
myHighlightInfos.replaceAll(info -> info.withRange(updateRange(event, info.range())));
|
||||
myTemplateFields.replaceAll(info -> info.withRange(updateRange(event, info.range())));
|
||||
if (myRenameSymbol != null) {
|
||||
myRenameSymbol = myRenameSymbol.withRange(updateRange(event, myRenameSymbol.symbolRange()));
|
||||
}
|
||||
@@ -460,6 +496,9 @@ final class PsiUpdateImpl {
|
||||
}
|
||||
|
||||
private @NotNull ModCommand getCommand() {
|
||||
if (myRenameSymbol != null && !myTemplateFields.isEmpty()) {
|
||||
throw new IllegalStateException("Cannot have both rename and template commands");
|
||||
}
|
||||
if (myErrorMessage != null) {
|
||||
return error(myErrorMessage);
|
||||
}
|
||||
@@ -467,7 +506,8 @@ final class PsiUpdateImpl {
|
||||
.andThen(myChangedDirectories.values().stream()
|
||||
.flatMap(info -> info.createFileCommands(myTracker.myProject))
|
||||
.reduce(nop(), ModCommand::andThen))
|
||||
.andThen(getNavigateCommand()).andThen(getHighlightCommand()).andThen(myRenameSymbol == null ? nop() : myRenameSymbol)
|
||||
.andThen(getNavigateCommand()).andThen(getHighlightCommand()).andThen(getTemplateCommand())
|
||||
.andThen(myRenameSymbol == null ? nop() : myRenameSymbol)
|
||||
.andThen(myInfoMessage == null ? nop() : ModCommands.info(myInfoMessage));
|
||||
}
|
||||
|
||||
@@ -492,5 +532,11 @@ final class PsiUpdateImpl {
|
||||
if (myHighlightInfos.isEmpty()) return nop();
|
||||
return new ModHighlight(myNavigationFile, myHighlightInfos);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ModCommand getTemplateCommand() {
|
||||
if (myTemplateFields.isEmpty()) return nop();
|
||||
return new ModStartTemplate(myNavigationFile, myTemplateFields, f -> nop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,36 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
*/
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ipp.junit;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
|
||||
import com.intellij.codeInsight.template.TemplateBuilder;
|
||||
import com.intellij.codeInsight.template.TemplateBuilderFactory;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.codeInspection.PsiUpdateModCommandAction;
|
||||
import com.intellij.modcommand.ModPsiUpdater;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
|
||||
import com.intellij.psi.codeStyle.VariableKind;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.siyeh.IntentionPowerPackBundle;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* @author Dmitry Batkovich
|
||||
*/
|
||||
public class DataPointHolderConversionIntention extends PsiElementBaseIntentionAction {
|
||||
public class DataPointHolderConversionIntention extends PsiUpdateModCommandAction<PsiIdentifier> {
|
||||
private static final String THEORIES_PACKAGE = "org.junit.experimental.theories";
|
||||
private static final String DATA_POINT_FQN = THEORIES_PACKAGE + ".DataPoint";
|
||||
private static final String DATA_POINTS_FQN = THEORIES_PACKAGE + ".DataPoints";
|
||||
|
||||
public DataPointHolderConversionIntention() {
|
||||
super(PsiIdentifier.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) {
|
||||
protected void invoke(@NotNull ActionContext context, @NotNull PsiIdentifier element, @NotNull ModPsiUpdater updater) {
|
||||
final PsiElement holder = element.getParent();
|
||||
PsiModifierListOwner createdElement =
|
||||
holder instanceof PsiField ? convertToMethod((PsiField)holder) : convertToField((PsiMethod)holder);
|
||||
@@ -44,11 +47,8 @@ public class DataPointHolderConversionIntention extends PsiElementBaseIntentionA
|
||||
modifierList.setModifierProperty(PsiModifier.PUBLIC, true);
|
||||
createdElement = (PsiModifierListOwner)oldElement.replace(createdElement);
|
||||
|
||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());
|
||||
final TemplateBuilder templateBuilder = TemplateBuilderFactory.getInstance().createTemplateBuilder(createdElement);
|
||||
final PsiNameIdentifierOwner asNameIdOwner = (PsiNameIdentifierOwner)createdElement;
|
||||
templateBuilder.replaceElement(asNameIdOwner.getNameIdentifier(), asNameIdOwner.getName());
|
||||
templateBuilder.run(editor, false);
|
||||
updater.rename(asNameIdOwner, List.of(requireNonNull(asNameIdOwner.getName())));
|
||||
}
|
||||
|
||||
private static PsiField convertToField(final PsiMethod method) {
|
||||
@@ -80,33 +80,33 @@ public class DataPointHolderConversionIntention extends PsiElementBaseIntentionA
|
||||
assert body != null;
|
||||
|
||||
final PsiStatement methodCode =
|
||||
elementFactory.createStatementFromText(PsiKeyword.RETURN + " " + fieldInitializer.getText() + ";", null);
|
||||
elementFactory.createStatementFromText(PsiKeyword.RETURN + " " + requireNonNull(fieldInitializer).getText() + ";", null);
|
||||
body.add(methodCode);
|
||||
return method;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) {
|
||||
final Pair<PsiMember, PsiAnnotation> dataPointsHolder = extractDataPointsHolder(element);
|
||||
if (dataPointsHolder != null && isConvertible(dataPointsHolder.getFirst())) {
|
||||
final String annotation = StringUtil.getShortName(dataPointsHolder.getSecond().getQualifiedName());
|
||||
setText(IntentionPowerPackBundle.message("intention.name.replace.field.or.method", annotation, dataPointsHolder.getFirst() instanceof PsiMethod ? 0 : 1));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
protected @Nullable Presentation getPresentation(@NotNull ActionContext context, @NotNull PsiIdentifier element) {
|
||||
final DataPointsHolder dataPointsHolder = extractDataPointsHolder(element);
|
||||
if (dataPointsHolder == null || !isConvertible(dataPointsHolder.holder())) return null;
|
||||
final String annotation = requireNonNull(dataPointsHolder.annotation().getNameReferenceElement()).getReferenceName();
|
||||
return Presentation.of(IntentionPowerPackBundle.message("intention.name.replace.field.or.method", annotation,
|
||||
dataPointsHolder.holder() instanceof PsiMethod ? 0 : 1));
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static Pair<PsiMember, PsiAnnotation> extractDataPointsHolder(@NotNull final PsiElement element) {
|
||||
if (!(element instanceof PsiIdentifier)) {
|
||||
return null;
|
||||
}
|
||||
private static DataPointsHolder extractDataPointsHolder(@NotNull final PsiIdentifier element) {
|
||||
final PsiElement maybeHolder = element.getParent();
|
||||
if (!(maybeHolder instanceof PsiMethod || maybeHolder instanceof PsiField)) {
|
||||
return null;
|
||||
}
|
||||
final PsiMember holder = (PsiMember)maybeHolder;
|
||||
final PsiAnnotation annotation = AnnotationUtil.findAnnotation(holder, DATA_POINT_FQN, DATA_POINTS_FQN);
|
||||
return annotation == null ? null : Pair.create(holder, annotation);
|
||||
return annotation == null ? null : new DataPointsHolder(holder, annotation);
|
||||
}
|
||||
|
||||
private record DataPointsHolder(PsiMember holder, PsiAnnotation annotation) {
|
||||
}
|
||||
|
||||
private static boolean isConvertible(@NotNull final PsiMember member) {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
// "Replace by @DataPoint method" "true"
|
||||
class Foo {
|
||||
|
||||
@org.junit.experimental.theories.DataPoint
|
||||
public static String typedMethodNameFromTemplates() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
class Foo {
|
||||
|
||||
//wrong @DataPoint method declaration (len(params) != 0)
|
||||
@org.junit.experimental.the ories.DataPoint
|
||||
@org.junit.experimental.theories.DataPoint
|
||||
public static int b<caret>ar(int j) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
class Foo {
|
||||
|
||||
//wrong @DataPoint method declaration (returnType == void)
|
||||
@org.junit.experimental.the ories.DataPoint
|
||||
@org.junit.experimental.theories.DataPoint
|
||||
public static void ba<caret>r() {
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.siyeh.ipp.junit;
|
||||
|
||||
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixTestCase;
|
||||
import com.intellij.codeInsight.intention.IntentionAction;
|
||||
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
|
||||
import com.intellij.codeInsight.template.impl.TemplateState;
|
||||
import com.intellij.modcommand.ModCommand;
|
||||
import com.intellij.modcommand.ModCommandAction;
|
||||
import com.intellij.modcommand.ModRenameSymbol;
|
||||
import com.intellij.modcommand.ModUpdateFileText;
|
||||
import com.intellij.openapi.application.PluginPathManager;
|
||||
import com.intellij.psi.codeStyle.JavaCodeStyleSettings;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Dmitry Batkovich
|
||||
*/
|
||||
@@ -55,13 +61,24 @@ public class DataPointHolderConversionIntentionTest extends LightQuickFixTestCas
|
||||
public void testNameTyping() {
|
||||
configureByFile(getBasePath() + "/beforeNameTyping.java");
|
||||
TemplateManagerImpl.setTemplateTesting(getTestRootDisposable());
|
||||
doAction("Replace by @DataPoint method");
|
||||
final TemplateState state = TemplateManagerImpl.getTemplateState(getEditor());
|
||||
assertNotNull(state);
|
||||
type("typedMethodNameFromTemplates");
|
||||
state.nextTab();
|
||||
assertTrue(state.isFinished());
|
||||
checkResultByFile(getBasePath() + "/afterNameTyping.java");
|
||||
IntentionAction intentionAction = findActionWithText("Replace by @DataPoint method");
|
||||
ModCommandAction mc = ModCommandAction.unwrap(intentionAction);
|
||||
assertNotNull(mc);
|
||||
List<ModCommand> commands = mc.perform(ModCommandAction.ActionContext.from(getEditor(), getFile())).unpack();
|
||||
assertEquals(2, commands.size());
|
||||
assertInstanceOf(commands.get(0), ModUpdateFileText.class);
|
||||
String actualText = ((ModUpdateFileText)commands.get(0)).newText();
|
||||
assertEquals("""
|
||||
// "Replace by @DataPoint method" "true"
|
||||
class Foo {
|
||||
|
||||
@org.junit.experimental.theories.DataPoint
|
||||
public static String bar() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}""", actualText);
|
||||
assertInstanceOf(commands.get(1), ModRenameSymbol.class);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
Reference in New Issue
Block a user