Introduce Variable: Reimplement in-place refactoring using AbstractInplaceIntroducer. Extract common superclass AbstractKotlinInplaceIntroducer

This commit is contained in:
Alexey Sedunov
2015-06-01 20:11:16 +03:00
parent 4a56b99cff
commit dae7717a69
4 changed files with 269 additions and 55 deletions
@@ -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<D: JetNamedDeclaration>(
expression: JetExpression,
occurrences: Array<JetExpression>,
title: String,
project: Project,
editor: Editor
): AbstractInplaceIntroducer<D, JetExpression>(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<JetSimpleNameExpression>()
?.replaced(JetPsiFactory(myProject).createExpression(exprText))
}
override fun updateTitle(declaration: D?) = updateTitle(declaration, null)
override fun saveSettings(declaration: D?) {
}
}
@@ -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<out String>,
project: Project,
editor: Editor
): AbstractInplaceIntroducer<JetParameter, JetExpression>(
project,
editor,
): AbstractKotlinInplaceIntroducer<JetParameter>(
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<KotlinInplaceParameterIntroducer>())
@@ -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<JetSimpleNameExpression>()
?: 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()
}
@@ -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<String> suggestedNamesSet = new LinkedHashSet<String>();
Collections.addAll(suggestedNamesSet, suggestedNames);
final String[] suggestedNames = JetNameSuggester.suggestNames(expression, validator, "value");
final Ref<JetProperty> propertyRef = new Ref<JetProperty>();
final ArrayList<JetExpression> references = new ArrayList<JetExpression>();
final Ref<JetExpression> reference = new Ref<JetExpression>();
@@ -222,13 +220,18 @@ public class KotlinIntroduceVariableHandler extends KotlinIntroduceHandlerBase {
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
PsiDocumentManager.getInstance(project).
doPostponedOperationsAndUnblockDocument(editor.getDocument());
KotlinInplaceVariableIntroducer<JetProperty> variableIntroducer =
new KotlinInplaceVariableIntroducer<JetProperty>(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) {
@@ -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<JetExpression>,
val suggestedNames: Array<out String>,
val isVar: Boolean,
val doNotChangeVar: Boolean,
val expressionType: JetType?,
val noTypeInference: Boolean,
project: Project,
editor: Editor
): AbstractKotlinInplaceIntroducer<JetProperty>(
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<out String>) = addedVariable
override fun addAdditionalVariables(builder: TemplateBuilderImpl) {
addedVariable.getTypeReference()?.let {
builder.replaceElement(it,
"TypeReferenceVariable",
SpecifyTypeExplicitlyIntention.createTypeExpressionForTemplate(expressionType!!),
false)
}
}
override fun buildTemplateAndStart(refs: Collection<PsiReference>,
stringUsages: Collection<util.Pair<PsiElement, TextRange>>,
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)
}
}
}
}