Introduce Property: Implement separate in-place introducer for properties

This commit is contained in:
Alexey Sedunov
2015-03-02 17:22:51 +03:00
parent 5382504306
commit b78ed32a07
4 changed files with 373 additions and 121 deletions
@@ -181,7 +181,7 @@ public class SpecifyTypeExplicitlyAction extends PsiElementBaseIntentionAction {
@Override @Override
public void templateFinished(Template template, boolean brokenOff) { public void templateFinished(Template template, boolean brokenOff) {
JetTypeReference typeRef = declaration.getTypeReference(); JetTypeReference typeRef = declaration.getTypeReference();
if (typeRef != null) { if (typeRef != null && typeRef.isValid()) {
ShortenReferences.DEFAULT.process(typeRef); ShortenReferences.DEFAULT.process(typeRef);
} }
} }
@@ -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<ExtractionTarget>
): 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<JComponent>() {
override fun pass(t: JComponent) {
(t as JCheckBox).setSelected(myProperty.isVar())
}
}
addPanelControl(ControlWrapper(it, condition, initializer))
}
getCreateExplicitTypeCheckBox()?.let {
val initializer = object: Pass<JComponent>() {
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 }
}
}
@@ -29,7 +29,6 @@ import kotlin.test.*
import com.intellij.openapi.application.* import com.intellij.openapi.application.*
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.* import org.jetbrains.kotlin.idea.refactoring.introduce.introduceProperty.*
import java.util.* import java.util.*
import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.*
public class KotlinIntroducePropertyHandler( public class KotlinIntroducePropertyHandler(
val helper: ExtractionEngineHelper = KotlinIntroducePropertyHandler.InteractiveExtractionHelper val helper: ExtractionEngineHelper = KotlinIntroducePropertyHandler.InteractiveExtractionHelper
@@ -78,19 +77,15 @@ public class KotlinIntroducePropertyHandler(
doPostponedOperationsAndUnblockDocument(editor.getDocument()) doPostponedOperationsAndUnblockDocument(editor.getDocument())
} }
val introducer = KotlinInplaceVariableIntroducer( val introducer = KotlinInplacePropertyIntroducer(
property, property = property,
editor, editor = editor,
project, project = project,
INTRODUCE_PROPERTY, // title title = INTRODUCE_PROPERTY,
JetExpression.EMPTY_ARRAY, // occurrences doNotChangeVar = false,
null, // expr exprType = descriptor.controlFlow.outputValueBoxer.returnType,
false, // replaceOccurrence extractionResult = it,
property, availableTargets = propertyTargets.filter { it.isAvailable(descriptor) }
false, // isVar
true, // doNotChangeVar
descriptor.controlFlow.outputValueBoxer.returnType,
true // noTypeInference
) )
introducer.performInplaceRefactoring(LinkedHashSet(descriptor.suggestedNames)) introducer.performInplaceRefactoring(LinkedHashSet(descriptor.suggestedNames))
} }
@@ -27,12 +27,15 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.project.Project; import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Pass;
import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*; import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil;
import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer; import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer;
import com.intellij.ui.NonFocusableCheckBox; import com.intellij.ui.NonFocusableCheckBox;
import kotlin.Function0;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyAction; import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyAction;
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers; import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
@@ -47,19 +50,73 @@ import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.awt.event.ActionListener; import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
public class KotlinInplaceVariableIntroducer extends InplaceVariableIntroducer<JetExpression> { public class KotlinInplaceVariableIntroducer extends InplaceVariableIntroducer<JetExpression> {
private static final Function0<Boolean> TRUE = new Function0<Boolean>() {
@Override
public Boolean invoke() {
return true;
}
};
private static final Pass<JComponent> DO_NOTHING = new Pass<JComponent>() {
@Override
public void pass(JComponent component) {
}
};
protected static final class ControlWrapper {
@NotNull
private final Function0<JComponent> factory;
@NotNull
private final Function0<Boolean> condition;
@NotNull
private final Pass<JComponent> initializer;
private JComponent component;
public ControlWrapper(
@NotNull Function0<JComponent> factory,
@NotNull Function0<Boolean> condition,
@NotNull Pass<JComponent> initializer) {
this.factory = factory;
this.condition = condition;
this.initializer = initializer;
}
public ControlWrapper(@NotNull Function0<JComponent> 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 boolean myReplaceOccurrence;
private final JetProperty myProperty; protected JetProperty myProperty;
private final boolean isVar; private final boolean isVar;
private final boolean myDoNotChangeVar; private final boolean myDoNotChangeVar;
@Nullable private final JetType myExprType; @Nullable private final JetType myExprType;
private final boolean noTypeInference; private final boolean noTypeInference;
private JCheckBox myVarCheckbox; private final List<ControlWrapper> panelControls = new ArrayList<ControlWrapper>();
private JCheckBox myExprTypeCheckbox; private JPanel contentPanel;
public KotlinInplaceVariableIntroducer( public KotlinInplaceVariableIntroducer(
PsiNamedElement elementToRename, Editor editor, Project project, PsiNamedElement elementToRename, Editor editor, Project project,
@@ -77,119 +134,181 @@ public class KotlinInplaceVariableIntroducer extends InplaceVariableIntroducer<J
this.noTypeInference = noTypeInference; this.noTypeInference = noTypeInference;
} }
@Override @NotNull
@Nullable private JPanel getContentPanel() {
protected JComponent getComponent() { if (contentPanel == null) {
if (!myDoNotChangeVar) { contentPanel = new JPanel(new GridBagLayout());
myVarCheckbox = new NonFocusableCheckBox("Declare with var"); contentPanel.setBorder(null);
myVarCheckbox.setSelected(isVar);
myVarCheckbox.setMnemonic('v');
myVarCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
new WriteCommandAction(myProject, getCommandName(), getCommandName()) {
@Override
protected void run(Result result) throws Throwable {
PsiDocumentManager.getInstance(myProject).commitDocument(myEditor.getDocument());
JetPsiFactory psiFactory = new JetPsiFactory(myProject);
ASTNode node = myVarCheckbox.isSelected() ? psiFactory.createVarNode() : psiFactory.createValNode();
myProperty.getValOrVarNode().getPsi().replace(node.getPsi());
}
}.execute();
}
});
} }
if (myExprType != null && !noTypeInference) { return contentPanel;
myExprTypeCheckbox = new NonFocusableCheckBox("Specify type explicitly"); }
myExprTypeCheckbox.setSelected(false);
myExprTypeCheckbox.setMnemonic('t');
myExprTypeCheckbox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final Ref<Boolean> greedyToRight = new Ref<Boolean>();
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);
}
TemplateState templateState = protected final void addPanelControl(@NotNull ControlWrapper panelControl) {
TemplateManagerImpl.getTemplateState(InjectedLanguageUtil.getTopLevelEditor(myEditor)); panelControls.add(panelControl);
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());
}
}
}
}
}
}
});
if (myEditor.getUserData(INTRODUCE_RESTART) == Boolean.TRUE) { protected final void addPanelControl(@Nullable Function0<JComponent> initializer) {
myInitialName = myProperty.getName(); if (initializer != null) {
performInplaceRefactoring(getSuggestionsForNextRun()); 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; int count = 1;
if (myVarCheckbox != null) { for (ControlWrapper panelControl : panelControls) {
panel.add(myVarCheckbox, new GridBagConstraints(0, count, 1, 1, 1, 0, GridBagConstraints.NORTHWEST, if (!panelControl.isAvailable()) continue;
GridBagConstraints.HORIZONTAL, panelControl.initialize();
new Insets(5, 5, 5, 5), 0, 0)); panel.add(panelControl.getComponent(), new GridBagConstraints(0, count, 1, 1, 1, 0, GridBagConstraints.NORTHWEST,
++count; GridBagConstraints.HORIZONTAL,
} new Insets(0, 5, 0, 5), 0, 0));
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));
++count; ++count;
} }
panel.add(Box.createVerticalBox(), new GridBagConstraints(0, count, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, panel.add(Box.createVerticalBox(), new GridBagConstraints(0, count, 1, 1, 1, 1, GridBagConstraints.NORTHWEST,
GridBagConstraints.BOTH, GridBagConstraints.BOTH,
new Insets(0, 0, 0, 0), 0, 0)); 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<JComponent> getCreateExplicitTypeCheckBox() {
if (myExprType == null || noTypeInference) return null;
return new Function0<JComponent>() {
@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<JComponent> getCreateVarCheckBox() {
if (myDoNotChangeVar) return null;
return new Function0<JComponent>() {
@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<Boolean> greedyToRight = new Ref<Boolean>();
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<String> getSuggestionsForNextRun() { private LinkedHashSet<String> getSuggestionsForNextRun() {
@@ -206,14 +325,18 @@ public class KotlinInplaceVariableIntroducer extends InplaceVariableIntroducer<J
return nameSuggestions; return nameSuggestions;
} }
@Override protected void addTypeReferenceVariable(TemplateBuilderImpl builder) {
protected void addAdditionalVariables(TemplateBuilderImpl builder) {
JetTypeReference typeReference = myProperty.getTypeReference(); JetTypeReference typeReference = myProperty.getTypeReference();
if (typeReference != null) { if (typeReference != null) {
builder.replaceElement(typeReference, SpecifyTypeExplicitlyAction.createTypeExpressionForTemplate(myExprType)); builder.replaceElement(typeReference, SpecifyTypeExplicitlyAction.createTypeExpressionForTemplate(myExprType));
} }
} }
@Override
protected void addAdditionalVariables(TemplateBuilderImpl builder) {
addTypeReferenceVariable(builder);
}
@Override @Override
protected boolean buildTemplateAndStart( protected boolean buildTemplateAndStart(
Collection<PsiReference> refs, Collection<PsiReference> refs,