From dae7717a6970ab56ac79741d0ac0cfc0c76fe830 Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 1 Jun 2015 20:11:16 +0300 Subject: [PATCH] Introduce Variable: Reimplement in-place refactoring using AbstractInplaceIntroducer. Extract common superclass AbstractKotlinInplaceIntroducer --- .../AbstractKotlinInplaceIntroducer.kt | 90 ++++++++++ .../KotlinInplaceParameterIntroducer.kt | 52 +----- .../KotlinIntroduceVariableHandler.java | 25 +-- .../KotlinVariableInplaceIntroducer.kt | 157 ++++++++++++++++++ 4 files changed, 269 insertions(+), 55 deletions(-) create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractKotlinInplaceIntroducer.kt create mode 100644 idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinVariableInplaceIntroducer.kt diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractKotlinInplaceIntroducer.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractKotlinInplaceIntroducer.kt new file mode 100644 index 00000000000..b0f0bbf5f9a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/AbstractKotlinInplaceIntroducer.kt @@ -0,0 +1,90 @@ +/* + * 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 + +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.RangeMarker +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile +import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer +import com.intellij.refactoring.rename.inplace.InplaceRefactoring +import com.intellij.ui.NonFocusableCheckBox +import com.intellij.util.ui.FormBuilder +import org.jetbrains.kotlin.idea.JetFileType +import org.jetbrains.kotlin.idea.core.replaced +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.psi.JetExpression +import org.jetbrains.kotlin.psi.JetNamedDeclaration +import org.jetbrains.kotlin.psi.JetPsiFactory +import org.jetbrains.kotlin.psi.JetSimpleNameExpression +import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType +import java.awt.BorderLayout + +public abstract class AbstractKotlinInplaceIntroducer( + expression: JetExpression, + occurrences: Array, + title: String, + project: Project, + editor: Editor +): AbstractInplaceIntroducer(project, editor, expression, null, occurrences, title, JetFileType.INSTANCE) { + protected fun initFormComponents(init: FormBuilder.() -> Unit) { + myWholePanel.setLayout(BorderLayout()) + + with(FormBuilder.createFormBuilder()) { + init() + myWholePanel.add(getPanel(), BorderLayout.CENTER) + } + } + + protected fun runWriteCommandAndRestart(action: () -> Unit) { + myEditor.putUserData(InplaceRefactoring.INTRODUCE_RESTART, true) + try { + stopIntroduce(myEditor) + myProject.executeWriteCommand(getCommandName(), getCommandName(), action) + // myExprMarker was invalidated by stopIntroduce() + myExprMarker = createMarker(myExpr) + startInplaceIntroduceTemplate() + } + finally { + myEditor.putUserData(InplaceRefactoring.INTRODUCE_RESTART, false) + } + } + + override fun getActionName(): String? = null + + override fun restoreExpression( + containingFile: PsiFile, + declaration: D, + marker: RangeMarker, + exprText: String + ): JetExpression? { + if (!declaration.isValid()) return null + + return containingFile + .findElementAt(marker.getStartOffset()) + ?.getNonStrictParentOfType() + ?.replaced(JetPsiFactory(myProject).createExpression(exprText)) + } + + override fun updateTitle(declaration: D?) = updateTitle(declaration, null) + + override fun saveSettings(declaration: D?) { + + } +} \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt index a4321902072..89cf58357d0 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinInplaceParameterIntroducer.kt @@ -19,7 +19,6 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.introduceParameter import com.intellij.codeInsight.template.impl.TemplateManagerImpl import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.RangeMarker import com.intellij.openapi.editor.impl.DocumentMarkupModel import com.intellij.openapi.editor.markup.EffectType import com.intellij.openapi.editor.markup.HighlighterTargetArea @@ -27,28 +26,24 @@ import com.intellij.openapi.editor.markup.MarkupModel import com.intellij.openapi.editor.markup.TextAttributes import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange -import com.intellij.psi.PsiAnchor -import com.intellij.psi.PsiFile -import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer import com.intellij.ui.JBColor import com.intellij.ui.NonFocusableCheckBox import com.intellij.util.ui.FormBuilder -import org.jetbrains.kotlin.idea.JetFileType -import org.jetbrains.kotlin.idea.core.replaced import org.jetbrains.kotlin.idea.refactoring.changeSignature.JetValVar +import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractKotlinInplaceIntroducer import org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable.KotlinInplaceVariableIntroducer import org.jetbrains.kotlin.idea.util.application.runWriteAction -import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiRange import org.jetbrains.kotlin.idea.util.psi.patternMatching.toRange import org.jetbrains.kotlin.idea.util.supertypes import org.jetbrains.kotlin.psi.* -import org.jetbrains.kotlin.psi.psiUtil.* +import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext +import org.jetbrains.kotlin.psi.psiUtil.getValueParameterList +import org.jetbrains.kotlin.psi.psiUtil.getValueParameters import org.jetbrains.kotlin.types.JetType import org.jetbrains.kotlin.utils.addToStdlib.singletonList import java.awt.BorderLayout import java.awt.Color import java.util.ArrayList -import java.util.Collections import javax.swing.JCheckBox public class KotlinInplaceParameterIntroducer( @@ -57,17 +52,15 @@ public class KotlinInplaceParameterIntroducer( val suggestedNames: Array, project: Project, editor: Editor -): AbstractInplaceIntroducer( - project, - editor, +): AbstractKotlinInplaceIntroducer( originalDescriptor.originalRange.elements.single() as JetExpression, - null, originalDescriptor.occurrencesToReplace .map { it.elements.single() as JetExpression } .filterNotNull() .toTypedArray(), INTRODUCE_PARAMETER, - JetFileType.INSTANCE + project, + editor ) { companion object { private val LOG = Logger.getInstance(javaClass()) @@ -106,7 +99,7 @@ public class KotlinInplaceParameterIntroducer( private var replaceAllCheckBox: JCheckBox? = null init { - val panel = with(FormBuilder.createFormBuilder()) { + initFormComponents { addComponent(getPreviewComponent()) val defaultValueCheckBox = NonFocusableCheckBox("Introduce default value") @@ -126,12 +119,7 @@ public class KotlinInplaceParameterIntroducer( addComponent(replaceAllCheckBox) this@KotlinInplaceParameterIntroducer.replaceAllCheckBox = replaceAllCheckBox } - - getPanel() } - - myWholePanel.setLayout(BorderLayout()) - myWholePanel.add(panel, BorderLayout.CENTER) } override fun getActionName() = "IntroduceParameter" @@ -159,24 +147,6 @@ public class KotlinInplaceParameterIntroducer( } } - override fun restoreExpression( - containingFile: PsiFile, - parameter: JetParameter, - marker: RangeMarker, - exprText: String - ): JetExpression? { - if (!parameter.isValid()) return null - - val refExpr = containingFile.findElementAt(marker.getStartOffset())?.getNonStrictParentOfType() - ?: return null - val refName = refExpr.getReferencedName() - if (refExpr.getReference()?.resolve() == parameter || parameter.getName() == refName || exprText == refName) { - return refExpr.replaced(JetPsiFactory(myProject).createExpression(exprText)) - } - - return null - } - override fun isReplaceAllOccurrences() = replaceAllCheckBox?.isSelected() ?: true override fun setReplaceAllOccurrences(allOccurrences: Boolean) { @@ -185,8 +155,6 @@ public class KotlinInplaceParameterIntroducer( override fun getComponent() = myWholePanel - override fun updateTitle(parameter: JetParameter?) = updateTitle(parameter, null) - override fun updateTitle(addedParameter: JetParameter?, currentName: String?) { val templateState = TemplateManagerImpl.getTemplateState(myEditor) if (templateState == null || templateState.getTemplate() == null) return @@ -249,10 +217,6 @@ public class KotlinInplaceParameterIntroducer( revalidate() } - override fun saveSettings(variable: JetParameter?) { - - } - override fun performIntroduce() { getDescriptorToRefactor(isReplaceAllOccurrences()).performRefactoring() } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.java b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.java index 330dc5ba153..80b7d00eedc 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.java +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.java @@ -73,7 +73,7 @@ import static org.jetbrains.kotlin.psi.PsiPackage.JetPsiFactory; public class KotlinIntroduceVariableHandler extends KotlinIntroduceHandlerBase { - private static final String INTRODUCE_VARIABLE = JetRefactoringBundle.message("introduce.variable"); + public static final String INTRODUCE_VARIABLE = JetRefactoringBundle.message("introduce.variable"); @Override public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile file, DataContext dataContext) { @@ -198,9 +198,7 @@ public class KotlinIntroduceVariableHandler extends KotlinIntroduceHandlerBase { calculateAnchor(commonParent, commonContainer, allReplaces), JetNameValidatorImpl.Target.PROPERTIES ); - String[] suggestedNames = JetNameSuggester.suggestNames(expression, validator, "value"); - final LinkedHashSet suggestedNamesSet = new LinkedHashSet(); - Collections.addAll(suggestedNamesSet, suggestedNames); + final String[] suggestedNames = JetNameSuggester.suggestNames(expression, validator, "value"); final Ref propertyRef = new Ref(); final ArrayList references = new ArrayList(); final Ref reference = new Ref(); @@ -222,13 +220,18 @@ public class KotlinIntroduceVariableHandler extends KotlinIntroduceHandlerBase { PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument()); PsiDocumentManager.getInstance(project). doPostponedOperationsAndUnblockDocument(editor.getDocument()); - KotlinInplaceVariableIntroducer variableIntroducer = - new KotlinInplaceVariableIntroducer(property, editor, project, INTRODUCE_VARIABLE, - references.toArray(new JetExpression[references.size()]), - reference.get(), finalReplaceOccurrence, - property, /*todo*/false, /*todo*/false, - expressionType, finalNoTypeInference); - variableIntroducer.performInplaceRefactoring(suggestedNamesSet); + KotlinVariableInplaceIntroducer variableIntroducer = + new KotlinVariableInplaceIntroducer(property, + reference.get(), + references.toArray(new JetExpression[references.size()]), + suggestedNames, + /*todo*/ false, + /*todo*/ false, + expressionType, + finalNoTypeInference, + project, + editor); + variableIntroducer.startInplaceIntroduceTemplate(); } } else if (onNonInteractiveFinish != null) { diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinVariableInplaceIntroducer.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinVariableInplaceIntroducer.kt new file mode 100644 index 00000000000..4256fee5332 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinVariableInplaceIntroducer.kt @@ -0,0 +1,157 @@ +/* + * 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.introduceVariable + +import com.intellij.codeInsight.template.TemplateBuilderImpl +import com.intellij.codeInsight.template.impl.TemplateManagerImpl +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.openapi.util +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiReference +import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil +import com.intellij.ui.NonFocusableCheckBox +import com.intellij.util.ui.FormBuilder +import org.jetbrains.kotlin.idea.intentions.SpecifyTypeExplicitlyIntention +import org.jetbrains.kotlin.idea.refactoring.introduce.AbstractKotlinInplaceIntroducer +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.application.executeWriteCommand +import org.jetbrains.kotlin.psi.JetExpression +import org.jetbrains.kotlin.psi.JetProperty +import org.jetbrains.kotlin.psi.JetPsiFactory +import org.jetbrains.kotlin.psi.psiUtil.startOffset +import org.jetbrains.kotlin.types.JetType +import java.awt.BorderLayout + +public class KotlinVariableInplaceIntroducer( + val addedVariable: JetProperty, + val originalExpression: JetExpression, + val occurrencesToReplace: Array, + val suggestedNames: Array, + val isVar: Boolean, + val doNotChangeVar: Boolean, + val expressionType: JetType?, + val noTypeInference: Boolean, + project: Project, + editor: Editor +): AbstractKotlinInplaceIntroducer( + originalExpression, + occurrencesToReplace, + KotlinIntroduceVariableHandler.INTRODUCE_VARIABLE, + project, + editor +) { + companion object { + } + + init { + initFormComponents { + if (!doNotChangeVar) { + val varCheckBox = NonFocusableCheckBox("Declare with var") + varCheckBox.setSelected(isVar) + varCheckBox.setMnemonic('v') + varCheckBox.addActionListener { + myProject.executeWriteCommand(getCommandName(), getCommandName()) { + PsiDocumentManager.getInstance(myProject).commitDocument(myEditor.getDocument()) + + val psiFactory = JetPsiFactory(myProject) + val keyword = if (varCheckBox.isSelected()) psiFactory.createVarKeyword() else psiFactory.createValKeyword() + addedVariable.getValOrVarKeyword().replace(keyword) + } + } + addComponent(varCheckBox) + } + + if (expressionType != null && !noTypeInference) { + val expressionTypeCheckBox = NonFocusableCheckBox("Specify type explicitly") + expressionTypeCheckBox.setSelected(false) + expressionTypeCheckBox.setMnemonic('t') + expressionTypeCheckBox.addActionListener { + runWriteCommandAndRestart { + if (expressionTypeCheckBox.isSelected()) { + val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(expressionType) + addedVariable.setTypeReference(JetPsiFactory(myProject).createType(renderedType)) + } + else { + addedVariable.setTypeReference(null) + } + } + } + addComponent(expressionTypeCheckBox) + } + } + } + + override fun getVariable() = addedVariable + + override fun suggestNames(replaceAll: Boolean, variable: JetProperty?) = suggestedNames + + override fun createFieldToStartTemplateOn(replaceAll: Boolean, names: Array) = addedVariable + + override fun addAdditionalVariables(builder: TemplateBuilderImpl) { + addedVariable.getTypeReference()?.let { + builder.replaceElement(it, + "TypeReferenceVariable", + SpecifyTypeExplicitlyIntention.createTypeExpressionForTemplate(expressionType!!), + false) + } + } + + override fun buildTemplateAndStart(refs: Collection, + stringUsages: Collection>, + scope: PsiElement, + containingFile: PsiFile): Boolean { + myEditor.getCaretModel().moveToOffset(getNameIdentifier()!!.startOffset) + + val result = super.buildTemplateAndStart(refs, stringUsages, scope, containingFile) + + val templateState = TemplateManagerImpl.getTemplateState(InjectedLanguageUtil.getTopLevelEditor(myEditor)) + if (templateState != null && addedVariable.getTypeReference() != null) { + templateState.addTemplateStateListener(SpecifyTypeExplicitlyIntention.createTypeReferencePostprocessor(addedVariable)) + } + + return result + } + + override fun updateTitle(variable: JetProperty?, value: String?) { + // No preview to update + } + + override fun deleteTemplateField(psiField: JetProperty?) { + // Do not delete introduced variable as it was created outside of in-place refactoring + } + + override fun isReplaceAllOccurrences() = true + + override fun setReplaceAllOccurrences(allOccurrences: Boolean) { + + } + + override fun getComponent() = myWholePanel + + override fun performIntroduce() { + val replacement = JetPsiFactory(myProject).createExpression(addedVariable.getName() ?: return) + getOccurrences().forEach { + if (it.isValid()) { + it.replace(replacement) + } + } + } +} \ No newline at end of file