diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/JetParserDefinition.java b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/JetParserDefinition.java index c7c898e1180..823e6394eff 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/parsing/JetParserDefinition.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/parsing/JetParserDefinition.java @@ -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 diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetBlockExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetBlockExpression.java index cf8074088aa..c59d75bb54e 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetBlockExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetBlockExpression.java @@ -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); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetBreakExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetBreakExpression.java index cd2037985fb..8d05f373d85 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetBreakExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetBreakExpression.java @@ -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); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClassInitializer.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClassInitializer.java index a2a93ee1f48..d2c97e6270c 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClassInitializer.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClassInitializer.java @@ -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); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClassObject.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClassObject.java index 4147cc3dd20..7b7b6717c5b 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClassObject.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetClassObject.java @@ -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); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetContinueExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetContinueExpression.java index 7dec7aedd95..f6c124ba380 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetContinueExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetContinueExpression.java @@ -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); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetFunction.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetFunction.java index 67b0b7c6b85..063085d7d8e 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetFunction.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetFunction.java @@ -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); + } } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetLabelQualifiedInstanceExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetLabelQualifiedInstanceExpression.java index 297830432d7..6832da45771 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetLabelQualifiedInstanceExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetLabelQualifiedInstanceExpression.java @@ -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); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetLoopExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetLoopExpression.java index 1bf26d49171..9322326bd30 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetLoopExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetLoopExpression.java @@ -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); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetNamedDeclaration.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetNamedDeclaration.java index dc37a770c64..0179fab1e42 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetNamedDeclaration.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetNamedDeclaration.java @@ -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); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetProperty.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetProperty.java index ccacaa8a4d4..012492e54c9 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetProperty.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetProperty.java @@ -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(); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetReturnExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetReturnExpression.java index 396a0f22515..94177a29ea7 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetReturnExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetReturnExpression.java @@ -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); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSecondaryConstructor.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSecondaryConstructor.java index 6d20610771c..accea61b0a2 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSecondaryConstructor.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSecondaryConstructor.java @@ -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); } diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSimpleNameExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSimpleNameExpression.java index fc2d71a0772..133e8581b4c 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSimpleNameExpression.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetSimpleNameExpression.java @@ -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); diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetStatementExpression.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetStatementExpression.java new file mode 100644 index 00000000000..aaa44a9ebb0 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetStatementExpression.java @@ -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 { +} diff --git a/compiler/tests/org/jetbrains/jet/parsing/JetParsingTest.java b/compiler/tests/org/jetbrains/jet/parsing/JetParsingTest.java index 48433f12b24..85446a53257 100644 --- a/compiler/tests/org/jetbrains/jet/parsing/JetParsingTest.java +++ b/compiler/tests/org/jetbrains/jet/parsing/JetParsingTest.java @@ -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; diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index 476da74b585..e21de172e4b 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -57,6 +57,7 @@ + diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetIntroduceHandlerBase.java b/idea/src/org/jetbrains/jet/plugin/refactoring/JetIntroduceHandlerBase.java new file mode 100644 index 00000000000..ca3ffb8071b --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetIntroduceHandlerBase.java @@ -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 { +} diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetNameSuggester.java b/idea/src/org/jetbrains/jet/plugin/refactoring/JetNameSuggester.java new file mode 100644 index 00000000000..4b676dd0a49 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetNameSuggester.java @@ -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: + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.java b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.java new file mode 100644 index 00000000000..d004617799d --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.java @@ -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 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(bundle); + } + return bundle; + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties new file mode 100644 index 00000000000..3f07febfbee --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringBundle.properties @@ -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 \ No newline at end of file diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java new file mode 100644 index 00000000000..e1165b4c97b --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringSupportProvider.java @@ -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; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java new file mode 100644 index 00000000000..a6fdc87cd69 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/JetRefactoringUtil.java @@ -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 expressions = new ArrayList(); + 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 toExtract = new ArrayList(); + 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; + } + } + +} diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetInplaceVariableIntroducer.java b/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetInplaceVariableIntroducer.java new file mode 100644 index 00000000000..c85824bf43f --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetInplaceVariableIntroducer.java @@ -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 { + + 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()); + } + } + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetIntroduceVariableHandler.java b/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetIntroduceVariableHandler.java new file mode 100644 index 00000000000..48f6d22fe02 --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetIntroduceVariableHandler.java @@ -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 allOccurrences = findOccurrences(occurrenceContainer, expression); + Pass callback = new Pass() { + @Override + public void pass(OccurrencesChooser.ReplaceChoice replaceChoice) { + boolean replaceOccurrence = container != expression.getParent(); + final List 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 suggestedNamesSet = new LinkedHashSet(); + Collections.addAll(suggestedNamesSet, suggestedNames); + PsiElement commonParent = PsiTreeUtil.findCommonParent(allReplaces); + PsiElement commonContainer = getContainer(commonParent); + final Ref propertyRef = new Ref(); + final ArrayList references = new ArrayList(); + final Ref reference = new Ref(); + 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.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 allReplaces, final PsiElement commonContainer, + final PsiElement commonParent, final boolean replaceOccurrence, + final Ref propertyRef, + final ArrayList references, + final Ref 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 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 result = new ArrayList(); + + 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 + } +} diff --git a/idea/testData/refactoring/introduceVariable/ClassBody.kt b/idea/testData/refactoring/introduceVariable/ClassBody.kt new file mode 100644 index 00000000000..a5bd81ba677 --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/ClassBody.kt @@ -0,0 +1,9 @@ +open class A { + val x = 1 +} +/* +open class A { + val i = 1 + val x = i +} +*/ \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/DoWhileAddBlock.kt b/idea/testData/refactoring/introduceVariable/DoWhileAddBlock.kt new file mode 100644 index 00000000000..6a84f39c52a --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/DoWhileAddBlock.kt @@ -0,0 +1,14 @@ +open class A { + { + do 1 while (true) + } +} +/* +open class A { + { + do { + val i = 1 + } while (true) + } +} +*/ \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/FileLevel.kt b/idea/testData/refactoring/introduceVariable/FileLevel.kt new file mode 100644 index 00000000000..9663d7878ad --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/FileLevel.kt @@ -0,0 +1,5 @@ +fun x(x: Int = 1) {} +/* +val i = 1 +fun x(x: Int = i) {} +*/ \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/FunctionAddBlock.kt b/idea/testData/refactoring/introduceVariable/FunctionAddBlock.kt new file mode 100644 index 00000000000..8403106987d --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/FunctionAddBlock.kt @@ -0,0 +1,6 @@ +fun x(): Int = 1 +/* +fun x(): Int { + val i = 1 +} +*/ \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/IfCondition.kt b/idea/testData/refactoring/introduceVariable/IfCondition.kt new file mode 100644 index 00000000000..f9eeed5f999 --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/IfCondition.kt @@ -0,0 +1,11 @@ +fun a() { + if (true) 2 + else 1 +} +/* +fun a() { + val b = true + if (b) 2 + else 1 +} +*/ \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/IfElseAddBlock.kt b/idea/testData/refactoring/introduceVariable/IfElseAddBlock.kt new file mode 100644 index 00000000000..1c390070055 --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/IfElseAddBlock.kt @@ -0,0 +1,12 @@ +fun a() { + if (true) 2 + else 1 +} +/* +fun a() { + if (true) 2 + else { + val i = 1 + } +} +*/ \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/IfThenAddBlock.kt b/idea/testData/refactoring/introduceVariable/IfThenAddBlock.kt new file mode 100644 index 00000000000..61e5cece18f --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/IfThenAddBlock.kt @@ -0,0 +1,11 @@ +fun a() { + if (true) 1 + else 2 +} +/* +fun a() { + if (true) { + val i = 1 + } else 2 +} +*/ \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/Simple.kt b/idea/testData/refactoring/introduceVariable/Simple.kt new file mode 100644 index 00000000000..23c4d75fe91 --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/Simple.kt @@ -0,0 +1,8 @@ +fun a() { + 1 +} +/* +fun a() { + val i = 1 +} +*/ \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/WhenAddBlock.kt b/idea/testData/refactoring/introduceVariable/WhenAddBlock.kt new file mode 100644 index 00000000000..a79b019167c --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/WhenAddBlock.kt @@ -0,0 +1,14 @@ +fun a() { + when (1) { + is 1 -> 1 + } +} +/* +fun a() { + when (1) { + is 1 -> { + val i = 1 + } + } +} +*/ \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/WhileAddBlock.kt b/idea/testData/refactoring/introduceVariable/WhileAddBlock.kt new file mode 100644 index 00000000000..49b99c3bfd1 --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/WhileAddBlock.kt @@ -0,0 +1,14 @@ +open class A { + { + while (true) 1 + } +} +/* +open class A { + { + while (true) { + val i = 1 + } + } +} +*/ \ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetIntroduceVariableTest.java b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetIntroduceVariableTest.java new file mode 100644 index 00000000000..168e4b221a7 --- /dev/null +++ b/idea/tests/org/jetbrains/jet/plugin/refactoring/introduceVariable/JetIntroduceVariableTest.java @@ -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()); + } +}