diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt new file mode 100644 index 00000000000..2ed584bc116 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterDialog.kt @@ -0,0 +1,200 @@ +/* + * 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.introduceParameter + +import com.intellij.openapi.options.ConfigurationException +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiNameHelper +import com.intellij.refactoring.BaseRefactoringProcessor +import com.intellij.refactoring.ui.NameSuggestionsField +import com.intellij.refactoring.ui.RefactoringDialog +import com.intellij.ui.NonFocusableCheckBox +import com.intellij.usageView.BaseUsageViewDescriptor +import com.intellij.usageView.UsageInfo +import com.intellij.usageView.UsageViewDescriptor +import org.jetbrains.kotlin.idea.JetFileType +import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers +import org.jetbrains.kotlin.idea.util.application.executeCommand +import org.jetbrains.kotlin.idea.util.application.runWriteAction +import org.jetbrains.kotlin.psi.JetParameter +import org.jetbrains.kotlin.psi.JetPsiFactory +import org.jetbrains.kotlin.psi.JetPsiUtil +import org.jetbrains.kotlin.resolve.AnalyzingUtils +import org.jetbrains.kotlin.types.JetType +import java.awt.GridBagConstraints +import java.awt.GridBagLayout +import java.awt.Insets +import java.util.ArrayList +import java.util.Collections +import java.util.LinkedHashMap +import javax.swing.JCheckBox +import javax.swing.JComponent +import javax.swing.JLabel +import javax.swing.JPanel + +public class KotlinIntroduceParameterDialog( + project: Project, + val descriptor: IntroduceParameterDescriptor, + nameSuggestions: Array, + val typeSuggestions: List +): RefactoringDialog(project, true) { + private val typeNameSuggestions = typeSuggestions + .map { IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(it) } + .copyToArray() + + private val nameField = NameSuggestionsField(nameSuggestions, project, JetFileType.INSTANCE) + private val typeField = NameSuggestionsField(typeNameSuggestions, project, JetFileType.INSTANCE) + private var replaceAllCheckBox: JCheckBox? = null + private val removeParamsCheckBoxes = LinkedHashMap(descriptor.parametersToRemove.size()) + + init { + setTitle(INTRODUCE_PARAMETER) + init() + + nameField.addDataChangedListener { validateButtons() } + typeField.addDataChangedListener { validateButtons() } + } + + override fun getPreferredFocusedComponent() = nameField.getFocusableComponent() + + override fun createNorthPanel(): JComponent? { + val gbConstraints = GridBagConstraints() + + val panel = JPanel(GridBagLayout()) + + gbConstraints.anchor = GridBagConstraints.WEST + gbConstraints.fill = GridBagConstraints.NONE + gbConstraints.gridx = 0 + + gbConstraints.insets = Insets(4, 4, 4, 0) + gbConstraints.gridwidth = 1 + gbConstraints.weightx = 0.0 + gbConstraints.weighty = 0.0 + gbConstraints.gridy = 0 + val nameLabel = JLabel("Parameter name:") + nameLabel.setDisplayedMnemonic('n') + nameLabel.setLabelFor(nameField) + panel.add(nameLabel, gbConstraints) + + gbConstraints.insets = Insets(4, 4, 4, 8) + gbConstraints.gridx++ + gbConstraints.weightx = 1.0 + gbConstraints.fill = GridBagConstraints.BOTH + panel.add(nameField, gbConstraints) + + gbConstraints.insets = Insets(4, 4, 4, 8) + gbConstraints.gridwidth = 1 + gbConstraints.weightx = 0.0 + gbConstraints.gridx = 0 + gbConstraints.gridy = 1 + gbConstraints.fill = GridBagConstraints.NONE + val typeLabel = JLabel("Parameter type: ") + typeLabel.setDisplayedMnemonic('t') + typeLabel.setLabelFor(typeField) + panel.add(typeLabel, gbConstraints) + + gbConstraints.gridx++ + gbConstraints.insets = Insets(4, 4, 4, 8) + gbConstraints.weightx = 1.0 + gbConstraints.fill = GridBagConstraints.BOTH + panel.add(typeField, gbConstraints) + + gbConstraints.gridx = 0 + gbConstraints.insets = Insets(4, 0, 4, 8) + gbConstraints.gridwidth = 2 + val occurrenceCount = descriptor.occurrencesToReplace.size() + if (occurrenceCount > 1) { + gbConstraints.gridy++ + val replaceAllCheckBox = NonFocusableCheckBox("Replace all occurrences ($occurrenceCount)") + replaceAllCheckBox.setSelected(true) + replaceAllCheckBox.setMnemonic('R') + replaceAllCheckBox.addActionListener { + val enableParamRemove = replaceAllCheckBox.isSelected() + removeParamsCheckBoxes.keySet().forEach { + it.setEnabled(enableParamRemove) + it.setSelected(enableParamRemove) + } + } + panel.add(replaceAllCheckBox, gbConstraints) + this.replaceAllCheckBox = replaceAllCheckBox + } + + if (replaceAllCheckBox != null) { + gbConstraints.insets = Insets(0, 16, 4, 8) + } + + for (parameter in descriptor.parametersToRemove) { + val cb = NonFocusableCheckBox("Remove parameter '${parameter.getName()}' no longer used") + + removeParamsCheckBoxes[cb] = parameter + cb.setSelected(true) + gbConstraints.gridy++ + panel.add(cb, gbConstraints) + } + + return panel + } + + override fun createCenterPanel() = null + + override fun canRun() { + fun validateElement(e: PsiElement, errorMessage: String) { + try { + AnalyzingUtils.checkForSyntacticErrors(e) + } + catch(e: Exception) { + throw ConfigurationException(errorMessage) + } + } + + val psiFactory = JetPsiFactory(myProject) + validateElement(psiFactory.createType(nameField.getEnteredName()), "Invalid parameter name") + validateElement(psiFactory.createType(typeField.getEnteredName()), "Invalid parameter type") + } + + override fun doAction() { + val descriptorToRefactor = descriptor.copy( + addedParameter = JetPsiFactory(myProject).createParameter("${nameField.getEnteredName()}: ${typeField.getEnteredName()}"), + occurrencesToReplace = with(descriptor) { + if (replaceAllCheckBox?.isSelected() ?: true) { + occurrencesToReplace + } + else { + Collections.singletonList(originalOccurrence) + } + } + ) + invokeRefactoring( + object: BaseRefactoringProcessor(myProject) { + override fun findUsages() = UsageInfo.EMPTY_ARRAY + + override fun performRefactoring(usages: Array) { + descriptorToRefactor.performRefactoring( + parametersToRemove = + removeParamsCheckBoxes.filter { it.key.isEnabled() && it.key.isSelected() }.map { it.value } + ) + } + + override fun createUsageViewDescriptor(usages: Array) = BaseUsageViewDescriptor() + + override fun getCommandName() = INTRODUCE_PARAMETER + } + ) + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt index 6e40f75c224..22f4e549e43 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceParameter/KotlinIntroduceParameterHandler.kt @@ -46,6 +46,7 @@ import org.jetbrains.kotlin.idea.util.application.executeCommand import org.jetbrains.kotlin.idea.util.application.runWriteAction import org.jetbrains.kotlin.idea.util.psi.patternMatching.JetPsiUnifier 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.getParentOfTypeAndBranch import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType @@ -103,7 +104,7 @@ public data class IntroduceParameterDescriptor( } } -fun IntroduceParameterDescriptor.performRefactoring() { +fun IntroduceParameterDescriptor.performRefactoring(parametersToRemove: List = this.parametersToRemove) { runWriteAction { JetPsiUtil.deleteElementWithDelimiters(addedParameter) @@ -204,58 +205,74 @@ public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() } .filterNotNull() - project.executeCommand(INTRODUCE_PARAMETER) { - val psiFactory = JetPsiFactory(project) - - val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(parameterType) - val newParameter = psiFactory.createParameter("${suggestedNames.first()}: $renderedType") + project.executeCommand( + INTRODUCE_PARAMETER, + null, + fun() { + val psiFactory = JetPsiFactory(project) + + val renderedType = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.renderType(parameterType) + val newParameter = psiFactory.createParameter("${suggestedNames.first()}: $renderedType") - val addedParameter = runWriteAction { - val newParameterList = - if (parameterList == null) { - val klass = targetParent as? JetClass - val anchor = klass?.getTypeParameterList() ?: klass?.getNameIdentifier() - assert(anchor != null, "Invalid declaration: ${JetPsiUtil.getElementTextWithContext(targetParent)}") + val isTestMode = ApplicationManager.getApplication().isUnitTestMode() + val inplaceIsAvailable = editor.getSettings().isVariableInplaceRenameEnabled() && !isTestMode - val constructor = targetParent.addAfter(psiFactory.createPrimaryConstructor(), anchor) as JetPrimaryConstructor - constructor.getValueParameterList()!! + val addedParameter = if (inplaceIsAvailable) { + runWriteAction { + val newParameterList = + if (parameterList == null) { + val klass = targetParent as? JetClass + val anchor = klass?.getTypeParameterList() ?: klass?.getNameIdentifier() + assert(anchor != null, "Invalid declaration: ${JetPsiUtil.getElementTextWithContext(targetParent)}") + + val constructor = targetParent.addAfter(psiFactory.createPrimaryConstructor(), anchor) as JetPrimaryConstructor + constructor.getValueParameterList()!! + } + else parameterList + + val lastParameter = newParameterList.getChildren().lastOrNull { it is JetParameter } as? JetParameter + if (lastParameter != null) { + val comma = newParameterList.addAfter(psiFactory.createComma(), lastParameter) + newParameterList.addAfter(newParameter, comma) as JetParameter + } + else { + val singleParameterList = psiFactory.createParameterList("(${newParameter.getText()})") + (newParameterList.replace(singleParameterList) as JetParameterList).getParameters().first() + } } - else parameterList + } + else newParameter - val lastParameter = newParameterList.getChildren().lastOrNull { it is JetParameter } as? JetParameter - if (lastParameter != null) { - val comma = newParameterList.addAfter(psiFactory.createComma(), lastParameter) - newParameterList.addAfter(newParameter, comma) as JetParameter - } - else { - val singleParameterList = psiFactory.createParameterList("(${newParameter.getText()})") - (newParameterList.replace(singleParameterList) as JetParameterList).getParameters().first() - } - } + val introduceParameterDescriptor = + configure(IntroduceParameterDescriptor(JetPsiUtil.deparenthesize(expression)!!, + targetParent, + functionDescriptor, + addedParameter, + parameterType, + false, + parametersUsages, + occurrencesToReplace)) + if (isTestMode) { + introduceParameterDescriptor.performRefactoring() + return + } - val introduceParameterDescriptor = - configure(IntroduceParameterDescriptor(JetPsiUtil.deparenthesize(expression)!!, - targetParent, - functionDescriptor, - addedParameter, - parameterType, - false, - parametersUsages, - occurrencesToReplace)) - if (editor.getSettings().isVariableInplaceRenameEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) { - with(PsiDocumentManager.getInstance(project)) { - commitDocument(editor.getDocument()) - doPostponedOperationsAndUnblockDocument(editor.getDocument()) - } + if (inplaceIsAvailable) { + with(PsiDocumentManager.getInstance(project)) { + commitDocument(editor.getDocument()) + doPostponedOperationsAndUnblockDocument(editor.getDocument()) + } - if (!KotlinInplaceParameterIntroducer(introduceParameterDescriptor, editor, project).startRefactoring(suggestedNames)) { - introduceParameterDescriptor.performRefactoring() + if (KotlinInplaceParameterIntroducer(introduceParameterDescriptor, editor, project) + .startRefactoring(suggestedNames)) return + } + + KotlinIntroduceParameterDialog(project, + introduceParameterDescriptor, + suggestedNames.copyToArray(), + listOf(parameterType) + parameterType.supertypes()).show() } - } - else { - introduceParameterDescriptor.performRefactoring() - } - } + ) } override fun invoke(project: Project, editor: Editor, file: PsiFile, dataContext: DataContext?) { @@ -294,4 +311,4 @@ public open class KotlinIntroduceParameterHandler: KotlinIntroduceHandlerBase() } } -val INTRODUCE_PARAMETER: String = JetRefactoringBundle.message("introduce.parameter") \ No newline at end of file +val INTRODUCE_PARAMETER: String = JetRefactoringBundle.message("introduce.parameter")