diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.kt index 4dfd951009e..ca73d22fd55 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/KotlinIntroduceVariableHandler.kt @@ -24,7 +24,7 @@ import com.intellij.openapi.command.impl.FinishMarkAction import com.intellij.openapi.command.impl.StartMarkAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project -import com.intellij.openapi.ui.Messages +import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.util.Key import com.intellij.openapi.util.TextRange import com.intellij.psi.* @@ -32,6 +32,7 @@ import com.intellij.psi.util.PsiTreeUtil import com.intellij.refactoring.HelpID import com.intellij.refactoring.introduce.inplace.OccurrencesChooser import com.intellij.refactoring.util.CommonRefactoringUtil +import com.intellij.ui.components.JBList import com.intellij.util.SmartList import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.FunctionDescriptor @@ -378,13 +379,29 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() { CommonRefactoringUtil.showErrorHint(project, editor, message, INTRODUCE_VARIABLE, HelpID.INTRODUCE_VARIABLE) } - private fun KtExpression.chooseApplicableComponentFunctions(haveOccurrencesToReplace: Boolean): List? { - if (haveOccurrencesToReplace) return emptyList() + private fun KtExpression.chooseApplicableComponentFunctions( + haveOccurrencesToReplace: Boolean, + editor: Editor?, + callback: (List) -> Unit + ) { + if (haveOccurrencesToReplace) return callback(emptyList()) val functions = getApplicableComponentFunctions(this) - if (functions.size <= 1) return emptyList() + if (functions.size <= 1) return callback(emptyList()) - return functions + if (ApplicationManager.getApplication().isUnitTestMode) return callback(functions) + + if (editor == null) return callback(emptyList()) + + val list = JBList("Create single variable", "Create destructuring declaration") + JBPopupFactory.getInstance() + .createListPopupBuilder(list) + .setMovable(true) + .setResizable(false) + .setRequestFocus(true) + .setItemChoosenCallback { callback(if (list.selectedIndex == 0) emptyList() else functions) } + .createPopup() + .showInBestPositionFor(editor) } private fun executeMultiDeclarationTemplate( @@ -517,67 +534,67 @@ object KotlinIntroduceVariableHandler : KotlinIntroduceHandlerBase() { commonContainer = container } - val componentFunctions = physicalExpression.chooseApplicableComponentFunctions(replaceOccurrence) ?: return@Pass - - val validator = NewDeclarationNameValidator( - commonContainer, - calculateAnchor(commonParent, commonContainer, allReplaces), - NewDeclarationNameValidator.Target.VARIABLES - ) - val suggestedNames = if (componentFunctions.isNotEmpty()) { - val collectingValidator = CollectingNameValidator(filter = validator) - componentFunctions.map { suggestNamesForComponent(it, project, collectingValidator) } - } - else { - KotlinNameSuggester.suggestNamesByExpressionAndType(expression, - substringInfo?.type, - bindingContext, - validator, - "value").singletonList() - } - val introduceVariableContext = IntroduceVariableContext( - expression, suggestedNames, allReplaces, commonContainer, commonParent, - replaceOccurrence, noTypeInference, expressionType, componentFunctions, bindingContext, resolutionFacade - ) - project.executeCommand(INTRODUCE_VARIABLE, null) { - runWriteAction { introduceVariableContext.runRefactoring() } - - val property = introduceVariableContext.propertyRef ?: return@executeCommand - - if (editor == null) { - onNonInteractiveFinish?.invoke(property) - return@executeCommand + physicalExpression.chooseApplicableComponentFunctions(replaceOccurrence, editor) { componentFunctions -> + val validator = NewDeclarationNameValidator( + commonContainer, + calculateAnchor(commonParent, commonContainer, allReplaces), + NewDeclarationNameValidator.Target.VARIABLES + ) + val suggestedNames = if (componentFunctions.isNotEmpty()) { + val collectingValidator = CollectingNameValidator(filter = validator) + componentFunctions.map { suggestNamesForComponent(it, project, collectingValidator) } } + else { + KotlinNameSuggester.suggestNamesByExpressionAndType(expression, + substringInfo?.type, + bindingContext, + validator, + "value").singletonList() + } + val introduceVariableContext = IntroduceVariableContext( + expression, suggestedNames, allReplaces, commonContainer, commonParent, + replaceOccurrence, noTypeInference, expressionType, componentFunctions, bindingContext, resolutionFacade + ) + project.executeCommand(INTRODUCE_VARIABLE, null) { + runWriteAction { introduceVariableContext.runRefactoring() } - editor.caretModel.moveToOffset(property.textOffset) - editor.selectionModel.removeSelection() + val property = introduceVariableContext.propertyRef ?: return@executeCommand - if (!isInplaceAvailable) return@executeCommand - - PsiDocumentManager.getInstance(project).commitDocument(editor.document) - PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) - - when (property) { - is KtProperty -> { - KotlinVariableInplaceIntroducer( - property, - introduceVariableContext.reference, - introduceVariableContext.references.toTypedArray(), - suggestedNames.single(), - /*todo*/ false, - /*todo*/ false, - expressionType, - noTypeInference, - project, - editor - ).startInplaceIntroduceTemplate() + if (editor == null) { + onNonInteractiveFinish?.invoke(property) + return@executeCommand } - is KtMultiDeclaration -> { - executeMultiDeclarationTemplate(project, editor, property, suggestedNames) - } + editor.caretModel.moveToOffset(property.textOffset) + editor.selectionModel.removeSelection() - else -> throw AssertionError("Unexpected declaration: ${property.getElementTextWithContext()}") + if (!isInplaceAvailable) return@executeCommand + + PsiDocumentManager.getInstance(project).commitDocument(editor.document) + PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.document) + + when (property) { + is KtProperty -> { + KotlinVariableInplaceIntroducer( + property, + introduceVariableContext.reference, + introduceVariableContext.references.toTypedArray(), + suggestedNames.single(), + /*todo*/ false, + /*todo*/ false, + expressionType, + noTypeInference, + project, + editor + ).startInplaceIntroduceTemplate() + } + + is KtMultiDeclaration -> { + executeMultiDeclarationTemplate(project, editor, property, suggestedNames) + } + + else -> throw AssertionError("Unexpected declaration: ${property.getElementTextWithContext()}") + } } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/introduceVariableUtils.kt b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/introduceVariableUtils.kt index ac6a2c26683..d2a31d880e7 100644 --- a/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/introduceVariableUtils.kt +++ b/idea/src/org/jetbrains/kotlin/idea/refactoring/introduce/introduceVariable/introduceVariableUtils.kt @@ -16,19 +16,37 @@ package org.jetbrains.kotlin.idea.refactoring.introduce.introduceVariable +import org.jetbrains.kotlin.builtins.PrimitiveType import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.idea.analysis.analyzeInContext import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade +import org.jetbrains.kotlin.idea.imports.importableFqName import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.createExpressionByPattern import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.util.isValidOperator +import org.jetbrains.kotlin.utils.addToStdlib.singletonList fun getApplicableComponentFunctions(expression: KtExpression): List { val facade = expression.getResolutionFacade() - val scope = expression.getResolutionScope(facade.analyze(expression), facade) + val context = facade.analyze(expression) + val builtIns = facade.moduleDescriptor.builtIns + + val forbiddenClasses = arrayListOf(builtIns.collection, builtIns.array) + PrimitiveType.values().mapTo(forbiddenClasses) { builtIns.getPrimitiveArrayClassDescriptor(it) } + + context.getType(expression)?.let { + if ((it.singletonList() + it.supertypes()).any { + val fqName = it.constructor.declarationDescriptor?.importableFqName + forbiddenClasses.any { it.fqNameSafe == fqName } + }) return emptyList() + } + + val scope = expression.getResolutionScope(context, facade) val psiFactory = KtPsiFactory(expression) @Suppress("UNCHECKED_CAST") diff --git a/idea/testData/refactoring/introduceVariable/multiDeclarations/array.kt b/idea/testData/refactoring/introduceVariable/multiDeclarations/array.kt new file mode 100644 index 00000000000..76cb424b71a --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/multiDeclarations/array.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo() { + arrayOf("1", "2", "3") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/multiDeclarations/array.kt.after b/idea/testData/refactoring/introduceVariable/multiDeclarations/array.kt.after new file mode 100644 index 00000000000..a88dfcc394b --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/multiDeclarations/array.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo() { + val arrayOf = arrayOf("1", "2", "3") +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/multiDeclarations/intArray.kt b/idea/testData/refactoring/introduceVariable/multiDeclarations/intArray.kt new file mode 100644 index 00000000000..348087dbf9b --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/multiDeclarations/intArray.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo() { + intArrayOf(1, 2, 3) +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/multiDeclarations/intArray.kt.after b/idea/testData/refactoring/introduceVariable/multiDeclarations/intArray.kt.after new file mode 100644 index 00000000000..bbd1f909673 --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/multiDeclarations/intArray.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo() { + val intArrayOf = intArrayOf(1, 2, 3) +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/multiDeclarations/list.kt b/idea/testData/refactoring/introduceVariable/multiDeclarations/list.kt new file mode 100644 index 00000000000..c598c14a927 --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/multiDeclarations/list.kt @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo() { + listOf(1, 2, 3) +} \ No newline at end of file diff --git a/idea/testData/refactoring/introduceVariable/multiDeclarations/list.kt.after b/idea/testData/refactoring/introduceVariable/multiDeclarations/list.kt.after new file mode 100644 index 00000000000..9250e09e953 --- /dev/null +++ b/idea/testData/refactoring/introduceVariable/multiDeclarations/list.kt.after @@ -0,0 +1,4 @@ +// WITH_RUNTIME +fun foo() { + val listOf = listOf(1, 2, 3) +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java index 28b1c460090..bb5e10eb065 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/refactoring/introduce/ExtractionTestGenerated.java @@ -516,6 +516,24 @@ public class ExtractionTestGenerated extends AbstractExtractionTest { KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/refactoring/introduceVariable/multiDeclarations"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("array.kt") + public void testArray() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/multiDeclarations/array.kt"); + doIntroduceVariableTest(fileName); + } + + @TestMetadata("intArray.kt") + public void testIntArray() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/multiDeclarations/intArray.kt"); + doIntroduceVariableTest(fileName); + } + + @TestMetadata("list.kt") + public void testList() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/multiDeclarations/list.kt"); + doIntroduceVariableTest(fileName); + } + @TestMetadata("notOperators.kt") public void testNotOperators() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("idea/testData/refactoring/introduceVariable/multiDeclarations/notOperators.kt");