Ctrl-Alt-V Ctrl-Alt-V to reassign expression to existing variable

This commit is contained in:
anna
2010-11-08 17:21:29 +03:00
parent b0d4c4f26c
commit c288da64df
9 changed files with 276 additions and 26 deletions
@@ -27,8 +27,8 @@ import com.intellij.codeInsight.CodeInsightUtil;
import com.intellij.codeInsight.completion.JavaCompletionUtil;
import com.intellij.codeInsight.highlighting.HighlightManager;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.codeInsight.template.TemplateBuilderImpl;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.ide.DataManager;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.lang.LanguageRefactoringSupport;
import com.intellij.lang.refactoring.RefactoringSupportProvider;
@@ -39,13 +39,10 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Pass;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.psi.*;
@@ -55,7 +52,6 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.*;
import com.intellij.refactoring.introduceField.ElementToWorkOn;
import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler;
import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer;
import com.intellij.refactoring.ui.TypeSelectorManagerImpl;
import com.intellij.refactoring.util.CommonRefactoringUtil;
@@ -65,7 +61,6 @@ import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.refactoring.util.occurences.ExpressionOccurenceManager;
import com.intellij.refactoring.util.occurences.NotInSuperCallOccurenceFilter;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.*;
import org.jetbrains.annotations.NonNls;
@@ -73,6 +68,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.List;
public abstract class IntroduceVariableBase extends IntroduceHandlerBase implements RefactoringActionHandler {
private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceVariable.IntroduceVariableBase");
@@ -367,6 +363,7 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase impleme
}
if (expr == null) {
if (ReassignVariableUtil.reassign(editor)) return false;
String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.block.should.represent.an.expression"));
showErrorMessage(project, editor, message);
return false;
@@ -475,11 +472,21 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase impleme
public void run() {
ApplicationManager.getApplication().runWriteAction(runnable);
if (isInplaceAvailableOnDataContext) {
PsiVariable elementToRename = variable.get().getElement();
final PsiVariable elementToRename = variable.get().getElement();
if (elementToRename != null) {
editor.getCaretModel().moveToOffset(elementToRename.getTextOffset());
new VariableInplaceRenamer(elementToRename, editor)
.performInplaceRename(false, new LinkedHashSet<String>(Arrays.asList(suggestedName.names)), new Consumer<Boolean>(){
final PsiDeclarationStatement declarationStatement = PsiTreeUtil.getParentOfType(elementToRename, PsiDeclarationStatement.class);
editor.putUserData(ReassignVariableUtil.DECLARATION_KEY, declarationStatement);
final VariableInplaceRenamer renamer = new VariableInplaceRenamer(elementToRename, editor){
@Override
protected void addAdditionalVariables(TemplateBuilderImpl builder) {
builder.replaceElement(elementToRename.getTypeElement(), "Variable_Type", ReassignVariableUtil
.createExpression(typeSelectorManager), false, true);
}
};
renamer.setAdvertisementText(
ReassignVariableUtil.getAdvertisementText(editor, declarationStatement, elementToRename.getType()));
renamer.performInplaceRename(false, new LinkedHashSet<String>(Arrays.asList(suggestedName.names)), new Consumer<Boolean>() {
@Override
public void consume(Boolean apply) {
if (apply) {
@@ -492,6 +499,7 @@ public abstract class IntroduceVariableBase extends IntroduceHandlerBase impleme
}
editor.getCaretModel().moveToOffset(startOffset);
}
editor.putUserData(ReassignVariableUtil.DECLARATION_KEY, null);
exprMarker.dispose();
}
});
@@ -0,0 +1,188 @@
/*
* Copyright 2000-2010 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.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.template.Expression;
import com.intellij.codeInsight.template.ExpressionContext;
import com.intellij.codeInsight.template.TextResult;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.ide.ui.ListCellRendererWrapper;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.Shortcut;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.keymap.Keymap;
import com.intellij.openapi.keymap.KeymapManager;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Key;
import com.intellij.psi.*;
import com.intellij.psi.scope.processor.VariablesProcessor;
import com.intellij.psi.scope.util.PsiScopesUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.refactoring.rename.inplace.VariableInplaceRenamer;
import com.intellij.refactoring.ui.TypeSelectorManager;
import com.intellij.refactoring.ui.TypeSelectorManagerImpl;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBList;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
/**
* User: anna
* Date: 11/8/10
*/
public class ReassignVariableUtil {
static final Key<PsiDeclarationStatement> DECLARATION_KEY = Key.create("var.type");
static boolean reassign(final Editor editor) {
final PsiDeclarationStatement declaration = editor.getUserData(DECLARATION_KEY);
final PsiType type = getVariableType(declaration);
if (type != null) {
VariablesProcessor proc = findVariablesOfType(editor, declaration, type);
if (proc.size() > 0) {
if (proc.size() == 1) {
replaceWithAssignment(declaration, proc.getResult(0), editor);
return true;
}
final DefaultListModel model = new DefaultListModel();
for (int i = 0; i < proc.size(); i++) {
model.addElement(proc.getResult(i));
}
final JList list = new JBList(model);
list.setCellRenderer(new ListCellRendererWrapper(new DefaultListCellRenderer()) {
@Override
public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
if (value instanceof PsiVariable) {
setText(((PsiVariable)value).getName());
setIcon(((PsiVariable)value).getIcon(0));
}
}
});
final VisualPosition visualPosition = editor.getCaretModel().getVisualPosition();
final Point point = editor.visualPositionToXY(new VisualPosition(visualPosition.line + 1, visualPosition.column));
JBPopupFactory.getInstance().createListPopupBuilder(list)
.setTitle("Choose variable to reassign")
.setRequestFocus(true)
.setItemChoosenCallback(new Runnable() {
public void run() {
replaceWithAssignment(declaration, (PsiVariable)list.getSelectedValue(), editor);
}
}).createPopup().show(new RelativePoint(editor.getContentComponent(), point));
}
return true;
}
return false;
}
@Nullable
private static PsiType getVariableType(@Nullable PsiDeclarationStatement declaration) {
if (declaration != null) {
final PsiElement[] declaredElements = declaration.getDeclaredElements();
if (declaredElements.length > 0 && declaredElements[0] instanceof PsiVariable) {
return ((PsiVariable)declaredElements[0]).getType();
}
}
return null;
}
private static VariablesProcessor findVariablesOfType(Editor editor, final PsiDeclarationStatement declaration, final PsiType type) {
VariablesProcessor proc = new VariablesProcessor(false) {
@Override
protected boolean check(PsiVariable var, ResolveState state) {
for (PsiElement element : declaration.getDeclaredElements()) {
if (element == var) return false;
}
return TypeConversionUtil.isAssignable(var.getType(), type);
}
};
PsiElement scope = editor.getUserData(DECLARATION_KEY);
while (scope != null) {
if (scope instanceof PsiFile || scope instanceof PsiMethod || scope instanceof PsiClassInitializer) break;
scope = scope.getParent();
}
if (scope == null) return proc;
PsiScopesUtil.treeWalkUp(proc, declaration, scope);
return proc;
}
static void replaceWithAssignment(final PsiDeclarationStatement declaration, final PsiVariable variable, Editor editor) {
final PsiVariable var = (PsiVariable)declaration.getDeclaredElements()[0];
final PsiExpression initializer = var.getInitializer();
new WriteCommandAction(declaration.getProject()) {
@Override
protected void run(Result result) throws Throwable {
//would generate red code for final variables
declaration.replace(JavaPsiFacade.getElementFactory(variable.getProject())
.createStatementFromText(variable.getName() + " = " + initializer.getText() + ";", declaration));
}
}.execute();
finishTemplate(editor);
}
private static void finishTemplate(Editor editor) {
final TemplateState templateState = TemplateManagerImpl.getTemplateState(editor);
final VariableInplaceRenamer renamer = editor.getUserData(VariableInplaceRenamer.INPLACE_RENAMER);
if (templateState != null && renamer != null) {
templateState.gotoEnd(true);
editor.putUserData(VariableInplaceRenamer.INPLACE_RENAMER, null);
}
}
static Expression createExpression(final TypeSelectorManagerImpl typeSelectorManager) {
final PsiType[] types = typeSelectorManager.getTypesForAll();
return new Expression() {
@Override
public com.intellij.codeInsight.template.Result calculateResult(ExpressionContext context) {
return new TextResult(typeSelectorManager.getDefaultType().getPresentableText());
}
@Override
public com.intellij.codeInsight.template.Result calculateQuickResult(ExpressionContext context) {
return new TextResult(typeSelectorManager.getDefaultType().getPresentableText());
}
@Override
public LookupElement[] calculateLookupItems(ExpressionContext context) {
LookupElement[] result = new LookupElement[types.length];
for (int i = 0, typesLength = types.length; i < typesLength; i++) {
result[i] = LookupElementBuilder.create(types[i].getPresentableText());
}
return result;
}
};
}
static String getAdvertisementText(Editor editor, PsiDeclarationStatement declaration, PsiType type) {
final VariablesProcessor processor = findVariablesOfType(editor, declaration, type);
if (processor.size() > 0) {
final Keymap keymap = KeymapManager.getInstance().getActiveKeymap();
final Shortcut[] shortcuts = keymap.getShortcuts("IntroduceVariable");
if (shortcuts.length > 0) {
return "Press " + shortcuts[0] + " to reassign existing variable";
}
}
return "Press Shift Tab to change type";
}
}
@@ -29,5 +29,10 @@ public abstract class Expression {
@Nullable
public abstract LookupElement[] calculateLookupItems(ExpressionContext context);
@Nullable
public String getAdvertisingText() {
return null;
}
}
@@ -29,7 +29,15 @@ public abstract class Template {
}
public abstract Variable addVariable(Expression expression, boolean isAlwaysStopAt);
public abstract Variable addVariable(@NonNls String name, Expression expression, Expression defaultValueExpression, boolean isAlwaysStopAt);
public Variable addVariable(@NonNls String name, Expression expression, Expression defaultValueExpression, boolean isAlwaysStopAt) {
return addVariable(name, expression, defaultValueExpression, isAlwaysStopAt, false);
}
public abstract Variable addVariable(@NonNls String name,
Expression expression,
Expression defaultValueExpression,
boolean isAlwaysStopAt,
boolean skipOnStart);
public abstract Variable addVariable(@NonNls String name, @NonNls String expression, @NonNls String defaultValueExpression, boolean isAlwaysStopAt);
public abstract void addEndVariable();
@@ -46,6 +46,7 @@ public class TemplateBuilderImpl implements TemplateBuilder {
private final Map<RangeMarker,Expression> myExpressions = new HashMap<RangeMarker, Expression>();
private final Map<RangeMarker,String> myVariableExpressions = new HashMap<RangeMarker, String>();
private final Map<RangeMarker, Boolean> myAlwaysStopAtMap = new HashMap<RangeMarker, Boolean>();
private final Map<RangeMarker, Boolean> mySkipOnStartMap = new HashMap<RangeMarker, Boolean>();
private final Map<RangeMarker, String> myVariableNamesMap = new HashMap<RangeMarker, String>();
private final Set<RangeMarker> myElements = new TreeSet<RangeMarker>(RangeMarker.BY_START_OFFSET);
@@ -79,10 +80,7 @@ public class TemplateBuilderImpl implements TemplateBuilder {
}
public void replaceElement(PsiElement element, String varName, Expression expression, boolean alwaysStopAt) {
final RangeMarker key = wrapElement(element);
myAlwaysStopAtMap.put(key, alwaysStopAt ? Boolean.TRUE : Boolean.FALSE);
myVariableNamesMap.put(key, varName);
replaceElement(key, expression);
replaceElement(element, varName, expression, alwaysStopAt, false);
}
public void replaceElement(PsiReference ref, String varName, Expression expression, boolean alwaysStopAt) {
@@ -201,7 +199,8 @@ public class TemplateBuilderImpl implements TemplateBuilder {
: myVariableNamesMap.get(element);
if (expression != null) {
template.addVariable(variableName, expression, expression, alwaysStopAt);
final Boolean skipOnStart = mySkipOnStartMap.get(element);
template.addVariable(variableName, expression, expression, alwaysStopAt, skipOnStart != null && skipOnStart.booleanValue());
}
else {
template.addVariableSegment(variableName);
@@ -258,4 +257,12 @@ public class TemplateBuilderImpl implements TemplateBuilder {
TemplateManager.getInstance(myFile.getProject()).startTemplate(editor, template);
}
public void replaceElement(PsiElement element, String varName, Expression expression, boolean alwaysStopAt, boolean skipOnStart) {
final RangeMarker key = wrapElement(element);
myAlwaysStopAtMap.put(key, alwaysStopAt ? Boolean.TRUE : Boolean.FALSE);
myVariableNamesMap.put(key, varName);
mySkipOnStartMap.put(key, Boolean.valueOf(skipOnStart));
replaceElement(key, expression);
}
}
@@ -139,12 +139,16 @@ public class TemplateImpl extends Template implements SchemeElement {
return addVariable("__Variable" + myVariables.size(), expression, isAlwaysStopAt);
}
public Variable addVariable(String name, Expression expression, Expression defaultValueExpression, boolean isAlwaysStopAt) {
public Variable addVariable(String name,
Expression expression,
Expression defaultValueExpression,
boolean isAlwaysStopAt,
boolean skipOnStart) {
if (mySegments != null) {
Segment segment = new Segment(name, myTemplateText.length());
mySegments.add(segment);
}
Variable variable = new Variable(name, expression, defaultValueExpression, isAlwaysStopAt);
Variable variable = new Variable(name, expression, defaultValueExpression, isAlwaysStopAt, skipOnStart);
myVariables.add(variable);
return variable;
}
@@ -442,6 +446,10 @@ public class TemplateImpl extends Template implements SchemeElement {
}
}
public boolean skipOnStart(int i) {
return myVariables.get(i).skipOnStart();
}
private static class Segment {
public String name;
public int offset;
@@ -473,7 +473,7 @@ public class TemplateState implements Disposable {
itemSelected(lookupItems[0], psiFile, currentSegmentNumber, ' ', lookupItems);
}
else {
runLookup(currentSegmentNumber, lookupItems, psiFile);
runLookup(currentSegmentNumber, lookupItems, expressionNode.getAdvertisingText(), psiFile);
}
}
else {
@@ -486,12 +486,14 @@ public class TemplateState implements Disposable {
focusCurrentHighlighter(true);
}
private void runLookup(final int currentSegmentNumber, final LookupElement[] lookupItems, final PsiFile psiFile) {
private void runLookup(final int currentSegmentNumber, final LookupElement[] lookupItems, String advertisingText, final PsiFile psiFile) {
if (myEditor == null) return;
final LookupManager lookupManager = LookupManager.getInstance(myProject);
final Lookup lookup = lookupManager.showLookup(myEditor, lookupItems);
((LookupImpl)lookup).setAdvertisementText(advertisingText);
((LookupImpl)lookup).refreshUi();
ourLookupShown = true;
lookup.addLookupListener(new LookupAdapter() {
public void lookupCanceled(LookupEvent event) {
@@ -864,6 +866,9 @@ public class TemplateState implements Disposable {
}
int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
if (segmentNumber < 0) return false;
if (myCurrentVariableNumber == -1) {
if (myTemplate.skipOnStart(currentVariableNumber)) return false;
}
int start = mySegments.getSegmentStart(segmentNumber);
ExpressionContext context = createExpressionContext(start);
Result result = expression.calculateResult(context);
@@ -27,12 +27,14 @@ public class Variable implements Cloneable {
private String myDefaultValueString;
private Expression myDefaultValueExpression;
private boolean mySkipOnStart;
public Variable(String name, Expression expression, Expression defaultValueExpression, boolean alwaysStopAt) {
public Variable(String name, Expression expression, Expression defaultValueExpression, boolean alwaysStopAt, boolean skipOnStart) {
myName = name;
myExpression = expression;
myDefaultValueExpression = defaultValueExpression;
myAlwaysStopAt = alwaysStopAt;
mySkipOnStart = skipOnStart;
}
public Variable(String name, String expression, String defaultValueString, boolean alwaysStopAt) {
@@ -40,6 +42,7 @@ public class Variable implements Cloneable {
myExpressionString = expression;
myDefaultValueString = defaultValueString;
myAlwaysStopAt = alwaysStopAt;
mySkipOnStart = false;
}
public String getExpressionString() {
@@ -103,6 +106,7 @@ public class Variable implements Cloneable {
final Variable variable = (Variable) o;
if (myAlwaysStopAt != variable.myAlwaysStopAt) return false;
if (mySkipOnStart != variable.mySkipOnStart) return false;
if (myDefaultValueString != null ? !myDefaultValueString.equals(variable.myDefaultValueString) : variable.myDefaultValueString != null) return false;
if (myExpressionString != null ? !myExpressionString.equals(variable.myExpressionString) : variable.myExpressionString != null) return false;
if (myName != null ? !myName.equals(variable.myName) : variable.myName != null) return false;
@@ -114,8 +118,13 @@ public class Variable implements Cloneable {
int result;
result = (myName != null ? myName.hashCode() : 0);
result = 29 * result + (myAlwaysStopAt ? 1 : 0);
result = 29 * result + (mySkipOnStart ? 1 : 0);
result = 29 * result + (myExpressionString != null ? myExpressionString.hashCode() : 0);
result = 29 * result + (myDefaultValueString != null ? myDefaultValueString.hashCode() : 0);
return result;
}
public boolean skipOnStart() {
return mySkipOnStart;
}
}
@@ -31,7 +31,6 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.colors.EditorColors;
import com.intellij.openapi.editor.colors.EditorColorsManager;
import com.intellij.openapi.editor.markup.RangeHighlighter;
@@ -39,7 +38,6 @@ import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
@@ -60,7 +58,6 @@ import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.refactoring.util.TextOccurrencesUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.Consumer;
import com.intellij.util.Function;
import com.intellij.util.PairProcessor;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.containers.Stack;
@@ -89,6 +86,12 @@ public class VariableInplaceRenamer {
private final Editor myEditor;
private final Project myProject;
public void setAdvertisementText(String advertisementText) {
myAdvertisementText = advertisementText;
}
private String myAdvertisementText;
private static final Stack<VariableInplaceRenamer> ourRenamersStack = new Stack<VariableInplaceRenamer>();
public VariableInplaceRenamer(@NotNull PsiNamedElement elementToRename, Editor editor) {
@@ -178,7 +181,8 @@ public class VariableInplaceRenamer {
for (PsiReference ref : refs) {
addVariable(ref, selectedElement, builder, offset, nameSuggestions);
}
addAdditionalVariables(builder);
final PsiElement scope1 = scope;
final int renameOffset = myElementToRename.getTextOffset();
CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
@@ -260,6 +264,9 @@ public class VariableInplaceRenamer {
return true;
}
protected void addAdditionalVariables(TemplateBuilderImpl builder) {
}
protected void addReferenceAtCaret(Collection<PsiReference> refs) {
PsiFile myEditorFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());
// Note, that myEditorFile can be different from myElement.getContainingFile() e.g. in injections: myElement declaration in one
@@ -459,5 +466,10 @@ public class VariableInplaceRenamer {
public Result calculateResult(ExpressionContext context) {
return new TextResult(myName);
}
@Override
public String getAdvertisingText() {
return myAdvertisementText;
}
}
}