Introduce Parameter: Implement dialog UI

This commit is contained in:
Alexey Sedunov
2015-04-07 20:59:05 +03:00
parent da14222c4a
commit 68a5b05af9
2 changed files with 264 additions and 47 deletions
@@ -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<String>,
val typeSuggestions: List<JetType>
): 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<JCheckBox, JetParameter>(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<out UsageInfo>) {
descriptorToRefactor.performRefactoring(
parametersToRemove =
removeParamsCheckBoxes.filter { it.key.isEnabled() && it.key.isSelected() }.map { it.value }
)
}
override fun createUsageViewDescriptor(usages: Array<out UsageInfo>) = BaseUsageViewDescriptor()
override fun getCommandName() = INTRODUCE_PARAMETER
}
)
}
}
@@ -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<JetParameter> = 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")
val INTRODUCE_PARAMETER: String = JetRefactoringBundle.message("introduce.parameter")