Introduce Variable feature (in pre-alpha quality).

Problems:
1. Still problems with containers, due to new for me JetContainerNode.
2. Needs name suggester
3. Needs name validator
4. Needs type annotation adding
5. Needs changing to var and checking write access of usages
6. Needs final modifier adding
7. Some problems on class and file level. Should be fixed. Problems with inplace rename on this positions. Additionally possibly should be added proper work with expressions on class body level and namespace/file level. It parsed as error now.
8. Needs much more tests than just 6.
This commit is contained in:
Alefas
2012-02-01 19:42:06 +04:00
parent 55fdeb9e35
commit c8b4e5ec74
36 changed files with 881 additions and 13 deletions
@@ -25,8 +25,10 @@ import org.jetbrains.jet.lexer.JetTokens;
public class JetParserDefinition implements ParserDefinition {
public JetParserDefinition() {
if (!ApplicationManager.getApplication().isCommandLine()) {
}
//todo: ApplicationManager.getApplication() is null during JetParsingTest setting up
/*if (!ApplicationManager.getApplication().isCommandLine()) {
}*/
}
@NotNull
@@ -9,7 +9,7 @@ import java.util.List;
/**
* @author max
*/
public class JetBlockExpression extends JetExpression {
public class JetBlockExpression extends JetExpression implements JetStatementExpression {
public JetBlockExpression(@NotNull ASTNode node) {
super(node);
}
@@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull;
/**
* @author max
*/
public class JetBreakExpression extends JetLabelQualifiedExpression {
public class JetBreakExpression extends JetLabelQualifiedExpression implements JetStatementExpression {
public JetBreakExpression(@NotNull ASTNode node) {
super(node);
}
@@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull;
/**
* @author max
*/
public class JetClassInitializer extends JetDeclaration {
public class JetClassInitializer extends JetDeclaration implements JetStatementExpression {
public JetClassInitializer(@NotNull ASTNode node) {
super(node);
}
@@ -8,7 +8,7 @@ import org.jetbrains.jet.JetNodeTypes;
/**
* @author max
*/
public class JetClassObject extends JetDeclaration {
public class JetClassObject extends JetDeclaration implements JetStatementExpression {
public JetClassObject(@NotNull ASTNode node) {
super(node);
}
@@ -6,7 +6,7 @@ import org.jetbrains.annotations.NotNull;
/**
* @author max
*/
public class JetContinueExpression extends JetLabelQualifiedExpression {
public class JetContinueExpression extends JetLabelQualifiedExpression implements JetStatementExpression {
public JetContinueExpression(@NotNull ASTNode node) {
super(node);
}
@@ -81,4 +81,9 @@ abstract public class JetFunction extends JetTypeParameterListOwner
public JetElement asElement() {
return this;
}
public boolean isLocal() {
PsiElement parent = getParent();
return !(parent instanceof JetFile || parent instanceof JetClassBody || parent instanceof JetNamespaceBody);
}
}
@@ -7,7 +7,8 @@ import org.jetbrains.jet.JetNodeTypes;
/**
* @author abreslav
*/
public abstract class JetLabelQualifiedInstanceExpression extends JetLabelQualifiedExpression {
public abstract class JetLabelQualifiedInstanceExpression extends JetLabelQualifiedExpression
implements JetStatementExpression {
public JetLabelQualifiedInstanceExpression(@NotNull ASTNode node) {
super(node);
@@ -8,7 +8,7 @@ import org.jetbrains.jet.JetNodeTypes;
/**
* @author abreslav
*/
public abstract class JetLoopExpression extends JetExpression {
public abstract class JetLoopExpression extends JetExpression implements JetStatementExpression {
public JetLoopExpression(@NotNull ASTNode node) {
super(node);
}
@@ -11,7 +11,7 @@ import org.jetbrains.jet.lexer.JetTokens;
/**
* @author max
*/
public abstract class JetNamedDeclaration extends JetDeclaration implements PsiNameIdentifierOwner {
public abstract class JetNamedDeclaration extends JetDeclaration implements PsiNameIdentifierOwner, JetStatementExpression {
public JetNamedDeclaration(@NotNull ASTNode node) {
super(node);
}
@@ -2,6 +2,8 @@ package org.jetbrains.jet.lang.psi;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiElement;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import com.intellij.psi.util.PsiTreeUtil;
@@ -39,6 +41,21 @@ public class JetProperty extends JetTypeParameterListOwner implements JetModifie
return getNode().findChildByType(JetTokens.VAR_KEYWORD) != null;
}
public boolean isLocal() {
PsiElement parent = getParent();
return !(parent instanceof JetFile || parent instanceof JetClassBody || parent instanceof JetNamespaceBody);
}
@NotNull
@Override
public SearchScope getUseScope() {
if (isLocal()) {
PsiElement block = PsiTreeUtil.getParentOfType(this, JetBlockExpression.class, JetClassInitializer.class);
if (block == null) return super.getUseScope();
else return new LocalSearchScope(block);
} else return super.getUseScope();
}
@Nullable
public JetTypeReference getReceiverTypeRef() {
ASTNode node = getNode().getFirstChildNode();
@@ -7,7 +7,7 @@ import org.jetbrains.annotations.Nullable;
/**
* @author max
*/
public class JetReturnExpression extends JetLabelQualifiedExpression {
public class JetReturnExpression extends JetLabelQualifiedExpression implements JetStatementExpression {
public JetReturnExpression(@NotNull ASTNode node) {
super(node);
}
@@ -12,7 +12,7 @@ import java.util.List;
/**
* @author max
*/
public class JetSecondaryConstructor extends JetDeclaration implements JetDeclarationWithBody {
public class JetSecondaryConstructor extends JetDeclaration implements JetDeclarationWithBody, JetStatementExpression {
public JetSecondaryConstructor(@NotNull ASTNode node) {
super(node);
}
@@ -83,6 +83,14 @@ public class JetSimpleNameExpression extends JetReferenceExpression {
return ReferenceProvidersRegistry.getReferencesFromProviders(this, PsiReferenceService.Hints.NO_HINTS);
}
@Nullable
@Override
public PsiReference getReference() {
PsiReference[] references = getReferences();
if (references.length == 1) return references[0];
else return null;
}
@Override
public void accept(@NotNull JetVisitorVoid visitor) {
visitor.visitSimpleNameExpression(this);
@@ -0,0 +1,15 @@
package org.jetbrains.jet.lang.psi;
/**
* User: Alefas
* Date: 31.01.12
*/
/**
* This is an interface to show that {@link JetExpression} is not
* actually an expression (in meaning that this expression can be placed after "val x = ").
* This is possibly redundant interface, all inheritors of this interface should be refactored that they are not
* {@link JetExpression}, after such refactoring, this interface can be removed.
*/
public interface JetStatementExpression {
}
@@ -70,7 +70,8 @@ public class JetParsingTest extends ParsingTestCase {
Method[] methods = elem.getClass().getDeclaredMethods();
for (Method method : methods) {
String methodName = method.getName();
if (!methodName.startsWith("get") && !methodName.startsWith("find") || methodName.equals("getReference") || methodName.equals("getReferences")) continue;
if (!methodName.startsWith("get") && !methodName.startsWith("find") || methodName.equals("getReference") ||
methodName.equals("getReferences") || methodName.equals("getUseScope")) continue;
if (method.getParameterTypes().length > 0) continue;
Class<?> declaringClass = method.getDeclaringClass();
if (!declaringClass.getName().startsWith("org.jetbrains.jet")) continue;
+1
View File
@@ -57,6 +57,7 @@
<lang.foldingBuilder language="jet" implementationClass="org.jetbrains.jet.plugin.JetFoldingBuilder"/>
<lang.formatter language="jet" implementationClass="org.jetbrains.jet.plugin.formatter.JetFormattingModelBuilder"/>
<lang.findUsagesProvider language="jet" implementationClass="org.jetbrains.jet.plugin.findUsages.JetFindUsagesProvider"/>
<lang.refactoringSupport language="jet" implementationClass="org.jetbrains.jet.plugin.refactoring.JetRefactoringSupportProvider"/>
<codeInsight.parameterInfo language="jet" implementationClass="org.jetbrains.jet.plugin.parameterInfo.JetFunctionParameterInfoHandler"/>
@@ -0,0 +1,10 @@
package org.jetbrains.jet.plugin.refactoring;
import com.intellij.refactoring.RefactoringActionHandler;
/**
* User: Alefas
* Date: 25.01.12
*/
public abstract class JetIntroduceHandlerBase implements RefactoringActionHandler {
}
@@ -0,0 +1,29 @@
package org.jetbrains.jet.plugin.refactoring;
import org.jetbrains.jet.lang.psi.JetExpression;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade;
import org.jetbrains.jet.lang.types.JetStandardLibrary;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.checker.JetTypeChecker;
/**
* User: Alefas
* Date: 31.01.12
*/
public class JetNameSuggester {
public static String[] suggestNames(JetExpression expression) {
String[] def = {"i"};
BindingContext bindingContext = AnalyzerFacade.analyzeFileWithCache((JetFile) expression.getContainingFile(),
AnalyzerFacade.SINGLE_DECLARATION_PROVIDER);
JetType jetType = bindingContext.get(BindingContext.EXPRESSION_TYPE, expression);
if (jetType == null) return def;
JetType booleanType = JetStandardLibrary.getJetStandardLibrary(expression.getProject()).getBooleanType();
if (JetTypeChecker.INSTANCE.equalTypes(jetType, booleanType)) {
return new String[] {"b"};
}
return new String[] {"i"}; //todo:
}
}
@@ -0,0 +1,37 @@
package org.jetbrains.jet.plugin.refactoring;
import com.intellij.CommonBundle;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.PropertyKey;
import java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.ResourceBundle;
/**
* User: Alefas
* Date: 25.01.12
*/
public class JetRefactoringBundle {
private static Reference<ResourceBundle> ourBundle;
@NonNls
private static final String BUNDLE = "org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle";
private JetRefactoringBundle() {
}
public static String message(@NonNls @PropertyKey(resourceBundle = BUNDLE)String key, Object... params) {
return CommonBundle.message(getBundle(), key, params);
}
private static ResourceBundle getBundle() {
ResourceBundle bundle = null;
if (ourBundle != null) bundle = ourBundle.get();
if (bundle == null) {
bundle = ResourceBundle.getBundle(BUNDLE);
ourBundle = new SoftReference<ResourceBundle>(bundle);
}
return bundle;
}
}
@@ -0,0 +1,5 @@
cannot.refactor.not.expression=Cannot refactor not expression.
expressions.title=Expressions
introduce.variable=Introduce Variable
cannot.refactor.no.container=Cannot refactor without refactoring container.
cannot.refactor.no.expression=Cannot perform refactoring without an expression
@@ -0,0 +1,31 @@
package org.jetbrains.jet.plugin.refactoring;
import com.intellij.lang.refactoring.RefactoringSupportProvider;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringActionHandler;
import org.jetbrains.jet.lang.psi.JetFunction;
import org.jetbrains.jet.lang.psi.JetProperty;
import org.jetbrains.jet.plugin.refactoring.introduceVariable.JetIntroduceVariableHandler;
/**
* User: Alefas
* Date: 25.01.12
*/
public class JetRefactoringSupportProvider extends RefactoringSupportProvider {
@Override
public RefactoringActionHandler getIntroduceVariableHandler() {
return new JetIntroduceVariableHandler();
}
@Override
public boolean isInplaceRenameAvailable(PsiElement element, PsiElement context) {
if (element instanceof JetProperty) {
JetProperty property = (JetProperty) element;
if (property.isLocal()) return true;
} else if (element instanceof JetFunction) {
JetFunction function = (JetFunction) element;
if (function.isLocal()) return true;
}
return false;
}
}
@@ -0,0 +1,157 @@
package org.jetbrains.jet.plugin.refactoring;
import com.intellij.codeInsight.unwrap.ScopeHighlighter;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.ui.popup.JBPopupAdapter;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.LightweightWindowEvent;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.components.JBList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.*;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.util.ArrayList;
/**
* User: Alefas
* Date: 25.01.12
*/
public class JetRefactoringUtil {
private JetRefactoringUtil() {
}
public interface SelectExpressionCallback {
void run(@Nullable JetExpression expression);
}
public static void selectExpression(@NotNull Editor editor,
@NotNull PsiFile file,
@NotNull SelectExpressionCallback callback) throws IntroduceRefactoringException {
if (editor.getSelectionModel().hasSelection()) {
int selectionStart = editor.getSelectionModel().getSelectionStart();
int selectionEnd = editor.getSelectionModel().getSelectionEnd();
String text = file.getText();
while (selectionStart < selectionEnd && Character.isSpaceChar(text.charAt(selectionStart))) ++selectionStart;
while (selectionStart < selectionEnd && Character.isSpaceChar(text.charAt(selectionEnd - 1))) --selectionEnd;
callback.run(findExpression(editor, file, selectionStart, selectionEnd));
} else {
int offset = editor.getCaretModel().getOffset();
smartSelectExpression(editor, file, offset, callback);
}
}
private static void smartSelectExpression(@NotNull Editor editor, @NotNull PsiFile file, int offset,
@NotNull final SelectExpressionCallback callback)
throws IntroduceRefactoringException {
if (offset < 0) throw new IntroduceRefactoringException(JetRefactoringBundle.message("cannot.refactor.not.expression"));
PsiElement element = file.findElementAt(offset);
if (element == null) throw new IntroduceRefactoringException(JetRefactoringBundle.message("cannot.refactor.not.expression"));
if (element instanceof PsiWhiteSpace) {
smartSelectExpression(editor, file, offset - 1, callback);
return;
}
ArrayList<JetExpression> expressions = new ArrayList<JetExpression>();
while (element != null && !(element instanceof JetBlockExpression) && !(element instanceof JetNamedFunction)
&& !(element instanceof JetClassBody) && !(element instanceof JetSecondaryConstructor)) {
if (element instanceof JetExpression && !(element instanceof JetStatementExpression)) {
expressions.add((JetExpression) element);
}
element = element.getParent();
}
if (expressions.size() == 0) throw new IntroduceRefactoringException(JetRefactoringBundle.message("cannot.refactor.not.expression"));
final DefaultListModel model = new DefaultListModel();
for (JetExpression expression : expressions) {
model.addElement(expression);
}
final ScopeHighlighter highlighter = new ScopeHighlighter(editor);
final JList list = new JBList(model);
list.setCellRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Component rendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
StringBuilder buffer = new StringBuilder();
JetExpression element = (JetExpression) value;
if (element.isValid()) {
setText(getExpressionShortText(element));
}
return rendererComponent;
}
});
list.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
highlighter.dropHighlight();
int selectedIndex = list.getSelectedIndex();
if (selectedIndex < 0) return;
JetExpression expression = (JetExpression) model.get(selectedIndex);
ArrayList<PsiElement> toExtract = new ArrayList<PsiElement>();
toExtract.add(expression);
highlighter.highlight(expression, toExtract);
}
});
JBPopupFactory.getInstance().createListPopupBuilder(list).
setTitle(JetRefactoringBundle.message("expressions.title")).setMovable(false).setResizable(false).
setRequestFocus(true).setItemChoosenCallback(new Runnable() {
@Override
public void run() {
callback.run((JetExpression) list.getSelectedValue());
}
}).addListener(new JBPopupAdapter() {
@Override
public void onClosed(LightweightWindowEvent event) {
highlighter.dropHighlight();
}
}).createPopup().showInBestPositionFor(editor);
}
public static String getExpressionShortText(@NotNull JetExpression expression) { //todo: write appropriate implementation
String expressionText = expression.getText();
if (expressionText.length() > 20) {
expressionText = expressionText.substring(0, 17) + "...";
}
return expressionText;
}
@Nullable
private static JetExpression findExpression(@NotNull Editor editor, @NotNull PsiFile file,
int startOffset, int endOffset) throws IntroduceRefactoringException{
PsiElement element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, endOffset, JetExpression.class);
if (element == null || element.getTextRange().getStartOffset() != startOffset ||
element.getTextRange().getEndOffset() != endOffset) {
//todo: if it's infix expression => add (), then commit document then return new created expression
throw new IntroduceRefactoringException(JetRefactoringBundle.message("cannot.refactor.not.expression"));
} else if (!(element instanceof JetExpression)) {
throw new IntroduceRefactoringException(JetRefactoringBundle.message("cannot.refactor.not.expression"));
}
return (JetExpression) element;
}
public static class IntroduceRefactoringException extends Exception {
private String myMessage;
public IntroduceRefactoringException(String message) {
myMessage = message;
}
public String getMessage() {
return myMessage;
}
}
}
@@ -0,0 +1,46 @@
package org.jetbrains.jet.plugin.refactoring.introduceVariable;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiNamedElement;
import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.JetExpression;
import org.jetbrains.jet.lang.psi.JetProperty;
/**
* User: Alefas
* Date: 01.02.12
*/
public class JetInplaceVariableIntroducer extends InplaceVariableIntroducer<JetExpression> {
private boolean myReplaceOccurrence;
private JetProperty myProperty;
public JetInplaceVariableIntroducer(PsiNamedElement elementToRename, Editor editor, Project project,
String title, JetExpression[] occurrences,
@Nullable JetExpression expr, boolean replaceOccurrence,
JetProperty property) {
super(elementToRename, editor, project, title, occurrences, expr);
this.myReplaceOccurrence = replaceOccurrence;
myProperty = property;
}
@Override
protected void moveOffsetAfter(boolean success) {
if (!myReplaceOccurrence || myExprMarker == null) {
myEditor.getCaretModel().moveToOffset(myProperty.getTextRange().getEndOffset());
} else {
int startOffset = myExprMarker.getStartOffset();
PsiFile file = myProperty.getContainingFile();
PsiElement elementAt = file.findElementAt(startOffset);
if (elementAt != null) {
myEditor.getCaretModel().moveToOffset(elementAt.getTextRange().getEndOffset());
} else {
myEditor.getCaretModel().moveToOffset(myExprMarker.getEndOffset());
}
}
}
}
@@ -0,0 +1,313 @@
package org.jetbrains.jet.plugin.refactoring.introduceVariable;
import com.intellij.codeInsight.PsiEquivalenceUtil;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pass;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.introduce.inplace.OccurrencesChooser;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.plugin.refactoring.JetIntroduceHandlerBase;
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester;
import org.jetbrains.jet.plugin.refactoring.JetRefactoringBundle;
import org.jetbrains.jet.plugin.refactoring.JetRefactoringUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
/**
* User: Alefas
* Date: 25.01.12
*/
public class JetIntroduceVariableHandler extends JetIntroduceHandlerBase {
private static final String INTRODUCE_VARIABLE = JetRefactoringBundle.message("introduce.variable");
@Override
public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, DataContext dataContext) {
JetRefactoringUtil.SelectExpressionCallback callback = new JetRefactoringUtil.SelectExpressionCallback() {
@Override
public void run(@Nullable JetExpression expression) {
doRefactoring(project, editor, expression);
}
};
try {
JetRefactoringUtil.selectExpression(editor, file, callback);
} catch (JetRefactoringUtil.IntroduceRefactoringException e) {
showErrorHint(project, editor, e.getMessage());
}
}
private static void doRefactoring(@NotNull final Project project, final Editor editor, @Nullable JetExpression _expression) {
if (_expression == null) {
showErrorHint(project, editor, JetRefactoringBundle.message("cannot.refactor.no.expression"));
return;
}
if (_expression.getParent() instanceof JetParenthesizedExpression) {
_expression = (JetExpression) _expression.getParent();
}
final JetExpression expression = _expression;
final PsiElement container = getContainer(expression);
final PsiElement occurrenceContainer = getOccurrenceContainer(expression);
if (container == null) {
showErrorHint(project, editor, JetRefactoringBundle.message("cannot.refactor.no.container"));
return;
}
final boolean isInplaceAvailableOnDataContext =
editor.getSettings().isVariableInplaceRenameEnabled() &&
!ApplicationManager.getApplication().isUnitTestMode();
final ArrayList<JetExpression> allOccurrences = findOccurrences(occurrenceContainer, expression);
Pass<OccurrencesChooser.ReplaceChoice> callback = new Pass<OccurrencesChooser.ReplaceChoice>() {
@Override
public void pass(OccurrencesChooser.ReplaceChoice replaceChoice) {
boolean replaceOccurrence = container != expression.getParent();
final List<JetExpression> allReplaces;
if (OccurrencesChooser.ReplaceChoice.ALL == replaceChoice) {
if (allOccurrences.size() > 1) replaceOccurrence = true;
allReplaces = allOccurrences;
} else {
allReplaces = Collections.singletonList(expression);
}
String[] suggestedNames = JetNameSuggester.suggestNames(expression); //todo: add name validator
final LinkedHashSet<String> suggestedNamesSet = new LinkedHashSet<String>();
Collections.addAll(suggestedNamesSet, suggestedNames);
PsiElement commonParent = PsiTreeUtil.findCommonParent(allReplaces);
PsiElement commonContainer = getContainer(commonParent);
final Ref<JetProperty> propertyRef = new Ref<JetProperty>();
final ArrayList<JetExpression> references = new ArrayList<JetExpression>();
final Ref<JetExpression> reference = new Ref<JetExpression>();
final Runnable introduceRunnable = introduceVariable(project, expression, suggestedNames, allReplaces, commonContainer,
commonParent, replaceOccurrence, propertyRef, references,
reference);
final boolean finalReplaceOccurrence = replaceOccurrence;
CommandProcessor.getInstance().executeCommand(project, new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(introduceRunnable);
JetProperty property = propertyRef.get();
if (property != null) {
editor.getCaretModel().moveToOffset(property.getTextOffset());
editor.getSelectionModel().removeSelection();
if (isInplaceAvailableOnDataContext) {
JetInplaceVariableIntroducer variableIntroducer =
new JetInplaceVariableIntroducer(property, editor, project, INTRODUCE_VARIABLE,
references.toArray(new JetExpression[references.size()]),
reference.get(), finalReplaceOccurrence,
property);
PsiDocumentManager.getInstance(project).
doPostponedOperationsAndUnblockDocument(editor.getDocument());
variableIntroducer.performInplaceRefactoring(suggestedNamesSet);
}
}
}
}, INTRODUCE_VARIABLE, null);
}
};
if (isInplaceAvailableOnDataContext) {
OccurrencesChooser.<JetExpression>simpleChooser(editor).
showChooser(expression, allOccurrences, callback);
} else {
callback.pass(OccurrencesChooser.ReplaceChoice.ALL);
}
}
private static Runnable introduceVariable(final @NotNull Project project, final JetExpression expression,
final String[] suggestedNames,
final List<JetExpression> allReplaces, final PsiElement commonContainer,
final PsiElement commonParent, final boolean replaceOccurrence,
final Ref<JetProperty> propertyRef,
final ArrayList<JetExpression> references,
final Ref<JetExpression> reference) {
return new Runnable() {
@Override
public void run() {
String variableText = "val " + suggestedNames[0] + " = ";
if (expression instanceof JetParenthesizedExpression) {
JetParenthesizedExpression parenthesizedExpression = (JetParenthesizedExpression) expression;
JetExpression innerExpression = parenthesizedExpression.getExpression();
if (innerExpression != null) variableText += innerExpression.getText();
else variableText += expression.getText();
} else variableText += expression.getText();
JetProperty property = JetPsiFactory.createProperty(project, variableText);
if (property == null) return;
PsiElement anchor = commonParent;
if (anchor != commonContainer) {
while (anchor.getParent() != commonContainer) {
anchor = anchor.getParent();
}
} else {
anchor = commonContainer.getFirstChild();
int startOffset = commonContainer.getTextRange().getEndOffset();
for (JetExpression expr : allReplaces) {
int offset = expr.getTextRange().getStartOffset();
if (offset < startOffset) startOffset = offset;
}
while (anchor != null && !anchor.getTextRange().contains(startOffset)) {
anchor = anchor.getNextSibling();
}
if (anchor == null) return;
}
boolean needBraces = !(commonContainer instanceof JetBlockExpression ||
commonContainer instanceof JetClassBody ||
commonContainer instanceof JetFile ||
commonContainer instanceof JetClassInitializer);
if (!needBraces) {
property = (JetProperty) commonContainer.addBefore(property, anchor);
commonContainer.addBefore(JetPsiFactory.createWhiteSpace(project, "\n"), anchor);
} else {
JetExpression emptyBody = JetPsiFactory.createEmptyBody(project);
PsiElement firstChild = emptyBody.getFirstChild();
emptyBody.addAfter(JetPsiFactory.createWhiteSpace(project, "\n"), firstChild);
property = (JetProperty) emptyBody.addAfter(property, firstChild);
emptyBody.addAfter(JetPsiFactory.createWhiteSpace(project, "\n"), firstChild);
anchor.replace(emptyBody);
}
for (JetExpression replace : allReplaces) {
if (replaceOccurrence) {
boolean isActualExpression = expression == replace;
JetExpression element = (JetExpression) replace.replace(JetPsiFactory.createExpression(project, suggestedNames[0]));
references.add(element);
if (isActualExpression) reference.set(element);
} else if (!needBraces) {
replace.delete();
}
}
propertyRef.set(property);
}
};
}
private static ArrayList<JetExpression> findOccurrences(PsiElement occurrenceContainer, @NotNull JetExpression expression) {
if (expression instanceof JetParenthesizedExpression) {
JetParenthesizedExpression parenthesizedExpression = (JetParenthesizedExpression) expression;
JetExpression innerExpression = parenthesizedExpression.getExpression();
if (innerExpression != null) {
expression = innerExpression;
}
}
final JetExpression actualExpression = expression;
final ArrayList<JetExpression> result = new ArrayList<JetExpression>();
JetVisitorVoid visitor = new JetVisitorVoid() {
@Override
public void visitJetElement(JetElement element) {
element.acceptChildren(this);
super.visitJetElement(element);
}
@Override
public void visitExpression(JetExpression expression) {
if (PsiEquivalenceUtil.areElementsEquivalent(expression, actualExpression)) {
PsiElement parent = expression.getParent();
if (parent instanceof JetParenthesizedExpression) {
result.add((JetParenthesizedExpression) parent);
} else {
result.add(expression);
}
} else {
super.visitExpression(expression);
}
}
};
occurrenceContainer.accept(visitor);
return result;
}
@Nullable
private static PsiElement getContainer(PsiElement place) {
if (place instanceof JetBlockExpression || place instanceof JetClassBody || place instanceof JetFile ||
place instanceof JetClassInitializer) {
return place;
}
while (place != null) {
PsiElement parent = place.getParent();
if (parent instanceof JetContainerNode) {
if (!(parent.getParent() instanceof JetIfExpression &&
((JetIfExpression) parent.getParent()).getCondition() == place)) {
return parent;
}
} if (parent instanceof JetBlockExpression || parent instanceof JetWhenEntry ||
parent instanceof JetClassBody || parent instanceof JetFile || parent instanceof JetClassInitializer) {
return parent;
} else if (parent instanceof JetNamedFunction) {
JetNamedFunction function = (JetNamedFunction) parent;
if (function.getBodyExpression() == place) {
return parent;
}
} else if (parent instanceof JetSecondaryConstructor) {
JetSecondaryConstructor secondaryConstructor = (JetSecondaryConstructor) parent;
if (secondaryConstructor.getBodyExpression() == place) {
return parent;
}
}
place = parent;
}
return null;
}
@Nullable
private static PsiElement getOccurrenceContainer(PsiElement place) {
PsiElement result = null;
while (place != null) {
PsiElement parent = place.getParent();
if (parent instanceof JetContainerNode) {
if (!(place instanceof JetBlockExpression) && !(parent.getParent() instanceof JetIfExpression &&
((JetIfExpression) parent.getParent()).getCondition() == place)) {
result = parent;
}
} else if (parent instanceof JetClassBody || parent instanceof JetFile || parent instanceof JetClassInitializer) {
if (result == null) return parent;
else return result;
} else if (parent instanceof JetBlockExpression) {
result = parent;
} else if (parent instanceof JetWhenEntry ) {
if (!(place instanceof JetBlockExpression)) {
result = parent;
}
} else if (parent instanceof JetNamedFunction) {
JetNamedFunction function = (JetNamedFunction) parent;
if (function.getBodyExpression() == place) {
if (!(place instanceof JetBlockExpression)) {
result = parent;
}
}
} else if (parent instanceof JetSecondaryConstructor) {
JetSecondaryConstructor secondaryConstructor = (JetSecondaryConstructor) parent;
if (secondaryConstructor.getBodyExpression() == place) {
if (!(place instanceof JetBlockExpression)) {
result = parent;
}
}
}
place = parent;
}
return null;
}
private static void showErrorHint(Project project, Editor editor, String message) {
if (ApplicationManager.getApplication().isUnitTestMode()) throw new RuntimeException(message);
CommonRefactoringUtil.showErrorHint(project, editor, message,
INTRODUCE_VARIABLE,
HelpID.INTRODUCE_VARIABLE);
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
//do nothing
}
}
@@ -0,0 +1,9 @@
open class A {
val x = <selection>1</selection>
}
/*
open class A {
val i = 1
val x = i
}
*/
@@ -0,0 +1,14 @@
open class A {
{
do <selection>1</selection> while (true)
}
}
/*
open class A {
{
do {
val i = 1
} while (true)
}
}
*/
@@ -0,0 +1,5 @@
fun x(x: Int = <selection>1</selection>) {}
/*
val i = 1
fun x(x: Int = i) {}
*/
@@ -0,0 +1,6 @@
fun x(): Int = <selection>1</selection>
/*
fun x(): Int {
val i = 1
}
*/
@@ -0,0 +1,11 @@
fun a() {
if (<selection>true</selection>) 2
else 1
}
/*
fun a() {
val b = true
if (b) 2
else 1
}
*/
@@ -0,0 +1,12 @@
fun a() {
if (true) 2
else <selection>1</selection>
}
/*
fun a() {
if (true) 2
else {
val i = 1
}
}
*/
@@ -0,0 +1,11 @@
fun a() {
if (true) <selection>1</selection>
else 2
}
/*
fun a() {
if (true) {
val i = 1
} else 2
}
*/
@@ -0,0 +1,8 @@
fun a() {
<selection>1</selection>
}
/*
fun a() {
val i = 1
}
*/
@@ -0,0 +1,14 @@
fun a() {
when (1) {
is 1 -> <selection>1</selection>
}
}
/*
fun a() {
when (1) {
is 1 -> {
val i = 1
}
}
}
*/
@@ -0,0 +1,14 @@
open class A {
{
while (true) <selection>1</selection>
}
}
/*
open class A {
{
while (true) {
val i = 1
}
}
}
*/
@@ -0,0 +1,86 @@
package org.jetbrains.jet.plugin.refactoring.introduceVariable;
import com.intellij.ide.DataManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.psi.PsiElement;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.PluginTestCaseBase;
/**
* User: Alefas
* Date: 31.01.12
*/
public class JetIntroduceVariableTest extends LightCodeInsightFixtureTestCase {
public void testClassBody() {
doTest();
}
public void testDoWhileAddBlock() {
doTest();
}
/*public void testFileLevel() {
doTest();
}
public void testFunctionAddBlock() {
doTest();
}*/
public void testIfCondition() {
doTest();
}
public void testIfElseAddBlock() {
doTest();
}
/*public void testIfThenAddBlock() {
doTest();
}*/
public void testSimple() {
doTest();
}
/*public void testWhenAddBlock() {
doTest();
}*/
public void testWhileAddBlock() {
doTest();
}
@Override
protected void setUp() throws Exception {
super.setUp();
myFixture.setTestDataPath(PluginTestCaseBase.getTestDataPathBase() + "/refactoring/introduceVariable");
}
private void doTest() {
myFixture.configureByFile(getTestName(false) + ".kt");
final JetFile file = (JetFile) myFixture.getFile();
PsiElement lastChild = file.getLastChild();
assert lastChild != null;
String expectedResultText = null;
if (lastChild.getNode().getElementType().equals(JetTokens.BLOCK_COMMENT)) {
String lastChildText = lastChild.getText();
expectedResultText = lastChildText.substring(2, lastChildText.length() - 2).trim();
} else if (lastChild.getNode().getElementType().equals(JetTokens.EOL_COMMENT)) {
expectedResultText = lastChild.getText().substring(2).trim();
}
assert expectedResultText != null;
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
new JetIntroduceVariableHandler().invoke(getProject(), myFixture.getEditor(), file,
DataManager.getInstance().getDataContext(myFixture.getEditor().
getComponent()));
}
});
int endOffset = file.getLastChild().getTextRange().getStartOffset();
assertEquals(expectedResultText, file.getText().substring(0, endOffset).trim());
}
}