diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyAction.java b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyAction.java index dfaa8a0931b..c1c91bf11f4 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyAction.java +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/SpecifyTypeExplicitlyAction.java @@ -181,7 +181,7 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction { @Override public void templateFinished(Template template, boolean brokenOff) { JetTypeReference typeRef = declaration.getTypeReference(); - if (typeRef != null) { + if (typeRef != null && typeRef.isValid()) { ShortenReferences.DEFAULT.process(typeRef); } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinInplacePropertyIntroducer.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinInplacePropertyIntroducer.kt new file mode 100644 index 00000000000..a409dffc34d --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinInplacePropertyIntroducer.kt @@ -0,0 +1,134 @@ +/* + * Copyright 2010-2015 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty + +import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.* +import org.jetbrains.kotlin.psi.* +import com.intellij.openapi.project.* +import com.intellij.openapi.editor.* +import org.jetbrains.kotlin.types.* +import javax.swing.* +import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.* +import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinInplaceVariableIntroducer.ControlWrapper +import com.intellij.openapi.util.* +import com.intellij.ui.* +import javax.swing.event.* +import com.intellij.openapi.application.* +import com.intellij.psi.* +import org.jetbrains.kotlin.psi.psiUtil.* +import com.intellij.codeInsight.template.* +import org.jetbrains.kotlin.idea.intentions.* + +public class KotlinInplacePropertyIntroducer( + property: JetProperty, + editor: Editor, + project: Project, + title: String, + doNotChangeVar: Boolean, + exprType: JetType?, + extractionResult: ExtractionResult, + private val availableTargets: List +): KotlinInplaceVariableIntroducer( + property, editor, project, title, JetExpression.EMPTY_ARRAY, null, false, property, false, doNotChangeVar, exprType, false +) { + { + assert(availableTargets.isNotEmpty(), "No targets available: ${JetPsiUtil.getElementTextWithContext(property)}") + } + + private var extractionResult = extractionResult + + private var currentTarget: ExtractionTarget = extractionResult.config.generatorOptions.target + set(value: ExtractionTarget) { + if (value == currentTarget) return + + $currentTarget = value + runWriteActionAndRestartRefactoring { + with (extractionResult.config) { + extractionResult = copy(generatorOptions = generatorOptions.copy(target = currentTarget)).generateDeclaration(myProperty) + myProperty = extractionResult.declaration as JetProperty + myElementToRename = myProperty + } + } + updatePanelControls() + } + + private fun isInitializer(): Boolean = currentTarget == ExtractionTarget.PROPERTY_WITH_INITIALIZER + + override fun initPanelControls() { + if (availableTargets.size() > 1) { + addPanelControl( + ControlWrapper { + val propertyKindComboBox = with(JComboBox(availableTargets.map { it.name.capitalize() }.copyToArray())) { + addPopupMenuListener( + object : PopupMenuListenerAdapter() { + override fun popupMenuWillBecomeInvisible(e: PopupMenuEvent?) { + ApplicationManager.getApplication().invokeLater { + currentTarget = availableTargets[getSelectedIndex()] + } + } + } + ) + + setSelectedIndex(availableTargets.indexOf(currentTarget)) + + this + } + + val propertyKindLabel = JLabel("Introduce as: ") + propertyKindLabel.setDisplayedMnemonic('I') + propertyKindLabel.setLabelFor(propertyKindComboBox) + + val panel = JPanel() + panel.add(propertyKindLabel) + panel.add(propertyKindComboBox) + + panel + } + ) + } + + if (ExtractionTarget.PROPERTY_WITH_INITIALIZER in availableTargets) { + val condition = { isInitializer() } + + getCreateVarCheckBox()?.let { + val initializer = object: Pass() { + override fun pass(t: JComponent) { + (t as JCheckBox).setSelected(myProperty.isVar()) + } + } + addPanelControl(ControlWrapper(it, condition, initializer)) + } + getCreateExplicitTypeCheckBox()?.let { + val initializer = object: Pass() { + override fun pass(t: JComponent) { + (t as JCheckBox).setSelected(myProperty.getTypeReference() != null) + } + } + addPanelControl(ControlWrapper(it, condition, initializer)) + } + } + } + + override fun addTypeReferenceVariable(builder: TemplateBuilderImpl) { + if (!isInitializer()) return + super.addTypeReferenceVariable(builder) + } + + override fun checkLocalScope(): PsiElement? { + return myElementToRename.parents().first { it is JetClassOrObject || it is JetFile } + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt index e31d8c6b013..776cfc7b654 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceProperty/KotlinIntroducePropertyHandler.kt @@ -29,7 +29,6 @@ import kotlin.test.* import com.intellij.openapi.application.* import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.* import java.util.* -import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.* public class KotlinIntroducePropertyHandler( val helper: ExtractionEngineHelper = KotlinIntroducePropertyHandler.InteractiveExtractionHelper @@ -78,19 +77,15 @@ public class KotlinIntroducePropertyHandler( doPostponedOperationsAndUnblockDocument(editor.getDocument()) } - val introducer = KotlinInplaceVariableIntroducer( - property, - editor, - project, - INTRODUCE_PROPERTY, // title - JetExpression.EMPTY_ARRAY, // occurrences - null, // expr - false, // replaceOccurrence - property, - false, // isVar - true, // doNotChangeVar - descriptor.controlFlow.outputValueBoxer.returnType, - true // noTypeInference + val introducer = KotlinInplacePropertyIntroducer( + property = property, + editor = editor, + project = project, + title = INTRODUCE_PROPERTY, + doNotChangeVar = false, + exprType = descriptor.controlFlow.outputValueBoxer.returnType, + extractionResult = it, + availableTargets = propertyTargets.filter { it.isAvailable(descriptor) } ) introducer.performInplaceRefactoring(LinkedHashSet(descriptor.suggestedNames)) } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinInplaceVariableIntroducer.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinInplaceVariableIntroducer.java index dbc57300b45..0c513b4cf92 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinInplaceVariableIntroducer.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinInplaceVariableIntroducer.java @@ -27,12 +27,15 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Pass; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.TextRange; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer; import com.intellij.ui.NonFocusableCheckBox; +import kotlin.Function0; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyAction; import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers; @@ -47,19 +50,73 @@ import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashSet; +import java.util.List; public class KotlinInplaceVariableIntroducer extends InplaceVariableIntroducer { + private static final Function0 TRUE = new Function0() { + @Override + public Boolean invoke() { + return true; + } + }; + + private static final Pass DO_NOTHING = new Pass() { + @Override + public void pass(JComponent component) { + + } + }; + + protected static final class ControlWrapper { + @NotNull + private final Function0 factory; + @NotNull + private final Function0 condition; + @NotNull + private final Pass initializer; + private JComponent component; + + public ControlWrapper( + @NotNull Function0 factory, + @NotNull Function0 condition, + @NotNull Pass initializer) { + this.factory = factory; + this.condition = condition; + this.initializer = initializer; + } + + public ControlWrapper(@NotNull Function0 factory) { + this(factory, TRUE, DO_NOTHING); + } + + public boolean isAvailable() { + return condition.invoke(); + } + + public void initialize() { + initializer.pass(getComponent()); + } + + @NotNull + public JComponent getComponent() { + if (component == null) { + component = factory.invoke(); + } + return component; + } + } private final boolean myReplaceOccurrence; - private final JetProperty myProperty; + protected JetProperty myProperty; private final boolean isVar; private final boolean myDoNotChangeVar; @Nullable private final JetType myExprType; private final boolean noTypeInference; - private JCheckBox myVarCheckbox; - private JCheckBox myExprTypeCheckbox; + private final List panelControls = new ArrayList(); + private JPanel contentPanel; public KotlinInplaceVariableIntroducer( PsiNamedElement elementToRename, Editor editor, Project project, @@ -77,119 +134,181 @@ public class KotlinInplaceVariableIntroducer extends InplaceVariableIntroducer greedyToRight = new Ref(); - new WriteCommandAction(myProject, getCommandName(), getCommandName()) { - @Override - protected void run(Result result) throws Throwable { - PsiDocumentManager.getInstance(myProject).commitDocument(myEditor.getDocument()); - if (myExprTypeCheckbox.isSelected()) { - ASTNode identifier = myProperty.getNode().findChildByType(JetTokens.IDENTIFIER); - if (identifier != null) { - TextRange range = identifier.getTextRange(); - RangeHighlighter[] highlighters = myEditor.getMarkupModel().getAllHighlighters(); - for (RangeHighlighter highlighter : highlighters) { - if (highlighter.getStartOffset() == range.getStartOffset()) { - if (highlighter.getEndOffset() == range.getEndOffset()) { - greedyToRight.set(highlighter.isGreedyToRight()); - highlighter.setGreedyToRight(false); - } - } - } - } - String renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(myExprType); - myProperty.setTypeReference(new JetPsiFactory(myProject).createType(renderedType)); - } - else { - myProperty.setTypeReference(null); - } + return contentPanel; + } - TemplateState templateState = - TemplateManagerImpl.getTemplateState(InjectedLanguageUtil.getTopLevelEditor(myEditor)); - if (templateState != null) { - myEditor.putUserData(INTRODUCE_RESTART, true); - templateState.cancelTemplate(); - } - } - }.execute(); - ApplicationManager.getApplication().runReadAction(new Runnable() { - @Override - public void run() { - if (myExprTypeCheckbox.isSelected()) { - ASTNode identifier = myProperty.getNode().findChildByType(JetTokens.IDENTIFIER); - if (identifier != null) { - TextRange range = identifier.getTextRange(); - RangeHighlighter[] highlighters = myEditor.getMarkupModel().getAllHighlighters(); - for (RangeHighlighter highlighter : highlighters) { - if (highlighter.getStartOffset() == range.getStartOffset()) { - if (highlighter.getEndOffset() == range.getEndOffset()) { - highlighter.setGreedyToRight(greedyToRight.get()); - } - } - } - } - } - } - }); + protected final void addPanelControl(@NotNull ControlWrapper panelControl) { + panelControls.add(panelControl); + } - if (myEditor.getUserData(INTRODUCE_RESTART) == Boolean.TRUE) { - myInitialName = myProperty.getName(); - performInplaceRefactoring(getSuggestionsForNextRun()); - } - } - }); + protected final void addPanelControl(@Nullable Function0 initializer) { + if (initializer != null) { + addPanelControl(new ControlWrapper(initializer)); } + } + + protected void initPanelControls() { + addPanelControl(getCreateVarCheckBox()); + addPanelControl(getCreateExplicitTypeCheckBox()); + } + + protected final void updatePanelControls() { + JPanel panel = getContentPanel(); + + panel.removeAll(); - JPanel panel = new JPanel(new GridBagLayout()); - panel.setBorder(null); int count = 1; - if (myVarCheckbox != null) { - panel.add(myVarCheckbox, new GridBagConstraints(0, count, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, - GridBagConstraints.HORIZONTAL, - new Insets(5, 5, 5, 5), 0, 0)); - ++count; - } - - if (myExprTypeCheckbox != null) { - panel.add(myExprTypeCheckbox, new GridBagConstraints(0, count, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, - GridBagConstraints.HORIZONTAL, - new Insets(5, 5, 5, 5), 0, 0)); + for (ControlWrapper panelControl : panelControls) { + if (!panelControl.isAvailable()) continue; + panelControl.initialize(); + panel.add(panelControl.getComponent(), new GridBagConstraints(0, count, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, + GridBagConstraints.HORIZONTAL, + new Insets(0, 5, 0, 5), 0, 0)); ++count; } panel.add(Box.createVerticalBox(), new GridBagConstraints(0, count, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0)); - return panel; + } + + @Override + @Nullable + protected final JComponent getComponent() { + panelControls.clear(); + initPanelControls(); + + updatePanelControls(); + + return getContentPanel(); + } + + @Nullable + protected final Function0 getCreateExplicitTypeCheckBox() { + if (myExprType == null || noTypeInference) return null; + + return new Function0() { + @Override + public JComponent invoke() { + final JCheckBox exprTypeCheckbox = new NonFocusableCheckBox("Specify type explicitly"); + exprTypeCheckbox.setSelected(false); + exprTypeCheckbox.setMnemonic('t'); + exprTypeCheckbox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(@NotNull ActionEvent e) { + runWriteActionAndRestartRefactoring( + new Runnable() { + @Override + public void run() { + if (exprTypeCheckbox.isSelected()) { + String renderedType = + IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(myExprType); + myProperty.setTypeReference(new JetPsiFactory(myProject).createType(renderedType)); + } + else { + myProperty.setTypeReference(null); + } + } + } + ); + } + }); + + return exprTypeCheckbox; + } + }; + } + + @Nullable + protected final Function0 getCreateVarCheckBox() { + if (myDoNotChangeVar) return null; + + return new Function0() { + @Override + public JComponent invoke() { + final JCheckBox varCheckbox = new NonFocusableCheckBox("Declare with var"); + varCheckbox.setSelected(isVar); + varCheckbox.setMnemonic('v'); + varCheckbox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(@NotNull ActionEvent e) { + new WriteCommandAction(myProject, getCommandName(), getCommandName()) { + @Override + protected void run(@NotNull Result result) throws Throwable { + PsiDocumentManager.getInstance(myProject).commitDocument(myEditor.getDocument()); + + JetPsiFactory psiFactory = new JetPsiFactory(myProject); + ASTNode node = varCheckbox.isSelected() ? psiFactory.createVarNode() : psiFactory.createValNode(); + myProperty.getValOrVarNode().getPsi().replace(node.getPsi()); + } + }.execute(); + } + }); + + return varCheckbox; + } + }; + } + + protected final void runWriteActionAndRestartRefactoring(final Runnable runnable) { + final Ref greedyToRight = new Ref(); + new WriteCommandAction(myProject, getCommandName(), getCommandName()) { + @Override + protected void run(@NotNull Result result) throws Throwable { + PsiDocumentManager.getInstance(myProject).commitDocument(myEditor.getDocument()); + + ASTNode identifier = myProperty.getNode().findChildByType(JetTokens.IDENTIFIER); + if (identifier != null) { + TextRange range = identifier.getTextRange(); + RangeHighlighter[] highlighters = myEditor.getMarkupModel().getAllHighlighters(); + for (RangeHighlighter highlighter : highlighters) { + if (highlighter.getStartOffset() == range.getStartOffset()) { + if (highlighter.getEndOffset() == range.getEndOffset()) { + greedyToRight.set(highlighter.isGreedyToRight()); + highlighter.setGreedyToRight(false); + } + } + } + } + + runnable.run(); + + TemplateState templateState = + TemplateManagerImpl.getTemplateState(InjectedLanguageUtil.getTopLevelEditor(myEditor)); + if (templateState != null) { + myEditor.putUserData(INTRODUCE_RESTART, true); + templateState.cancelTemplate(); + } + } + }.execute(); + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + ASTNode identifier = myProperty.getNode().findChildByType(JetTokens.IDENTIFIER); + if (identifier != null) { + TextRange range = identifier.getTextRange(); + RangeHighlighter[] highlighters = myEditor.getMarkupModel().getAllHighlighters(); + for (RangeHighlighter highlighter : highlighters) { + if (highlighter.getStartOffset() == range.getStartOffset()) { + if (highlighter.getEndOffset() == range.getEndOffset()) { + highlighter.setGreedyToRight(greedyToRight.get()); + } + } + } + } + } + }); + + if (myEditor.getUserData(INTRODUCE_RESTART) == Boolean.TRUE) { + myInitialName = myProperty.getName(); + performInplaceRefactoring(getSuggestionsForNextRun()); + } } private LinkedHashSet getSuggestionsForNextRun() { @@ -206,14 +325,18 @@ public class KotlinInplaceVariableIntroducer extends InplaceVariableIntroducer refs,