[mod-commands] Basic support for templates and member chooser

GitOrigin-RevId: e7df218ea71800e71409de20474f43eb758e41de
This commit is contained in:
Tagir Valeev
2023-07-12 18:14:37 +02:00
committed by intellij-monorepo-bot
parent 95f104864a
commit 4c7609bdd7
38 changed files with 660 additions and 482 deletions

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -1,5 +1,5 @@
// "Initialize variable 'X'" "true-preview"
interface Foo {
char X = 0;
char X = 0 .Y;
int a = X;
}

View File

@@ -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){
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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){}
}

View File

@@ -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);
}

View File

@@ -3,9 +3,9 @@ class Test {
/**
*
*/
void foo() {
foo(0);
}
void foo() {
foo(0);
}
/**
* @param i

View File

@@ -1,8 +1,8 @@
// "Generate overloaded constructor with default parameter values" "true"
record Test(int x) {
Test() {
this(0);
}
this(0);
}
public Test {

View File

@@ -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;

View File

@@ -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) {}
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {
}
}
""");

View File

@@ -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);
}

View File

@@ -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();
}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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.

View File

@@ -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);
}
}
}

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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();
}
};
}
}

View File

@@ -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);
}

View File

@@ -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());
}
}
}

View File

@@ -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) {

View File

@@ -1,9 +0,0 @@
// "Replace by @DataPoint method" "true"
class Foo {
@org.junit.experimental.theories.DataPoint
public static String typedMethodNameFromTemplates() {
return null;
}
}

View File

@@ -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;
}

View File

@@ -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() {
}

View File

@@ -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