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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+313
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
+86
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user