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
public void templateFinished(Template template, boolean brokenOff) {
JetTypeReference typeRef = declaration.getTypeReference();
if (typeRef != null) {
if (typeRef != null && typeRef.isValid()) {
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 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))
}
@@ -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<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 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<ControlWrapper> panelControls = new ArrayList<ControlWrapper>();
private JPanel contentPanel;
public KotlinInplaceVariableIntroducer(
PsiNamedElement elementToRename, Editor editor, Project project,
@@ -77,119 +134,181 @@ public class KotlinInplaceVariableIntroducer extends InplaceVariableIntroducer<J
this.noTypeInference = noTypeInference;
}
@Override
@Nullable
protected JComponent getComponent() {
if (!myDoNotChangeVar) {
myVarCheckbox = new NonFocusableCheckBox("Declare with var");
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();
}
});
@NotNull
private JPanel getContentPanel() {
if (contentPanel == null) {
contentPanel = new JPanel(new GridBagLayout());
contentPanel.setBorder(null);
}
if (myExprType != null && !noTypeInference) {
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);
}
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<JComponent> 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<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() {
@@ -206,14 +325,18 @@ public class KotlinInplaceVariableIntroducer extends InplaceVariableIntroducer<J
return nameSuggestions;
}
@Override
protected void addAdditionalVariables(TemplateBuilderImpl builder) {
protected void addTypeReferenceVariable(TemplateBuilderImpl builder) {
JetTypeReference typeReference = myProperty.getTypeReference();
if (typeReference != null) {
builder.replaceElement(typeReference, SpecifyTypeExplicitlyAction.createTypeExpressionForTemplate(myExprType));
}
}
@Override
protected void addAdditionalVariables(TemplateBuilderImpl builder) {
addTypeReferenceVariable(builder);
}
@Override
protected boolean buildTemplateAndStart(
Collection<PsiReference> refs,