Extract utility functions from ExtractKotlinFunctionHandler

This commit is contained in:
Alexey Sedunov
2014-09-24 18:48:22 +04:00
parent 67df2b448f
commit 22e3557895
2 changed files with 148 additions and 134 deletions
@@ -31,15 +31,12 @@ import org.jetbrains.jet.plugin.refactoring.extractFunction.ui.KotlinExtractFunc
import org.jetbrains.jet.lang.psi.JetFile
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.psi.JetElement
import org.jetbrains.jet.plugin.refactoring.getAllExtractionContainers
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.jet.lang.psi.psiUtil.getOutermostParentContainedIn
import org.jetbrains.jet.plugin.refactoring.checkConflictsInteractively
import org.jetbrains.jet.plugin.refactoring.executeWriteCommand
import org.jetbrains.jet.lang.psi.JetBlockExpression
import org.jetbrains.jet.lang.psi.JetClassBody
import kotlin.test.fail
import org.jetbrains.jet.lang.psi.JetDeclarationWithBody
import org.jetbrains.jet.plugin.refactoring.extractFunction.AnalysisResult.Status
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.MessageType
@@ -47,27 +44,10 @@ import javax.swing.event.HyperlinkEvent
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
import com.intellij.ui.awt.RelativePoint
import com.intellij.openapi.ui.popup.Balloon.Position
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.lang.psi.JetDeclaration
import java.util.Collections
import org.jetbrains.jet.lang.psi.JetProperty
import org.jetbrains.jet.lang.psi.JetParameterList
import org.jetbrains.jet.lang.psi.JetClassInitializer
import org.jetbrains.jet.lang.psi.JetFunctionLiteral
import com.intellij.ide.util.PsiElementListCellRenderer
import com.intellij.openapi.util.text.StringUtil
import javax.swing.Icon
import org.jetbrains.jet.plugin.refactoring.getPsiElementPopup
import com.intellij.psi.PsiNamedElement
import org.jetbrains.jet.plugin.util.collapseSpaces
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.lang.psi.JetPropertyAccessor
import org.jetbrains.jet.lang.psi.JetClassOrObject
import org.jetbrains.jet.plugin.util.psi.patternMatching.toRange
import org.jetbrains.jet.lang.psi.JetMultiDeclaration
import org.jetbrains.jet.plugin.refactoring.chooseContainerElementIfNecessary
import org.jetbrains.jet.plugin.refactoring.getExtractionContainers
public open class ExtractKotlinFunctionHandlerHelper {
open fun adjustExtractionData(data: ExtractionData): ExtractionData = data
@@ -212,108 +192,24 @@ fun selectElements(
continuation(elements, outermostParent)
}
fun getContainers(element: PsiElement, strict: Boolean): List<JetElement> {
if (allContainersEnabled) return element.getAllExtractionContainers(strict)
val declaration = element.getParentByType(javaClass<JetDeclaration>(), strict)?.let { declaration ->
stream(declaration) { it.getParentByType(javaClass<JetDeclaration>(), true) }.firstOrNull { it !is JetFunctionLiteral }
} ?: return Collections.emptyList()
val parent = declaration.getParent()?.let {
when (it) {
is JetProperty, is JetMultiDeclaration -> it.getParent()
is JetParameterList -> it.getParent()?.getParent()
else -> it
}
}
return when (parent) {
is JetFile -> Collections.singletonList(parent)
is JetClassBody -> {
element.getAllExtractionContainers(strict).filterIsInstance(javaClass<JetClassBody>())
}
else -> {
val enclosingDeclaration =
PsiTreeUtil.getNonStrictParentOfType(parent, javaClass<JetDeclarationWithBody>(), javaClass<JetClassInitializer>())
val targetContainer = when (enclosingDeclaration) {
is JetDeclarationWithBody -> enclosingDeclaration.getBodyExpression()
is JetClassInitializer -> enclosingDeclaration.getBody()
else -> null
}
if (targetContainer is JetBlockExpression) Collections.singletonList(targetContainer) else Collections.emptyList()
}
}
}
fun selectTargetContainer(elements: List<PsiElement>) {
val parent = PsiTreeUtil.findCommonParent(elements)
?: throw AssertionError("Should have at least one parent: ${elements.makeString("\n")}")
val containers = getContainers(parent, elements.size == 1)
val containers = parent.getExtractionContainers(elements.size == 1, allContainersEnabled)
if (containers.empty) {
noContainerError()
return
}
if (containers.size == 1 || ApplicationManager.getApplication()!!.isUnitTestMode()) {
onSelectionComplete(parent, elements, containers[0])
return
}
getPsiElementPopup(
chooseContainerElementIfNecessary(
containers,
editor,
containers.copyToArray(),
object: PsiElementListCellRenderer<JetElement>() {
private fun JetElement.renderName(): String? {
if (this is JetPropertyAccessor) {
return (getParent() as JetProperty).renderName() + if (isGetter()) ".get" else ".set"
}
return (this as? PsiNamedElement)?.getName() ?: "<anonymous>"
}
private fun JetElement.renderDeclaration(): String? {
val name = renderName()
val descriptor = AnalyzerFacadeWithCache.getContextForElement(this)[BindingContext.DECLARATION_TO_DESCRIPTOR, this]
val params = (descriptor as? FunctionDescriptor)?.let { descriptor ->
descriptor.getValueParameters()
.map { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it.getType()) }
.joinToString(", ", "(", ")")
} ?: ""
return "$name$params"
}
private fun JetElement.renderText(): String {
return StringUtil.shortenTextWithEllipsis(getText()!!.collapseSpaces(), 53, 0)
}
private fun JetElement.getRepresentativeElement(): JetElement {
return when (this) {
is JetBlockExpression -> (getParent() as? JetDeclarationWithBody) ?: this
is JetClassBody -> getParent() as JetClassOrObject
else -> this
}
}
override fun getElementText(element: JetElement): String? {
val representativeElement = element.getRepresentativeElement()
return when (representativeElement) {
is JetFile, is JetDeclarationWithBody, is JetClassOrObject -> representativeElement.renderDeclaration()
else -> representativeElement.renderText()
}
}
override fun getContainerText(element: JetElement?, name: String?): String? = null
override fun getIconFlags(): Int = 0
override fun getIcon(element: PsiElement?): Icon? =
super.getIcon((element as? JetElement)?.getRepresentativeElement())
},
"Select target code block",
{
onSelectionComplete(parent, elements, it)
true
}
).showInBestPositionFor(editor)
true,
{ it },
{ onSelectionComplete(parent, elements, it) }
)
}
fun selectMultipleExpressions() {
@@ -345,4 +241,4 @@ fun selectElements(
editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE)
selectSingleExpression()
}
}
@@ -62,16 +62,18 @@ import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.ui.components.JBList
import com.intellij.openapi.ui.popup.JBPopupAdapter
import com.intellij.openapi.ui.popup.LightweightWindowEvent
import com.intellij.openapi.editor.Document
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElementVisitor
import com.intellij.psi.PsiWhiteSpace
import com.intellij.openapi.util.Computable
import com.intellij.openapi.ui.DialogWrapper
import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.psi.psiUtil.getParentByType
import org.jetbrains.jet.lang.psi.psiUtil.isAncestor
import org.jetbrains.jet.plugin.caches.resolve.getLazyResolveSession
import com.intellij.psi.PsiNamedElement
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import org.jetbrains.jet.renderer.DescriptorRenderer
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.jet.plugin.util.collapseSpaces
import javax.swing.Icon
/**
* Replace [[JetSimpleNameExpression]] (and its enclosing qualifier) with qualified element given by FqName
@@ -157,7 +159,7 @@ public inline fun JetFile.createTempCopy(textTransform: (String) -> String): Jet
return tmpFile
}
public fun PsiElement.getAllExtractionContainers(strict: Boolean): List<JetElement> {
public fun PsiElement.getAllExtractionContainers(strict: Boolean = true): List<JetElement> {
val containers = ArrayList<JetElement>()
var element: PsiElement? = if (strict) getParent() else this
@@ -172,6 +174,38 @@ public fun PsiElement.getAllExtractionContainers(strict: Boolean): List<JetEleme
return containers
}
public fun PsiElement.getExtractionContainers(strict: Boolean = true, includeAll: Boolean = false): List<JetElement> {
if (includeAll) return getAllExtractionContainers(strict)
val declaration = getParentByType(javaClass<JetDeclaration>(), strict)?.let { declaration ->
stream(declaration) { it.getParentByType(javaClass<JetDeclaration>(), true) }.firstOrNull { it !is JetFunctionLiteral }
} ?: return Collections.emptyList()
val parent = declaration.getParent()?.let {
when (it) {
is JetProperty, is JetMultiDeclaration -> it.getParent()
is JetParameterList -> it.getParent()?.getParent()
else -> it
}
}
return when (parent) {
is JetFile -> Collections.singletonList(parent)
is JetClassBody -> {
getAllExtractionContainers(strict).filterIsInstance(javaClass<JetClassBody>())
}
else -> {
val enclosingDeclaration =
PsiTreeUtil.getNonStrictParentOfType(parent, javaClass<JetDeclarationWithBody>(), javaClass<JetClassInitializer>())
val targetContainer = when (enclosingDeclaration) {
is JetDeclarationWithBody -> enclosingDeclaration.getBodyExpression()
is JetClassInitializer -> enclosingDeclaration.getBody()
else -> null
}
if (targetContainer is JetBlockExpression) Collections.singletonList(targetContainer) else Collections.emptyList()
}
}
}
public fun Project.checkConflictsInteractively(conflicts: MultiMap<PsiElement, String>, onAccept: () -> Unit) {
if (!conflicts.isEmpty()) {
if (ApplicationManager.getApplication()!!.isUnitTestMode()) throw ConflictsInTestsException(conflicts.values())
@@ -202,21 +236,23 @@ public fun <T: Any> Project.executeWriteCommand(name: String, command: () -> T):
return result!!
}
public fun <T : PsiElement> getPsiElementPopup(
public fun <T, E: PsiElement> getPsiElementPopup(
editor: Editor,
elements: Array<T>,
renderer: PsiElementListCellRenderer<T>,
title: String? = null,
elements: List<T>,
renderer: PsiElementListCellRenderer<E>,
title: String?,
highlightSelection: Boolean,
toPsi: (T) -> E,
processor: (T) -> Boolean): JBPopup {
val highlighter = SelectionAwareScopeHighlighter(editor)
val highlighter = if (highlightSelection) SelectionAwareScopeHighlighter(editor) else null
val list = JBList(elements.toList())
val list = JBList(elements.map(toPsi))
list.setCellRenderer(renderer)
list.addListSelectionListener { e ->
highlighter.dropHighlight()
highlighter?.dropHighlight()
val index = list.getSelectedIndex()
if (index >= 0) {
highlighter.highlight(list.getModel()!!.getElementAt(index) as PsiElement)
highlighter?.highlight(list.getModel()!!.getElementAt(index) as PsiElement)
}
}
@@ -224,16 +260,14 @@ public fun <T : PsiElement> getPsiElementPopup(
title?.let { setTitle(it) }
renderer.installSpeedSearch(this, true)
setItemChoosenCallback {
for (element in list.getSelectedValues()) {
element.let {
[suppress("UNCHECKED_CAST")]
processor(it as T)
}
val index = list.getSelectedIndex()
if (index >= 0) {
processor(elements[index])
}
}
addListener(object: JBPopupAdapter() {
override fun onClosed(event: LightweightWindowEvent?) {
highlighter.dropHighlight();
highlighter?.dropHighlight();
}
})
@@ -303,4 +337,88 @@ public fun JetElement.getContextForContainingDeclarationBody(): BindingContext?
else -> null
}
return bodyElement?.let { getContainingJetFile().getLazyResolveSession().resolveToElement(it) }
}
public fun chooseContainerElement<T>(
containers: List<T>,
editor: Editor,
title: String,
highlightSelection: Boolean,
toPsi: (T) -> JetElement,
onSelect: (T) -> Unit) {
return getPsiElementPopup(
editor,
containers,
object : PsiElementListCellRenderer<JetElement>() {
private fun JetElement.renderName(): String {
if (this is JetPropertyAccessor) {
return (getParent() as JetProperty).renderName() + if (isGetter()) ".get" else ".set"
}
if (this is JetObjectDeclaration && this.isClassObject()) {
return "Class object of ${getParentByType(javaClass<JetClassOrObject>(), true)?.renderName() ?: "<anonymous>"}"
}
return (this as? PsiNamedElement)?.getName() ?: "<anonymous>"
}
private fun JetElement.renderDeclaration(): String? {
val name = renderName()
val descriptor = AnalyzerFacadeWithCache.getContextForElement(this)[BindingContext.DECLARATION_TO_DESCRIPTOR, this]
val params = (descriptor as? FunctionDescriptor)?.let { descriptor ->
descriptor.getValueParameters()
.map { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it.getType()) }
.joinToString(", ", "(", ")")
} ?: ""
return "$name$params"
}
private fun JetElement.renderText(): String {
return StringUtil.shortenTextWithEllipsis(getText()!!.collapseSpaces(), 53, 0)
}
private fun JetElement.getRepresentativeElement(): JetElement {
return when (this) {
is JetBlockExpression -> (getParent() as? JetDeclarationWithBody) ?: this
is JetClassBody -> getParent() as JetClassOrObject
else -> this
}
}
override fun getElementText(element: JetElement): String? {
val representativeElement = element.getRepresentativeElement()
return when (representativeElement) {
is JetFile, is JetDeclarationWithBody, is JetClassOrObject -> representativeElement.renderDeclaration()
else -> representativeElement.renderText()
}
}
override fun getContainerText(element: JetElement?, name: String?): String? = null
override fun getIconFlags(): Int = 0
override fun getIcon(element: PsiElement?): Icon? =
super.getIcon((element as? JetElement)?.getRepresentativeElement())
},
title,
highlightSelection,
toPsi,
{
onSelect(it)
true
}
).showInBestPositionFor(editor)
}
public fun chooseContainerElementIfNecessary<T>(
containers: List<T>,
editor: Editor,
title: String,
highlightSelection: Boolean,
toPsi: (T) -> JetElement,
onSelect: (T) -> Unit
) {
when {
containers.empty -> return
containers.size == 1 || ApplicationManager.getApplication()!!.isUnitTestMode() -> onSelect(containers.first())
else -> chooseContainerElement(containers, editor, title, highlightSelection, toPsi, onSelect)
}
}