Files
openide/java/java-impl/src/com/intellij/refactoring/introduceVariable/JavaVariableInplaceIntroducer.java
Tagir Valeev d7869b5a91 KeymapUtil#getPrimaryShortcut
GitOrigin-RevId: 28370d4a5bda45fdb27537601e4a9af151ef39ba
2020-01-26 07:39:16 +00:00

515 lines
21 KiB
Java

/*
* Copyright 2000-2017 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.
*/
package com.intellij.refactoring.introduceVariable;
import com.intellij.codeInsight.intention.impl.TypeExpression;
import com.intellij.codeInsight.template.TemplateBuilderImpl;
import com.intellij.openapi.actionSystem.Shortcut;
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.editor.ScrollType;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.codeStyle.VariableKind;
import com.intellij.psi.scope.processor.VariablesProcessor;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiTypesUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.JavaRefactoringSettings;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduceParameter.AbstractJavaInplaceIntroducer;
import com.intellij.refactoring.rename.ResolveSnapshotProvider;
import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer;
import com.intellij.refactoring.ui.TypeSelectorManagerImpl;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.ui.NonFocusableCheckBox;
import com.intellij.util.ui.JBUI;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class JavaVariableInplaceIntroducer extends AbstractJavaInplaceIntroducer {
private SmartPsiElementPointer<? extends PsiElement> myPointer;
private JCheckBox myCanBeFinalCb;
private JCheckBox myCanBeVarTypeCb;
private final IntroduceVariableSettings mySettings;
private final SmartPsiElementPointer<PsiElement> myChosenAnchor;
private final boolean myCantChangeFinalModifier;
private final boolean myHasTypeSuggestion;
private ResolveSnapshotProvider.ResolveSnapshot myConflictResolver;
private final TypeExpression myExpression;
private final boolean myReplaceSelf;
private boolean myDeleteSelf = true;
private final boolean mySkipTypeExpressionOnStart;
private final PsiFile myFile;
private final boolean myCanBeVarType;
public JavaVariableInplaceIntroducer(final Project project,
IntroduceVariableSettings settings, PsiElement chosenAnchor, final Editor editor,
final PsiExpression expr,
final boolean cantChangeFinalModifier,
final PsiExpression[] occurrences,
final TypeSelectorManagerImpl selectorManager,
final String title) {
super(project, editor, RefactoringUtil.outermostParenthesizedExpression(expr), null, occurrences, selectorManager, title);
mySettings = settings;
myChosenAnchor = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(chosenAnchor);
myFile = chosenAnchor.getContainingFile();
myCantChangeFinalModifier = cantChangeFinalModifier;
myHasTypeSuggestion = selectorManager.getTypesForAll().length > 1;
myTitle = title;
myExpression = new TypeExpression(myProject, isReplaceAllOccurrences()
? myTypeSelectorManager.getTypesForAll()
: myTypeSelectorManager.getTypesForOne());
final List<RangeMarker> rangeMarkers = getOccurrenceMarkers();
editor.putUserData(ReassignVariableUtil.OCCURRENCES_KEY,
rangeMarkers.toArray(new RangeMarker[0]));
PsiElement parent = myExpr.getParent();
myReplaceSelf = parent instanceof PsiExpressionStatement && !(parent.getParent() instanceof PsiSwitchLabeledRuleStatement);
mySkipTypeExpressionOnStart = !(myExpr instanceof PsiFunctionalExpression && myReplaceSelf);
myCanBeVarType = IntroduceVariableBase.canBeExtractedWithoutExplicitType(myExpr);
}
@Override
protected void beforeTemplateStart() {
if (!mySkipTypeExpressionOnStart) {
final PsiVariable variable = getVariable();
final PsiTypeElement typeElement = variable != null ? variable.getTypeElement() : null;
if (typeElement != null) {
myEditor.getCaretModel().moveToOffset(typeElement.getTextOffset());
}
}
final ResolveSnapshotProvider resolveSnapshotProvider = VariableInplaceRenamer.INSTANCE.forLanguage(myScope.getLanguage());
myConflictResolver = resolveSnapshotProvider != null ? resolveSnapshotProvider.createSnapshot(myScope) : null;
super.beforeTemplateStart();
}
@Override
@Nullable
protected PsiVariable getVariable() {
final PsiElement declarationStatement = myPointer != null ? myPointer.getElement() : null;
if (declarationStatement instanceof PsiDeclarationStatement) {
PsiElement[] declaredElements = ((PsiDeclarationStatement)declarationStatement).getDeclaredElements();
return declaredElements.length == 0 ? null : (PsiVariable)declaredElements[0];
}
return declarationStatement instanceof PsiVariable ? (PsiVariable)declarationStatement : null;
}
@Override
protected String getActionName() {
return "IntroduceVariable";
}
@Override
protected String getRefactoringId() {
return "refactoring.extractVariable";
}
@Override
protected void restoreState(@NotNull PsiVariable psiField) {
if (myDeleteSelf) return;
super.restoreState(psiField);
}
@Override
protected boolean ensureValid() {
final PsiVariable variable = getVariable();
return variable != null && isIdentifier(getInputName(), variable.getLanguage());
}
@Override
protected void performCleanup() {
super.performCleanup();
PsiVariable variable = getVariable();
if (variable != null) {
CommandProcessor.getInstance().executeCommand(myProject, () -> super.restoreState(variable), null, null);
}
}
@Override
protected void deleteTemplateField(PsiVariable variable) {
if (!myDeleteSelf) return;
if (myReplaceSelf) {
variable.replace(variable.getInitializer());
} else {
super.deleteTemplateField(variable);
}
}
@Override
protected PsiExpression getBeforeExpr() {
final PsiVariable variable = getVariable();
if (variable != null) {
return variable.getInitializer();
}
return super.getBeforeExpr();
}
@Override
protected void performIntroduce() {
final PsiVariable psiVariable = getVariable();
if (psiVariable == null) {
return;
}
TypeSelectorManagerImpl.typeSelected(psiVariable.getType(), myTypeSelectorManager.getDefaultType());
if (myCanBeFinalCb != null) {
JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_FINALS = psiVariable.hasModifierProperty(PsiModifier.FINAL);
}
if (myCanBeVarTypeCb != null) {
JavaRefactoringSettings.getInstance().INTRODUCE_LOCAL_CREATE_VAR_TYPE = myCanBeVarTypeCb.isSelected();
}
final Document document = myEditor.getDocument();
LOG.assertTrue(psiVariable.isValid());
adjustLine(psiVariable, document);
int startOffset = getExprMarker() != null && getExprMarker().isValid() ? getExprMarker().getStartOffset() : psiVariable.getTextOffset();
final PsiFile file = psiVariable.getContainingFile();
final PsiReference referenceAt = file.findReferenceAt(startOffset);
if (referenceAt != null && referenceAt.resolve() instanceof PsiVariable) {
startOffset = referenceAt.getElement().getTextRange().getEndOffset();
}
else {
final PsiDeclarationStatement declarationStatement = PsiTreeUtil.getParentOfType(psiVariable, PsiDeclarationStatement.class);
if (declarationStatement != null) {
startOffset = declarationStatement.getTextRange().getEndOffset();
}
}
myEditor.getCaretModel().moveToOffset(startOffset);
myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
ApplicationManager.getApplication().runWriteAction(() -> {
if (myConflictResolver != null && myInsertedName != null && isIdentifier(myInsertedName, psiVariable.getLanguage())) {
myConflictResolver.apply(psiVariable.getName());
}
if (psiVariable.getInitializer() != null) {
appendTypeCasts(getOccurrenceMarkers(), file, myProject, psiVariable);
}
});
}
@Override
public boolean isReplaceAllOccurrences() {
return mySettings.isReplaceAllOccurrences();
}
@Override
public void setReplaceAllOccurrences(boolean allOccurrences) {}
@Override
protected boolean startsOnTheSameElement(RefactoringActionHandler handler, PsiElement element) {
return handler instanceof IntroduceVariableHandler && super.startsOnTheSameElement(handler, element);
}
@Override
@Nullable
protected JComponent getComponent() {
if (myCantChangeFinalModifier && !(myCanBeVarType && getVariable() instanceof PsiLocalVariable)) return null;
if (!myCantChangeFinalModifier) {
myCanBeFinalCb = new NonFocusableCheckBox("Declare final");
myCanBeFinalCb.setSelected(createFinals());
myCanBeFinalCb.setMnemonic('f');
final FinalListener finalListener = new FinalListener(myEditor);
myCanBeFinalCb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
WriteCommandAction.writeCommandAction(myProject).withName(getCommandName()).withGroupId(getCommandName()).run(() -> {
PsiDocumentManager.getInstance(myProject).commitDocument(myEditor.getDocument());
final PsiVariable variable = getVariable();
if (variable != null) {
finalListener.perform(myCanBeFinalCb.isSelected(), variable);
}
});
}
});
}
if (myCanBeVarType && getVariable() instanceof PsiLocalVariable) {
myCanBeVarTypeCb = new NonFocusableCheckBox(RefactoringBundle.message("declare.var.type"));
myCanBeVarTypeCb.setSelected(IntroduceVariableBase.createVarType());
myCanBeVarTypeCb.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
WriteCommandAction.writeCommandAction(myProject).withName(getCommandName()).withGroupId(getCommandName()).run(() -> {
final PsiVariable variable = getVariable();
if (variable != null) {
PsiTypeElement typeElement = variable.getTypeElement();
LOG.assertTrue(typeElement != null);
if (myCanBeVarTypeCb.isSelected()) {
IntroduceVariableBase.expandDiamondsAndReplaceExplicitTypeWithVar(typeElement, variable);
}
else {
typeElement = PsiTypesUtil.replaceWithExplicitType(typeElement);
if (typeElement != null) { //simplify as it was before `var`
IntroduceVariableBase.simplifyVariableInitializer(variable.getInitializer(), typeElement.getType());
}
}
}
});
}
});
}
final JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(null);
GridBagConstraints gridBagConstraints = new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL,
JBUI.insets(5), 0, 0);
if (myCanBeFinalCb != null) {
panel.add(myCanBeFinalCb, gridBagConstraints);
}
if (myCanBeVarTypeCb != null) {
panel.add(myCanBeVarTypeCb, gridBagConstraints);
}
gridBagConstraints.fill = GridBagConstraints.BOTH;
panel.add(Box.createVerticalBox(), gridBagConstraints);
return panel;
}
@Override
protected void addAdditionalVariables(TemplateBuilderImpl builder) {
final PsiVariable variable = getVariable();
if (variable != null) {
final PsiTypeElement typeElement = variable.getTypeElement();
if (typeElement != null) {
builder.replaceElement(typeElement, "Variable_Type", AbstractJavaInplaceIntroducer.createExpression(myExpression, typeElement.getText()), true, mySkipTypeExpressionOnStart);
}
}
}
@Override
protected void collectAdditionalElementsToRename(@NotNull List<Pair<PsiElement, TextRange>> stringUsages) {
if (isReplaceAllOccurrences()) {
for (PsiExpression expression : getOccurrences()) {
LOG.assertTrue(expression.isValid(), expression.getText());
stringUsages.add(Pair.create(expression, new TextRange(0, expression.getTextLength())));
}
} else if (getExpr() != null && !myReplaceSelf && getExpr().getParent() != getVariable()) {
final PsiExpression expr = getExpr();
LOG.assertTrue(expr.isValid(), expr.getText());
stringUsages.add(Pair.create(expr, new TextRange(0, expr.getTextLength())));
}
}
@Override
protected void addReferenceAtCaret(Collection<PsiReference> refs) {
if (!isReplaceAllOccurrences()) {
final PsiExpression expr = getExpr();
if (expr == null && !myReplaceSelf || expr != null && expr.getParent() == getVariable()) {
return;
}
}
super.addReferenceAtCaret(refs);
}
private static void appendTypeCasts(List<RangeMarker> occurrenceMarkers,
PsiFile file,
Project project,
@Nullable PsiVariable psiVariable) {
if (occurrenceMarkers != null) {
for (RangeMarker occurrenceMarker : occurrenceMarkers) {
final PsiElement refVariableElement = file.findElementAt(occurrenceMarker.getStartOffset());
final PsiReferenceExpression referenceExpression = PsiTreeUtil.getParentOfType(refVariableElement, PsiReferenceExpression.class);
if (referenceExpression != null) {
final PsiElement parent = referenceExpression.getParent();
if (parent instanceof PsiVariable) {
createCastInVariableDeclaration(project, (PsiVariable)parent);
}
else if (parent instanceof PsiReferenceExpression && psiVariable != null) {
final PsiExpression initializer = psiVariable.getInitializer();
LOG.assertTrue(initializer != null);
final PsiType type = initializer.getType();
if (((PsiReferenceExpression)parent).resolve() == null && type != null && !type.equals(psiVariable.getType())) {
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
final PsiExpression castedExpr =
elementFactory.createExpressionFromText("((" + type.getCanonicalText() + ")" + referenceExpression.getText() + ")", parent);
JavaCodeStyleManager.getInstance(project).shortenClassReferences(referenceExpression.replace(castedExpr));
}
}
}
}
}
if (psiVariable != null && psiVariable.isValid()) {
createCastInVariableDeclaration(project, psiVariable);
}
}
private static void createCastInVariableDeclaration(Project project, PsiVariable psiVariable) {
final PsiExpression initializer = psiVariable.getInitializer();
LOG.assertTrue(initializer != null);
final PsiType type = psiVariable.getType();
final PsiType initializerType = initializer.getType();
if (initializerType != null &&
!TypeConversionUtil.isAssignable(type, initializerType) &&
!PsiTypesUtil.hasUnresolvedComponents(type)) {
final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project);
final PsiExpression castExpr =
elementFactory.createExpressionFromText("(" + type.getCanonicalText() + ")" + initializer.getText(), psiVariable);
JavaCodeStyleManager.getInstance(project).shortenClassReferences(initializer.replace(castExpr));
}
}
@Nullable
private static String getAdvertisementText(final PsiDeclarationStatement declaration,
final PsiType type,
final boolean hasTypeSuggestion) {
final VariablesProcessor processor = ReassignVariableUtil.findVariablesOfType(declaration, type);
if (processor.size() > 0) {
final Shortcut shortcut = KeymapUtil.getPrimaryShortcut("IntroduceVariable");
if (shortcut != null) {
return "Press " + KeymapUtil.getShortcutText(shortcut) + " to reassign existing variable";
}
}
if (hasTypeSuggestion) {
final Shortcut shortcut = KeymapUtil.getPrimaryShortcut("PreviousTemplateVariable");
if (shortcut != null) {
return "Press " + KeymapUtil.getShortcutText(shortcut) + " to change type";
}
}
return null;
}
protected boolean createFinals() {
return IntroduceVariableBase.createFinals(myFile);
}
public static void adjustLine(final PsiVariable psiVariable, final Document document) {
final int modifierListOffset = psiVariable.getTextRange().getStartOffset();
final int varLineNumber = document.getLineNumber(modifierListOffset);
ApplicationManager.getApplication().runWriteAction(() -> {
PsiDocumentManager.getInstance(psiVariable.getProject()).doPostponedOperationsAndUnblockDocument(document);
CodeStyleManager.getInstance(psiVariable.getProject()).adjustLineIndent(document, document.getLineStartOffset(varLineNumber));
});
}
@Override
protected PsiVariable createFieldToStartTemplateOn(String[] names, PsiType psiType) {
PsiVariable variable = introduceVariable();
final PsiVariable restoredVar = getVariable();
if (restoredVar != null) {
variable = restoredVar;
}
if (variable == null) return null;
if (isReplaceAllOccurrences()) {
List<RangeMarker> occurrences = new ArrayList<>();
ReferencesSearch.search(variable).forEach(reference -> {
occurrences.add(createMarker(reference.getElement()));
});
setOccurrenceMarkers(occurrences);
myOccurrences = new PsiExpression[occurrences.size()];
}
final PsiIdentifier identifier = variable.getNameIdentifier();
if (identifier != null) {
myEditor.getCaretModel().moveToOffset(identifier.getTextOffset());
}
try {
myDeleteSelf = false;
restoreState(variable);
}
finally {
myDeleteSelf = true;
}
PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myEditor.getDocument());
initOccurrencesMarkers();
return variable;
}
@Nullable
protected PsiVariable introduceVariable() {
PsiVariable variable = VariableExtractor
.introduce(myProject, myExpr, myEditor, myChosenAnchor.getElement(), getOccurrences(), mySettings);
SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(myProject);
if (variable instanceof PsiField || variable instanceof PsiResourceVariable) {
myPointer = smartPointerManager.createSmartPsiElementPointer(variable);
}
else {
final PsiDeclarationStatement declarationStatement = PsiTreeUtil.getParentOfType(variable, PsiDeclarationStatement.class);
if (declarationStatement != null) {
SmartPsiElementPointer<PsiDeclarationStatement> pointer = smartPointerManager.createSmartPsiElementPointer(declarationStatement);
myPointer = pointer;
myEditor.putUserData(ReassignVariableUtil.DECLARATION_KEY, pointer);
setAdvertisementText(getAdvertisementText(declarationStatement, variable.getType(), myHasTypeSuggestion));
}
}
PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myEditor.getDocument());
return variable;
}
@Override
protected int getCaretOffset() {
final PsiVariable variable = getVariable();
if (variable != null) {
final PsiIdentifier identifier = variable.getNameIdentifier();
if (identifier != null) {
return identifier.getTextOffset();
}
}
return super.getCaretOffset();
}
@Override
public void finish(boolean success) {
super.finish(success);
myEditor.putUserData(ReassignVariableUtil.DECLARATION_KEY, null);
}
@Override
protected String[] suggestNames(PsiType defaultType, String propName) {
return IntroduceVariableBase.getSuggestedName(defaultType, myExpr).names;
}
@Override
protected VariableKind getVariableKind() {
return VariableKind.LOCAL_VARIABLE;
}
}