diff --git a/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt b/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt index e4cdb5d1b44..5d63cfa4629 100644 --- a/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt +++ b/idea/src/org/jetbrains/kotlin/idea/intentions/ObjectLiteralToLambdaIntention.kt @@ -112,10 +112,10 @@ class ObjectLiteralToLambdaIntention : SelfTargetingRangeIntention(KtCallableDeclaration::class.java, "Convert member to extension"), LowPriorityAction { @@ -81,34 +84,71 @@ class ConvertMemberToExtensionIntention : } runWriteAction { - val (extension, bodyToSelect) = createExtensionCallableAndPrepareBodyToSelect(element, allowExpected) + val (extension, bodyTypeToSelect) = createExtensionCallableAndPrepareBodyToSelect(element, allowExpected) editor?.apply { unblockDocument() - if (bodyToSelect != null) { - val range = bodyToSelect.textRange - moveCaret(range.startOffset, ScrollType.CENTER) + if (extension.isValid) { - val parent = bodyToSelect.parent - val lastSibling = - if (parent is KtBlockExpression) - parent.rBrace?.siblings(forward = false, withItself = false)?.first { it !is PsiWhiteSpace } - else - bodyToSelect.siblings(forward = true, withItself = false).lastOrNull() - val endOffset = lastSibling?.endOffset ?: range.endOffset - selectionModel.setSelection(range.startOffset, endOffset) + if (bodyTypeToSelect != GeneratedBodyType.NOTHING) { + val bodyToSelect = getBodyForSelection(extension, bodyTypeToSelect) + + if (bodyToSelect != null) { + val range = bodyToSelect.textRange + moveCaret(range.startOffset, ScrollType.CENTER) + + val parent = bodyToSelect.parent + val lastSibling = + if (parent is KtBlockExpression) + parent.rBrace?.siblings(forward = false, withItself = false)?.first { it !is PsiWhiteSpace } + else + bodyToSelect.siblings(forward = true, withItself = false).lastOrNull() + val endOffset = lastSibling?.endOffset ?: range.endOffset + selectionModel.setSelection(range.startOffset, endOffset) + } else { + LOG.error("Extension created with new method body for $bodyToSelect but this body was not found after document commit. Extension text: \"${extension.text}\"") + moveCaret(extension.textOffset, ScrollType.CENTER) + } + } else { + moveCaret(extension.textOffset, ScrollType.CENTER) + } } else { - moveCaret(extension.textOffset, ScrollType.CENTER) + LOG.error("Extension invalidated during document commit. Extension text \"${extension.text}\"") } } } } + private fun getBodyForSelection(extension: KtCallableDeclaration, bodyTypeToSelect: GeneratedBodyType): KtExpression? { + fun selectBody(declaration: KtDeclarationWithBody): KtExpression? { + + if (!declaration.hasBody()) return extension + + return declaration.bodyExpression?.let { + (it as? KtBlockExpression)?.statements?.singleOrNull() ?: it + } + } + + return when (bodyTypeToSelect) { + GeneratedBodyType.FUNCTION -> (extension as? KtFunction)?.let { selectBody(it) } + GeneratedBodyType.GETTER -> (extension as? KtProperty)?.getter?.let { selectBody(it) } + GeneratedBodyType.SETTER -> (extension as? KtProperty)?.setter?.let { selectBody(it) } + else -> null + } + } + + private enum class GeneratedBodyType { + NOTHING, + FUNCTION, + SETTER, + GETTER + } + private fun processSingleDeclaration( element: KtCallableDeclaration, allowExpected: Boolean - ): Pair { + ): Pair { val descriptor = element.unsafeResolveToDescriptor() val containingClass = descriptor.containingDeclaration as ClassDescriptor @@ -161,14 +201,7 @@ class ConvertMemberToExtensionIntention : extension.addModifier(KtTokens.EXPECT_KEYWORD) } - var bodyToSelect: KtExpression? = null - - fun selectBody(declaration: KtDeclarationWithBody) { - if (bodyToSelect == null) { - val body = declaration.bodyExpression - bodyToSelect = (body as? KtBlockExpression)?.statements?.single() ?: body - } - } + var bodyTypeToSelect = GeneratedBodyType.NOTHING val bodyText = getFunctionBodyTextFromTemplate( project, @@ -183,7 +216,7 @@ class ConvertMemberToExtensionIntention : if (!extension.hasBody() && !isEffectivelyExpected) { //TODO: methods in PSI for setBody extension.add(psiFactory.createBlock(bodyText)) - selectBody(extension) + bodyTypeToSelect = GeneratedBodyType.FUNCTION } } @@ -198,10 +231,10 @@ class ConvertMemberToExtensionIntention : if (getter == null) { getter = extension.addAfter(templateGetter, extension.typeReference) as KtPropertyAccessor extension.addBefore(psiFactory.createNewLine(), getter) - selectBody(getter) + bodyTypeToSelect = GeneratedBodyType.GETTER } else if (!getter.hasBody()) { getter = getter.replace(templateGetter) as KtPropertyAccessor - selectBody(getter) + bodyTypeToSelect = GeneratedBodyType.GETTER } if (extension.isVar) { @@ -209,10 +242,14 @@ class ConvertMemberToExtensionIntention : if (setter == null) { setter = extension.addAfter(templateSetter, getter) as KtPropertyAccessor extension.addBefore(psiFactory.createNewLine(), setter) - selectBody(setter) + if (bodyTypeToSelect == GeneratedBodyType.NOTHING) { + bodyTypeToSelect = GeneratedBodyType.SETTER + } } else if (!setter.hasBody()) { - setter = setter.replace(templateSetter) as KtPropertyAccessor - selectBody(setter) + setter.replace(templateSetter) as KtPropertyAccessor + if (bodyTypeToSelect == GeneratedBodyType.NOTHING) { + bodyTypeToSelect = GeneratedBodyType.SETTER + } } } } @@ -240,7 +277,7 @@ class ConvertMemberToExtensionIntention : } } - return extension to bodyToSelect + return extension to bodyTypeToSelect } private fun askIfExpectedIsAllowed(file: KtFile): Boolean { @@ -258,7 +295,7 @@ class ConvertMemberToExtensionIntention : private fun createExtensionCallableAndPrepareBodyToSelect( element: KtCallableDeclaration, allowExpected: Boolean = true - ): Pair { + ): Pair { val expectedDeclaration = element.liftToExpected() as? KtCallableDeclaration if (expectedDeclaration != null) { element.withExpectedActuals().filterIsInstance().forEach { @@ -269,11 +306,11 @@ class ConvertMemberToExtensionIntention : } val classVisibility = element.containingClass()?.visibilityModifierType() - val (extension, bodyToSelect) = processSingleDeclaration(element, allowExpected) + val (extension, bodyTypeToSelect) = processSingleDeclaration(element, allowExpected) if (classVisibility != null && extension.visibilityModifier() == null) { extension.addModifier(classVisibility) } - return extension to bodyToSelect + return extension to bodyTypeToSelect } private fun newTypeParameterList(member: KtCallableDeclaration): KtTypeParameterList? { @@ -290,4 +327,4 @@ class ConvertMemberToExtensionIntention : return ConvertMemberToExtensionIntention().createExtensionCallableAndPrepareBodyToSelect(element).first } } -} +} \ No newline at end of file