182: generic PopupChooserBuilder<T> compilation fix
This commit is contained in:
committed by
Nikolay Krasko
parent
fdb98fd4ab
commit
1b783070b5
+261
@@ -0,0 +1,261 @@
|
||||
/*
|
||||
* 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.actions.generate
|
||||
|
||||
import com.intellij.codeInsight.generation.actions.GenerateActionPopupTemplateInjector
|
||||
import com.intellij.codeInsight.hint.HintManager
|
||||
import com.intellij.ide.fileTemplates.FileTemplateManager
|
||||
import com.intellij.ide.fileTemplates.impl.AllFileTemplatesConfigurable
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.InputValidator
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.ui.popup.PopupChooserBuilder
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiElementFactory
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.testIntegration.JavaTestFramework
|
||||
import com.intellij.testIntegration.TestFramework
|
||||
import com.intellij.testIntegration.TestIntegrationUtils.MethodKind
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.insertMember
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject.BodyType
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.generateUnsupportedOrSuperCall
|
||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.setupEditorSelection
|
||||
import org.jetbrains.kotlin.idea.refactoring.j2k
|
||||
import org.jetbrains.kotlin.idea.testIntegration.findSuitableFrameworks
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isIdentifier
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
||||
import org.jetbrains.kotlin.utils.ifEmpty
|
||||
|
||||
abstract class KotlinGenerateTestSupportActionBase(
|
||||
private val methodKind : MethodKind
|
||||
) : KotlinGenerateActionBase(), GenerateActionPopupTemplateInjector {
|
||||
companion object {
|
||||
private fun findTargetClass(editor: Editor, file: PsiFile): KtClassOrObject? {
|
||||
val elementAtCaret = file.findElementAt(editor.caretModel.offset) ?: return null
|
||||
return elementAtCaret.parentsWithSelf.filterIsInstance<KtClassOrObject>().firstOrNull { !it.isLocal }
|
||||
}
|
||||
|
||||
private fun chooseAndPerform(editor: Editor, frameworks: List<TestFramework>, consumer: (TestFramework) -> Unit) {
|
||||
frameworks.ifEmpty { return }
|
||||
frameworks.singleOrNull()?.let { return consumer(it) }
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) return consumer(frameworks.first())
|
||||
|
||||
val list = JBList<TestFramework>(*frameworks.toTypedArray())
|
||||
list.cellRenderer = TestFrameworkListCellRenderer()
|
||||
|
||||
PopupChooserBuilder<TestFramework>(list)
|
||||
.setFilteringEnabled { (it as TestFramework).name }
|
||||
.setTitle("Choose Framework")
|
||||
.setItemChoosenCallback { consumer(list.selectedValue as TestFramework) }
|
||||
.setMovable(true)
|
||||
.createPopup()
|
||||
.showInBestPositionFor(editor)
|
||||
}
|
||||
|
||||
private val BODY_VAR = "\${BODY}"
|
||||
private val NAME_VAR = "\${NAME}"
|
||||
|
||||
private val NAME_VALIDATOR = object : InputValidator {
|
||||
override fun checkInput(inputString: String) = inputString.quoteIfNeeded().isIdentifier()
|
||||
override fun canClose(inputString: String) = true
|
||||
}
|
||||
}
|
||||
|
||||
class SetUp : KotlinGenerateTestSupportActionBase(MethodKind.SET_UP) {
|
||||
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject): Boolean {
|
||||
return framework.findSetUpMethod(targetClass.toLightClass()!!) == null
|
||||
}
|
||||
}
|
||||
|
||||
class Test : KotlinGenerateTestSupportActionBase(MethodKind.TEST) {
|
||||
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject) = true
|
||||
}
|
||||
|
||||
class Data : KotlinGenerateTestSupportActionBase(MethodKind.DATA) {
|
||||
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject): Boolean {
|
||||
if (framework !is JavaTestFramework) return false
|
||||
return framework.findParametersMethod(targetClass.toLightClass()) == null
|
||||
}
|
||||
}
|
||||
|
||||
class TearDown : KotlinGenerateTestSupportActionBase(MethodKind.TEAR_DOWN) {
|
||||
override fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject): Boolean {
|
||||
return framework.findTearDownMethod(targetClass.toLightClass()!!) == null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getTargetClass(editor: Editor, file: PsiFile): KtClassOrObject? {
|
||||
return findTargetClass(editor, file)
|
||||
}
|
||||
|
||||
override fun isValidForClass(targetClass: KtClassOrObject): Boolean {
|
||||
return findSuitableFrameworks(targetClass).any { isApplicableTo(it, targetClass) }
|
||||
}
|
||||
|
||||
protected abstract fun isApplicableTo(framework: TestFramework, targetClass: KtClassOrObject): Boolean
|
||||
|
||||
override fun invoke(project: Project, editor: Editor, file: PsiFile) {
|
||||
val klass = findTargetClass(editor, file) ?: return
|
||||
|
||||
if (testFrameworkToUse != null) {
|
||||
val frameworkToUse = findSuitableFrameworks(klass).first { it.name == testFrameworkToUse }
|
||||
if (isApplicableTo(frameworkToUse, klass)) {
|
||||
doGenerate(editor, file, klass, frameworkToUse)
|
||||
}
|
||||
}
|
||||
else {
|
||||
val frameworks = findSuitableFrameworks(klass)
|
||||
.filter { methodKind.getFileTemplateDescriptor(it) != null && isApplicableTo(it, klass) }
|
||||
|
||||
chooseAndPerform(editor, frameworks) { doGenerate(editor, file, klass, it) }
|
||||
}
|
||||
}
|
||||
|
||||
var testFrameworkToUse: String? = null
|
||||
|
||||
private val DUMMY_NAME = "__KOTLIN_RULEZZZ__"
|
||||
|
||||
private fun doGenerate(editor: Editor, file: PsiFile, klass: KtClassOrObject, framework: TestFramework) {
|
||||
val project = file.project
|
||||
val commandName = "Generate test function"
|
||||
|
||||
val fileTemplateDescriptor = methodKind.getFileTemplateDescriptor(framework)
|
||||
val fileTemplate = FileTemplateManager.getInstance(project).getCodeTemplate(fileTemplateDescriptor.fileName)
|
||||
var templateText = fileTemplate.text.replace(BODY_VAR, "")
|
||||
var name: String? = null
|
||||
if (templateText.contains(NAME_VAR)) {
|
||||
name = if (templateText.contains("test$NAME_VAR")) "Name" else "name"
|
||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||
name = Messages.showInputDialog("Choose test name: ", commandName, null, name, NAME_VALIDATOR)
|
||||
?: return
|
||||
}
|
||||
|
||||
templateText = fileTemplate.text.replace(NAME_VAR, DUMMY_NAME)
|
||||
}
|
||||
|
||||
try {
|
||||
var errorHint: String? = null
|
||||
project.executeWriteCommand(commandName) {
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
|
||||
val factory = PsiElementFactory.SERVICE.getInstance(project)
|
||||
val psiMethod = factory.createMethodFromText(templateText, null)
|
||||
psiMethod.throwsList.referenceElements.forEach { it.delete() }
|
||||
var function = psiMethod.j2k() as? KtNamedFunction ?: run {
|
||||
errorHint = "Couldn't convert Java template to Kotlin"
|
||||
return@executeWriteCommand
|
||||
}
|
||||
name?.let {
|
||||
function = substituteNewName(function, it)
|
||||
}
|
||||
val functionInPlace = insertMember(editor, klass, function)
|
||||
|
||||
val functionDescriptor = functionInPlace.unsafeResolveToDescriptor() as FunctionDescriptor
|
||||
val overriddenDescriptors = functionDescriptor.overriddenDescriptors
|
||||
val bodyText = when (overriddenDescriptors.size) {
|
||||
0 -> generateUnsupportedOrSuperCall(project, functionDescriptor, BodyType.EMPTY)
|
||||
1 -> generateUnsupportedOrSuperCall(project, overriddenDescriptors.single(), BodyType.SUPER)
|
||||
else -> generateUnsupportedOrSuperCall(project, overriddenDescriptors.first(), BodyType.QUALIFIED_SUPER)
|
||||
}
|
||||
functionInPlace.bodyExpression?.delete()
|
||||
functionInPlace.add(KtPsiFactory(project).createBlock(bodyText))
|
||||
|
||||
if (overriddenDescriptors.isNotEmpty()) {
|
||||
functionInPlace.addModifier(KtTokens.OVERRIDE_KEYWORD)
|
||||
}
|
||||
|
||||
setupEditorSelection(editor, functionInPlace)
|
||||
}
|
||||
errorHint?.let { HintManager.getInstance().showErrorHint(editor, it) }
|
||||
}
|
||||
catch (e: IncorrectOperationException) {
|
||||
HintManager.getInstance().showErrorHint(editor, "Cannot generate method: " + e.message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun substituteNewName(function: KtNamedFunction, name: String): KtNamedFunction {
|
||||
val psiFactory = KtPsiFactory(function)
|
||||
|
||||
// First replace all DUMMY_NAME occurrences in names as they need special treatment due to quotation
|
||||
var function1 = function
|
||||
function1.accept(
|
||||
object : KtTreeVisitorVoid() {
|
||||
private fun getNewId(currentId: String): String? {
|
||||
if (!currentId.contains(DUMMY_NAME)) return null
|
||||
return currentId.replace(DUMMY_NAME, name).quoteIfNeeded()
|
||||
}
|
||||
|
||||
override fun visitNamedDeclaration(declaration: KtNamedDeclaration) {
|
||||
val nameIdentifier = declaration.nameIdentifier ?: return
|
||||
val newId = getNewId(nameIdentifier.text) ?: return
|
||||
declaration.setName(newId)
|
||||
}
|
||||
|
||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||
val newId = getNewId(expression.text) ?: return
|
||||
expression.replace(psiFactory.createSimpleName(newId))
|
||||
}
|
||||
}
|
||||
)
|
||||
// Then text-replace remaining occurrences (if any)
|
||||
val functionText = function1.text
|
||||
if (functionText.contains(DUMMY_NAME)) {
|
||||
function1 = psiFactory.createFunction(function1.text.replace(DUMMY_NAME, name))
|
||||
}
|
||||
return function1
|
||||
}
|
||||
|
||||
override fun createEditTemplateAction(dataContext: DataContext): AnAction? {
|
||||
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return null
|
||||
val editor = CommonDataKeys.EDITOR.getData(dataContext) ?: return null
|
||||
val file = CommonDataKeys.PSI_FILE.getData(dataContext) ?: return null
|
||||
|
||||
val targetClass = getTargetClass(editor, file) ?: return null
|
||||
val frameworks = findSuitableFrameworks(targetClass).ifEmpty { return null }
|
||||
|
||||
return object : AnAction("Edit Template") {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
chooseAndPerform(editor, frameworks) {
|
||||
val descriptor = methodKind.getFileTemplateDescriptor(it)
|
||||
if (descriptor == null) {
|
||||
HintManager.getInstance().showErrorHint(editor, "No template found for ${it.name}:${templatePresentation.text}")
|
||||
return@chooseAndPerform
|
||||
}
|
||||
|
||||
AllFileTemplatesConfigurable.editCodeTemplate(FileUtil.getNameWithoutExtension(descriptor.fileName), project)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.intentions
|
||||
|
||||
import com.intellij.codeInsight.CodeInsightBundle
|
||||
import com.intellij.codeInsight.FileModificationService
|
||||
import com.intellij.codeInsight.generation.OverrideImplementUtil
|
||||
import com.intellij.ide.util.PsiClassListCellRenderer
|
||||
import com.intellij.ide.util.PsiElementListCellRenderer
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.fileEditor.OpenFileDescriptor
|
||||
import com.intellij.openapi.ui.popup.PopupChooserBuilder
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiClass
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideImplementMembersHandler
|
||||
import org.jetbrains.kotlin.idea.core.overrideImplement.OverrideMemberChooserObject
|
||||
import org.jetbrains.kotlin.idea.refactoring.isAbstract
|
||||
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
|
||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
|
||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
|
||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.types.TypeSubstitutor
|
||||
import org.jetbrains.kotlin.types.substitutions.getTypeSubstitutor
|
||||
import org.jetbrains.kotlin.util.findCallableMemberBySignature
|
||||
import java.util.*
|
||||
import javax.swing.ListSelectionModel
|
||||
|
||||
abstract class ImplementAbstractMemberIntentionBase :
|
||||
SelfTargetingRangeIntention<KtNamedDeclaration>(KtNamedDeclaration::class.java, "", "Implement abstract member") {
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance("#${ImplementAbstractMemberIntentionBase::class.java.canonicalName}")
|
||||
}
|
||||
|
||||
protected fun findExistingImplementation(
|
||||
subClass: ClassDescriptor,
|
||||
superMember: CallableMemberDescriptor
|
||||
): CallableMemberDescriptor? {
|
||||
val superClass = superMember.containingDeclaration as? ClassDescriptor ?: return null
|
||||
val substitutor = getTypeSubstitutor(superClass.defaultType, subClass.defaultType) ?: TypeSubstitutor.EMPTY
|
||||
val signatureInSubClass = superMember.substitute(substitutor) as? CallableMemberDescriptor ?: return null
|
||||
val subMember = subClass.findCallableMemberBySignature(signatureInSubClass)
|
||||
return if (subMember?.kind?.isReal == true) subMember else null
|
||||
}
|
||||
|
||||
protected abstract fun acceptSubClass(subClassDescriptor: ClassDescriptor, memberDescriptor: CallableMemberDescriptor): Boolean
|
||||
|
||||
private fun findClassesToProcess(member: KtNamedDeclaration): Sequence<PsiElement> {
|
||||
val baseClass = member.containingClassOrObject as? KtClass ?: return emptySequence()
|
||||
val memberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return emptySequence()
|
||||
|
||||
fun acceptSubClass(subClass: PsiElement): Boolean {
|
||||
val classDescriptor = when (subClass) {
|
||||
is KtLightClass -> subClass.kotlinOrigin?.resolveToDescriptorIfAny()
|
||||
is KtEnumEntry -> subClass.resolveToDescriptorIfAny()
|
||||
is PsiClass -> subClass.getJavaClassDescriptor()
|
||||
else -> null
|
||||
} ?: return false
|
||||
return acceptSubClass(classDescriptor, memberDescriptor)
|
||||
}
|
||||
|
||||
if (baseClass.isEnum()) {
|
||||
return baseClass.declarations
|
||||
.asSequence()
|
||||
.filterIsInstance<KtEnumEntry>()
|
||||
.filter(::acceptSubClass)
|
||||
}
|
||||
|
||||
return HierarchySearchRequest(baseClass, baseClass.useScope, false)
|
||||
.searchInheritors()
|
||||
.asSequence()
|
||||
.filter(::acceptSubClass)
|
||||
}
|
||||
|
||||
protected abstract fun computeText(element: KtNamedDeclaration): String?
|
||||
|
||||
override fun applicabilityRange(element: KtNamedDeclaration): TextRange? {
|
||||
if (!element.isAbstract()) return null
|
||||
|
||||
text = computeText(element) ?: return null
|
||||
|
||||
if (!findClassesToProcess(element).any()) return null
|
||||
|
||||
return element.nameIdentifier?.textRange
|
||||
}
|
||||
|
||||
protected abstract val preferConstructorParameters: Boolean
|
||||
|
||||
private fun implementInKotlinClass(editor: Editor?, member: KtNamedDeclaration, targetClass: KtClassOrObject) {
|
||||
val subClassDescriptor = targetClass.resolveToDescriptorIfAny() ?: return
|
||||
val superMemberDescriptor = member.resolveToDescriptorIfAny() as? CallableMemberDescriptor ?: return
|
||||
val superClassDescriptor = superMemberDescriptor.containingDeclaration as? ClassDescriptor ?: return
|
||||
val substitutor = getTypeSubstitutor(superClassDescriptor.defaultType, subClassDescriptor.defaultType)
|
||||
?: TypeSubstitutor.EMPTY
|
||||
val descriptorToImplement = superMemberDescriptor.substitute(substitutor) as CallableMemberDescriptor
|
||||
val chooserObject = OverrideMemberChooserObject.create(member.project,
|
||||
descriptorToImplement,
|
||||
descriptorToImplement,
|
||||
OverrideMemberChooserObject.BodyType.EMPTY,
|
||||
preferConstructorParameters)
|
||||
OverrideImplementMembersHandler.generateMembers(editor, targetClass, listOf(chooserObject), false)
|
||||
}
|
||||
|
||||
private fun implementInJavaClass(member: KtNamedDeclaration, targetClass: PsiClass) {
|
||||
member.toLightMethods().forEach { OverrideImplementUtil.overrideOrImplement(targetClass, it) }
|
||||
}
|
||||
|
||||
private fun implementInClass(member: KtNamedDeclaration, targetClasses: List<PsiElement>) {
|
||||
val project = member.project
|
||||
project.executeCommand(CodeInsightBundle.message("intention.implement.abstract.method.command.name")) {
|
||||
if (!FileModificationService.getInstance().preparePsiElementsForWrite(targetClasses)) return@executeCommand
|
||||
runWriteAction {
|
||||
for (targetClass in targetClasses) {
|
||||
try {
|
||||
val descriptor = OpenFileDescriptor(project, targetClass.containingFile.virtualFile)
|
||||
val targetEditor = FileEditorManager.getInstance(project).openTextEditor(descriptor, true)!!
|
||||
when (targetClass) {
|
||||
is KtLightClass -> targetClass.kotlinOrigin?.let { implementInKotlinClass(targetEditor, member, it) }
|
||||
is KtEnumEntry -> implementInKotlinClass(targetEditor, member, targetClass)
|
||||
is PsiClass -> implementInJavaClass(member, targetClass)
|
||||
}
|
||||
}
|
||||
catch(e: IncorrectOperationException) {
|
||||
LOG.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ClassRenderer : PsiElementListCellRenderer<PsiElement>() {
|
||||
private val psiClassRenderer = PsiClassListCellRenderer()
|
||||
|
||||
override fun getComparator(): Comparator<PsiElement> {
|
||||
val baseComparator = psiClassRenderer.comparator
|
||||
return Comparator { o1, o2 ->
|
||||
when {
|
||||
o1 is KtEnumEntry && o2 is KtEnumEntry -> o1.name!!.compareTo(o2.name!!)
|
||||
o1 is KtEnumEntry -> -1
|
||||
o2 is KtEnumEntry -> 1
|
||||
o1 is PsiClass && o2 is PsiClass -> baseComparator.compare(o1, o2)
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getIconFlags() = 0
|
||||
|
||||
override fun getElementText(element: PsiElement?): String? {
|
||||
return when (element) {
|
||||
is KtEnumEntry -> element.name
|
||||
is PsiClass -> psiClassRenderer.getElementText(element)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getContainerText(element: PsiElement?, name: String?): String? {
|
||||
return when (element) {
|
||||
is KtEnumEntry -> element.containingClassOrObject?.fqName?.asString()
|
||||
is PsiClass -> PsiClassListCellRenderer.getContainerTextStatic(element)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun applyTo(element: KtNamedDeclaration, editor: Editor?) {
|
||||
if (editor == null) throw IllegalArgumentException("This intention requires an editor")
|
||||
val project = element.project
|
||||
|
||||
val classesToProcess = project.runSynchronouslyWithProgress(
|
||||
CodeInsightBundle.message("intention.implement.abstract.method.searching.for.descendants.progress"),
|
||||
true
|
||||
) { findClassesToProcess(element).toList() } ?: return
|
||||
if (classesToProcess.isEmpty()) return
|
||||
|
||||
classesToProcess.singleOrNull()?.let { return implementInClass(element, listOf(it)) }
|
||||
|
||||
val renderer = ClassRenderer()
|
||||
val sortedClasses = classesToProcess.sortedWith(renderer.comparator)
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) return implementInClass(element, sortedClasses)
|
||||
|
||||
val list = JBList(sortedClasses).apply {
|
||||
selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
|
||||
cellRenderer = renderer
|
||||
}
|
||||
val builder = PopupChooserBuilder<PsiElement>(list)
|
||||
renderer.installSpeedSearch(builder)
|
||||
builder
|
||||
.setTitle(CodeInsightBundle.message("intention.implement.abstract.method.class.chooser.title"))
|
||||
.setItemChoosenCallback {
|
||||
val index = list.selectedIndex
|
||||
if (index < 0) return@setItemChoosenCallback
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
implementInClass(element, list.selectedValues.toList() as List<KtClassOrObject>)
|
||||
}
|
||||
.createPopup()
|
||||
.showInBestPositionFor(editor)
|
||||
}
|
||||
}
|
||||
|
||||
class ImplementAbstractMemberIntention : ImplementAbstractMemberIntentionBase() {
|
||||
override fun computeText(element: KtNamedDeclaration): String? {
|
||||
return when(element) {
|
||||
is KtProperty -> "Implement abstract property"
|
||||
is KtNamedFunction -> "Implement abstract function"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun acceptSubClass(subClassDescriptor: ClassDescriptor, memberDescriptor: CallableMemberDescriptor): Boolean {
|
||||
return subClassDescriptor.kind != ClassKind.INTERFACE && findExistingImplementation(subClassDescriptor, memberDescriptor) == null
|
||||
}
|
||||
|
||||
override val preferConstructorParameters: Boolean
|
||||
get() = false
|
||||
}
|
||||
|
||||
class ImplementAbstractMemberAsConstructorParameterIntention : ImplementAbstractMemberIntentionBase() {
|
||||
override fun computeText(element: KtNamedDeclaration): String? {
|
||||
if (element !is KtProperty) return null
|
||||
return "Implement as constructor parameter"
|
||||
}
|
||||
|
||||
override fun acceptSubClass(subClassDescriptor: ClassDescriptor, memberDescriptor: CallableMemberDescriptor): Boolean {
|
||||
val kind = subClassDescriptor.kind
|
||||
return (kind == ClassKind.CLASS || kind == ClassKind.ENUM_CLASS)
|
||||
&& subClassDescriptor !is JavaClassDescriptor
|
||||
&& findExistingImplementation(subClassDescriptor, memberDescriptor) == null
|
||||
}
|
||||
|
||||
override val preferConstructorParameters: Boolean
|
||||
get() = true
|
||||
|
||||
override fun applicabilityRange(element: KtNamedDeclaration): TextRange? {
|
||||
if (element !is KtProperty) return null
|
||||
return super.applicabilityRange(element)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,993 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||
* that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.refactoring
|
||||
|
||||
import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageUtils
|
||||
import com.intellij.codeInsight.unwrap.RangeSplitter
|
||||
import com.intellij.codeInsight.unwrap.UnwrapHandler
|
||||
import com.intellij.ide.IdeBundle
|
||||
import com.intellij.ide.util.PsiElementListCellRenderer
|
||||
import com.intellij.lang.injection.InjectedLanguageManager
|
||||
import com.intellij.lang.java.JavaLanguage
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.command.CommandAdapter
|
||||
import com.intellij.openapi.command.CommandEvent
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.colors.EditorColors
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||
import com.intellij.openapi.editor.markup.TextAttributes
|
||||
import com.intellij.openapi.options.ConfigurationException
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.JavaProjectRootsUtil
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.ui.popup.*
|
||||
import com.intellij.openapi.util.Pass
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.LocalFileSystem
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.impl.light.LightElement
|
||||
import com.intellij.psi.presentation.java.SymbolPresentationUtil
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor.ConflictsInTestsException
|
||||
import com.intellij.refactoring.changeSignature.ChangeSignatureUtil
|
||||
import com.intellij.refactoring.listeners.RefactoringEventData
|
||||
import com.intellij.refactoring.listeners.RefactoringEventListener
|
||||
import com.intellij.refactoring.ui.ConflictsDialog
|
||||
import com.intellij.refactoring.util.ConflictsUtil
|
||||
import com.intellij.refactoring.util.RefactoringUIUtil
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.usageView.UsageViewTypeLocation
|
||||
import com.intellij.util.VisibilityUtil
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.kotlin.asJava.LightClassUtil
|
||||
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
|
||||
import org.jetbrains.kotlin.asJava.getAccessorLightMethods
|
||||
import org.jetbrains.kotlin.asJava.namedUnwrappedElement
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaMemberDescriptor
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.core.*
|
||||
import org.jetbrains.kotlin.idea.core.util.showYesNoCancelDialog
|
||||
import org.jetbrains.kotlin.idea.j2k.IdeaJavaToKotlinServices
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar
|
||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
|
||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.actualsForExpected
|
||||
import org.jetbrains.kotlin.idea.util.liftToExpected
|
||||
import org.jetbrains.kotlin.idea.util.string.collapseSpaces
|
||||
import org.jetbrains.kotlin.j2k.ConverterSettings
|
||||
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.resolve.*
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getCallWithAssert
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import java.io.File
|
||||
import java.lang.annotation.Retention
|
||||
import java.util.*
|
||||
import javax.swing.Icon
|
||||
|
||||
val CHECK_SUPER_METHODS_YES_NO_DIALOG = "CHECK_SUPER_METHODS_YES_NO_DIALOG"
|
||||
|
||||
@JvmOverloads
|
||||
fun getOrCreateKotlinFile(fileName: String,
|
||||
targetDir: PsiDirectory,
|
||||
packageName: String? = targetDir.getPackage()?.qualifiedName): KtFile? =
|
||||
(targetDir.findFile(fileName) ?: createKotlinFile(fileName, targetDir, packageName)) as? KtFile
|
||||
|
||||
fun createKotlinFile(fileName: String,
|
||||
targetDir: PsiDirectory,
|
||||
packageName: String? = targetDir.getPackage()?.qualifiedName): KtFile {
|
||||
targetDir.checkCreateFile(fileName)
|
||||
val packageFqName = packageName?.let(::FqName) ?: FqName.ROOT
|
||||
val file = PsiFileFactory.getInstance(targetDir.project).createFileFromText(
|
||||
fileName, KotlinFileType.INSTANCE, if (!packageFqName.isRoot) "package ${packageFqName.quoteSegmentsIfNeeded()} \n\n" else ""
|
||||
)
|
||||
|
||||
return targetDir.add(file) as KtFile
|
||||
}
|
||||
|
||||
fun File.toVirtualFile(): VirtualFile? = LocalFileSystem.getInstance().findFileByIoFile(this)
|
||||
|
||||
fun File.toPsiFile(project: Project): PsiFile? = toVirtualFile()?.toPsiFile(project)
|
||||
|
||||
fun File.toPsiDirectory(project: Project): PsiDirectory? {
|
||||
return toVirtualFile()?.let { vfile -> PsiManager.getInstance(project).findDirectory(vfile) }
|
||||
}
|
||||
|
||||
fun VirtualFile.toPsiFile(project: Project): PsiFile? = PsiManager.getInstance(project).findFile(this)
|
||||
|
||||
fun VirtualFile.toPsiDirectory(project: Project): PsiDirectory? = PsiManager.getInstance(project).findDirectory(this)
|
||||
|
||||
fun VirtualFile.toPsiFileOrDirectory(project: Project): PsiFileSystemItem? = if (isDirectory) toPsiDirectory(project) else toPsiFile(project)
|
||||
|
||||
fun PsiElement.getUsageContext(): PsiElement {
|
||||
return when (this) {
|
||||
is KtElement -> PsiTreeUtil.getParentOfType(
|
||||
this,
|
||||
KtPropertyAccessor::class.java,
|
||||
KtProperty::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtConstructor::class.java,
|
||||
KtClassOrObject::class.java
|
||||
) ?: containingFile
|
||||
else -> ConflictsUtil.getContainer(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun PsiElement.isInJavaSourceRoot(): Boolean =
|
||||
!JavaProjectRootsUtil.isOutsideJavaSourceRoot(containingFile)
|
||||
|
||||
fun KtFile.createTempCopy(text: String? = null): KtFile {
|
||||
val tmpFile = KtPsiFactory(this).createAnalyzableFile(name, text ?: this.text ?: "", this)
|
||||
tmpFile.originalFile = this
|
||||
tmpFile.suppressDiagnosticsInDebugMode = suppressDiagnosticsInDebugMode
|
||||
return tmpFile
|
||||
}
|
||||
|
||||
fun PsiElement.getAllExtractionContainers(strict: Boolean = true): List<KtElement> {
|
||||
val containers = ArrayList<KtElement>()
|
||||
|
||||
var objectOrNonInnerNestedClassFound = false
|
||||
val parents = if (strict) parents else parentsWithSelf
|
||||
for (element in parents) {
|
||||
val isValidContainer = when (element) {
|
||||
is KtFile -> true
|
||||
is KtClassBody -> !objectOrNonInnerNestedClassFound || element.parent is KtObjectDeclaration
|
||||
is KtBlockExpression -> !objectOrNonInnerNestedClassFound
|
||||
else -> false
|
||||
}
|
||||
if (!isValidContainer) continue
|
||||
|
||||
containers.add(element as KtElement)
|
||||
|
||||
if (!objectOrNonInnerNestedClassFound) {
|
||||
val bodyParent = (element as? KtClassBody)?.parent
|
||||
objectOrNonInnerNestedClassFound =
|
||||
(bodyParent is KtObjectDeclaration && !bodyParent.isObjectLiteral())
|
||||
|| (bodyParent is KtClass && !bodyParent.isInner())
|
||||
}
|
||||
}
|
||||
|
||||
return containers
|
||||
}
|
||||
|
||||
fun PsiElement.getExtractionContainers(strict: Boolean = true, includeAll: Boolean = false): List<KtElement> {
|
||||
fun getEnclosingDeclaration(element: PsiElement, strict: Boolean): PsiElement? {
|
||||
return (if (strict) element.parents else element.parentsWithSelf)
|
||||
.filter {
|
||||
(it is KtDeclarationWithBody && it !is KtFunctionLiteral && !(it is KtNamedFunction && it.name == null))
|
||||
|| it is KtAnonymousInitializer
|
||||
|| it is KtClassBody
|
||||
|| it is KtFile
|
||||
}
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
if (includeAll) return getAllExtractionContainers(strict)
|
||||
|
||||
val enclosingDeclaration = getEnclosingDeclaration(this, strict)?.let {
|
||||
if (it is KtDeclarationWithBody || it is KtAnonymousInitializer) getEnclosingDeclaration(it, true) else it
|
||||
}
|
||||
|
||||
return when (enclosingDeclaration) {
|
||||
is KtFile -> Collections.singletonList(enclosingDeclaration)
|
||||
is KtClassBody -> getAllExtractionContainers(strict).filterIsInstance<KtClassBody>()
|
||||
else -> {
|
||||
val targetContainer = when (enclosingDeclaration) {
|
||||
is KtDeclarationWithBody -> enclosingDeclaration.bodyExpression
|
||||
is KtAnonymousInitializer -> enclosingDeclaration.body
|
||||
else -> null
|
||||
}
|
||||
if (targetContainer is KtBlockExpression) Collections.singletonList(targetContainer) else Collections.emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.checkConflictsInteractively(
|
||||
conflicts: MultiMap<PsiElement, String>,
|
||||
onShowConflicts: () -> Unit = {},
|
||||
onAccept: () -> Unit) {
|
||||
if (!conflicts.isEmpty) {
|
||||
if (ApplicationManager.getApplication()!!.isUnitTestMode) throw ConflictsInTestsException(conflicts.values())
|
||||
|
||||
val dialog = ConflictsDialog(this, conflicts) { onAccept() }
|
||||
dialog.show()
|
||||
if (!dialog.isOK) {
|
||||
if (dialog.isShowConflicts) {
|
||||
onShowConflicts()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
onAccept()
|
||||
}
|
||||
|
||||
fun reportDeclarationConflict(
|
||||
conflicts: MultiMap<PsiElement, String>,
|
||||
declaration: PsiElement,
|
||||
message: (renderedDeclaration: String) -> String
|
||||
) {
|
||||
conflicts.putValue(declaration, message(RefactoringUIUtil.getDescription(declaration, true).capitalize()))
|
||||
}
|
||||
|
||||
fun <T, E : PsiElement> getPsiElementPopup(
|
||||
editor: Editor,
|
||||
elements: List<T>,
|
||||
renderer: PsiElementListCellRenderer<E>,
|
||||
title: String?,
|
||||
highlightSelection: Boolean,
|
||||
toPsi: (T) -> E,
|
||||
processor: (T) -> Boolean): JBPopup {
|
||||
val highlighter = if (highlightSelection) SelectionAwareScopeHighlighter(editor) else null
|
||||
|
||||
val list = JBList(elements.map(toPsi))
|
||||
list.cellRenderer = renderer
|
||||
list.addListSelectionListener {
|
||||
highlighter?.dropHighlight()
|
||||
val index = list.selectedIndex
|
||||
if (index >= 0) {
|
||||
highlighter?.highlight(list.model!!.getElementAt(index) as PsiElement)
|
||||
}
|
||||
}
|
||||
|
||||
return with(PopupChooserBuilder<E>(list)) {
|
||||
title?.let { setTitle(it) }
|
||||
renderer.installSpeedSearch(this, true)
|
||||
setItemChoosenCallback {
|
||||
val index = list.selectedIndex
|
||||
if (index >= 0) {
|
||||
processor(elements[index])
|
||||
}
|
||||
}
|
||||
addListener(object : JBPopupAdapter() {
|
||||
override fun onClosed(event: LightweightWindowEvent?) {
|
||||
highlighter?.dropHighlight()
|
||||
}
|
||||
})
|
||||
|
||||
createPopup()
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionAwareScopeHighlighter(val editor: Editor) {
|
||||
private val highlighters = ArrayList<RangeHighlighter>()
|
||||
|
||||
private fun addHighlighter(r: TextRange, attr: TextAttributes) {
|
||||
highlighters.add(
|
||||
editor.markupModel.addRangeHighlighter(
|
||||
r.startOffset,
|
||||
r.endOffset,
|
||||
UnwrapHandler.HIGHLIGHTER_LEVEL,
|
||||
attr,
|
||||
HighlighterTargetArea.EXACT_RANGE
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
fun highlight(wholeAffected: PsiElement) {
|
||||
dropHighlight()
|
||||
|
||||
val affectedRange = wholeAffected.textRange ?: return
|
||||
|
||||
val attributes = EditorColorsManager.getInstance().globalScheme.getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES)!!
|
||||
val selectedRange = with(editor.selectionModel) { TextRange(selectionStart, selectionEnd) }
|
||||
for (r in RangeSplitter.split(affectedRange, Collections.singletonList(selectedRange))) {
|
||||
addHighlighter(r, attributes)
|
||||
}
|
||||
}
|
||||
|
||||
fun dropHighlight() {
|
||||
highlighters.forEach { it.dispose() }
|
||||
highlighters.clear()
|
||||
}
|
||||
}
|
||||
|
||||
fun PsiFile.getLineStartOffset(line: Int): Int? {
|
||||
val doc = PsiDocumentManager.getInstance(project).getDocument(this)
|
||||
if (doc != null && line >= 0 && line < doc.lineCount) {
|
||||
val startOffset = doc.getLineStartOffset(line)
|
||||
val element = findElementAt(startOffset) ?: return startOffset
|
||||
|
||||
if (element is PsiWhiteSpace || element is PsiComment) {
|
||||
return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace::class.java, PsiComment::class.java)?.startOffset ?: startOffset
|
||||
}
|
||||
return startOffset
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun PsiFile.getLineEndOffset(line: Int): Int? {
|
||||
return PsiDocumentManager.getInstance(project).getDocument(this)?.getLineEndOffset(line)
|
||||
}
|
||||
|
||||
fun PsiElement.getLineNumber(start: Boolean = true): Int {
|
||||
return PsiDocumentManager.getInstance(project).getDocument(this.containingFile)?.getLineNumber(if (start) this.startOffset else this.endOffset) ?: 0
|
||||
}
|
||||
|
||||
fun PsiElement.getLineCount(): Int {
|
||||
val doc = containingFile?.let { file -> PsiDocumentManager.getInstance(project).getDocument(file) }
|
||||
if (doc != null) {
|
||||
val spaceRange = textRange ?: TextRange.EMPTY_RANGE
|
||||
|
||||
if (spaceRange.endOffset <= doc.textLength) {
|
||||
val startLine = doc.getLineNumber(spaceRange.startOffset)
|
||||
val endLine = doc.getLineNumber(spaceRange.endOffset)
|
||||
|
||||
return endLine - startLine
|
||||
}
|
||||
}
|
||||
|
||||
return (text ?: "").count { it == '\n' } + 1
|
||||
}
|
||||
|
||||
fun PsiElement.isMultiLine(): Boolean = getLineCount() > 1
|
||||
|
||||
class SeparateFileWrapper(manager: PsiManager) : LightElement(manager, KotlinLanguage.INSTANCE) {
|
||||
override fun toString() = ""
|
||||
}
|
||||
|
||||
fun <T> chooseContainerElement(
|
||||
containers: List<T>,
|
||||
editor: Editor,
|
||||
title: String,
|
||||
highlightSelection: Boolean,
|
||||
toPsi: (T) -> PsiElement,
|
||||
onSelect: (T) -> Unit) {
|
||||
return getPsiElementPopup(
|
||||
editor,
|
||||
containers,
|
||||
object : PsiElementListCellRenderer<PsiElement>() {
|
||||
private fun PsiElement.renderName(): String {
|
||||
if (this is KtPropertyAccessor) {
|
||||
return property.renderName() + if (isGetter) ".get" else ".set"
|
||||
}
|
||||
if (this is KtObjectDeclaration && this.isCompanion()) {
|
||||
return "Companion object of ${getStrictParentOfType<KtClassOrObject>()?.renderName() ?: "<anonymous>"}"
|
||||
}
|
||||
return (this as? PsiNamedElement)?.name ?: "<anonymous>"
|
||||
}
|
||||
|
||||
private fun PsiElement.renderDeclaration(): String? {
|
||||
if (this is KtFunctionLiteral || isFunctionalExpression()) return renderText()
|
||||
|
||||
val descriptor = when {
|
||||
this is KtFile -> name
|
||||
this is KtElement -> analyze()[BindingContext.DECLARATION_TO_DESCRIPTOR, this]
|
||||
this is PsiMember -> getJavaMemberDescriptor()
|
||||
else -> null
|
||||
} ?: return null
|
||||
val name = renderName()
|
||||
val params = (descriptor as? FunctionDescriptor)?.valueParameters
|
||||
?.map { DescriptorRenderer.Companion.SHORT_NAMES_IN_TYPES.renderType(it.type) }
|
||||
?.joinToString(", ", "(", ")") ?: ""
|
||||
return "$name$params"
|
||||
}
|
||||
|
||||
private fun PsiElement.renderText(): String {
|
||||
if (this is SeparateFileWrapper) return "Extract to separate file"
|
||||
return StringUtil.shortenTextWithEllipsis(text!!.collapseSpaces(), 53, 0)
|
||||
}
|
||||
|
||||
private fun PsiElement.getRepresentativeElement(): PsiElement {
|
||||
return when (this) {
|
||||
is KtBlockExpression -> (parent as? KtDeclarationWithBody) ?: this
|
||||
is KtClassBody -> parent as KtClassOrObject
|
||||
else -> this
|
||||
}
|
||||
}
|
||||
|
||||
override fun getElementText(element: PsiElement): String? {
|
||||
val representativeElement = element.getRepresentativeElement()
|
||||
return representativeElement.renderDeclaration() ?: representativeElement.renderText()
|
||||
}
|
||||
|
||||
override fun getContainerText(element: PsiElement, name: String?): String? = null
|
||||
|
||||
override fun getIconFlags(): Int = 0
|
||||
|
||||
override fun getIcon(element: PsiElement): Icon? =
|
||||
super.getIcon(element.getRepresentativeElement())
|
||||
},
|
||||
title,
|
||||
highlightSelection,
|
||||
toPsi,
|
||||
{
|
||||
onSelect(it)
|
||||
true
|
||||
}
|
||||
).showInBestPositionFor(editor)
|
||||
}
|
||||
|
||||
fun <T> chooseContainerElementIfNecessary(
|
||||
containers: List<T>,
|
||||
editor: Editor,
|
||||
title: String,
|
||||
highlightSelection: Boolean,
|
||||
toPsi: (T) -> PsiElement,
|
||||
onSelect: (T) -> Unit
|
||||
) {
|
||||
when {
|
||||
containers.isEmpty() -> return
|
||||
containers.size == 1 || ApplicationManager.getApplication()!!.isUnitTestMode -> onSelect(containers.first())
|
||||
else -> chooseContainerElement(containers, editor, title, highlightSelection, toPsi, onSelect)
|
||||
}
|
||||
}
|
||||
|
||||
fun PsiElement.isTrueJavaMethod(): Boolean = this is PsiMethod && this !is KtLightMethod
|
||||
|
||||
fun PsiElement.canRefactor(): Boolean {
|
||||
if (!this.isValid) return false
|
||||
|
||||
return when {
|
||||
this is PsiPackage ->
|
||||
directories.any { it.canRefactor() }
|
||||
this is KtElement ||
|
||||
this is PsiMember && language == JavaLanguage.INSTANCE ||
|
||||
this is PsiDirectory ->
|
||||
ProjectRootsUtil.isInProjectSource(this)
|
||||
else ->
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyModifierListItems(from: PsiModifierList, to: PsiModifierList, withPsiModifiers: Boolean = true) {
|
||||
if (withPsiModifiers) {
|
||||
for (modifier in PsiModifier.MODIFIERS) {
|
||||
if (from.hasExplicitModifier(modifier)) {
|
||||
to.setModifierProperty(modifier, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (annotation in from.annotations) {
|
||||
val annotationName = annotation.qualifiedName ?: continue
|
||||
|
||||
if (Retention::class.java.name != annotationName) {
|
||||
to.addAnnotation(annotationName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> copyTypeParameters(
|
||||
from: T,
|
||||
to: T,
|
||||
inserter: (T, PsiTypeParameterList) -> Unit
|
||||
) where T : PsiTypeParameterListOwner, T : PsiNameIdentifierOwner {
|
||||
val factory = PsiElementFactory.SERVICE.getInstance((from as PsiElement).project)
|
||||
val templateTypeParams = from.typeParameterList?.typeParameters ?: PsiTypeParameter.EMPTY_ARRAY
|
||||
if (templateTypeParams.isNotEmpty()) {
|
||||
inserter(to, factory.createTypeParameterList())
|
||||
val targetTypeParamList = to.typeParameterList
|
||||
val newTypeParams = templateTypeParams.map {
|
||||
factory.createTypeParameter(it.name, it.extendsList.referencedTypes)
|
||||
}
|
||||
ChangeSignatureUtil.synchronizeList(
|
||||
targetTypeParamList,
|
||||
newTypeParams,
|
||||
{ it!!.typeParameters.toList() },
|
||||
BooleanArray(newTypeParams.size)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun createJavaMethod(function: KtFunction, targetClass: PsiClass): PsiMethod {
|
||||
val template = LightClassUtil.getLightClassMethod(function)
|
||||
?: throw AssertionError("Can't generate light method: ${function.getElementTextWithContext()}")
|
||||
return createJavaMethod(template, targetClass)
|
||||
}
|
||||
|
||||
fun createJavaMethod(template: PsiMethod, targetClass: PsiClass): PsiMethod {
|
||||
val factory = PsiElementFactory.SERVICE.getInstance(template.project)
|
||||
val methodToAdd = if (template.isConstructor) {
|
||||
factory.createConstructor(template.name)
|
||||
}
|
||||
else {
|
||||
factory.createMethod(template.name, template.returnType)
|
||||
}
|
||||
val method = targetClass.add(methodToAdd) as PsiMethod
|
||||
|
||||
copyModifierListItems(template.modifierList, method.modifierList)
|
||||
if (targetClass.isInterface) {
|
||||
method.modifierList.setModifierProperty(PsiModifier.FINAL, false)
|
||||
}
|
||||
|
||||
copyTypeParameters(template, method) { method, typeParameterList ->
|
||||
method.addAfter(typeParameterList, method.modifierList)
|
||||
}
|
||||
|
||||
val targetParamList = method.parameterList
|
||||
val newParams = template.parameterList.parameters.map {
|
||||
val param = factory.createParameter(it.name!!, it.type)
|
||||
copyModifierListItems(it.modifierList!!, param.modifierList!!)
|
||||
param
|
||||
}
|
||||
ChangeSignatureUtil.synchronizeList(
|
||||
targetParamList,
|
||||
newParams,
|
||||
{ it.parameters.toList() },
|
||||
BooleanArray(newParams.size)
|
||||
)
|
||||
|
||||
if (template.modifierList.hasModifierProperty(PsiModifier.ABSTRACT) || targetClass.isInterface) {
|
||||
method.body!!.delete()
|
||||
}
|
||||
else if (!template.isConstructor) {
|
||||
CreateFromUsageUtils.setupMethodBody(method)
|
||||
}
|
||||
|
||||
return method
|
||||
}
|
||||
|
||||
fun createJavaField(property: KtNamedDeclaration, targetClass: PsiClass): PsiField {
|
||||
val accessorLightMethods = property.getAccessorLightMethods()
|
||||
val template = accessorLightMethods.getter
|
||||
?: throw AssertionError("Can't generate light method: ${property.getElementTextWithContext()}")
|
||||
|
||||
val factory = PsiElementFactory.SERVICE.getInstance(template.project)
|
||||
val field = targetClass.add(factory.createField(property.name!!, template.returnType!!)) as PsiField
|
||||
|
||||
with(field.modifierList!!) {
|
||||
val templateModifiers = template.modifierList
|
||||
setModifierProperty(VisibilityUtil.getVisibilityModifier(templateModifiers), true)
|
||||
if ((property as KtValVarKeywordOwner).valOrVarKeyword.toValVar() != KotlinValVar.Var || targetClass.isInterface) {
|
||||
setModifierProperty(PsiModifier.FINAL, true)
|
||||
}
|
||||
copyModifierListItems(templateModifiers, this, false)
|
||||
}
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
fun createJavaClass(klass: KtClass, targetClass: PsiClass?, forcePlainClass: Boolean = false): PsiClass {
|
||||
val kind = if (forcePlainClass) ClassKind.CLASS else (klass.unsafeResolveToDescriptor() as ClassDescriptor).kind
|
||||
|
||||
val factory = PsiElementFactory.SERVICE.getInstance(klass.project)
|
||||
val className = klass.name!!
|
||||
val javaClassToAdd = when (kind) {
|
||||
ClassKind.CLASS -> factory.createClass(className)
|
||||
ClassKind.INTERFACE -> factory.createInterface(className)
|
||||
ClassKind.ANNOTATION_CLASS -> factory.createAnnotationType(className)
|
||||
ClassKind.ENUM_CLASS -> factory.createEnum(className)
|
||||
else -> throw AssertionError("Unexpected class kind: ${klass.getElementTextWithContext()}")
|
||||
}
|
||||
val javaClass = (targetClass?.add(javaClassToAdd) ?: javaClassToAdd) as PsiClass
|
||||
|
||||
val template = klass.toLightClass() ?: throw AssertionError("Can't generate light class: ${klass.getElementTextWithContext()}")
|
||||
|
||||
copyModifierListItems(template.modifierList!!, javaClass.modifierList!!)
|
||||
if (template.isInterface) {
|
||||
javaClass.modifierList!!.setModifierProperty(PsiModifier.ABSTRACT, false)
|
||||
}
|
||||
|
||||
copyTypeParameters(template, javaClass) { klass, typeParameterList ->
|
||||
klass.addAfter(typeParameterList, klass.nameIdentifier)
|
||||
}
|
||||
|
||||
// Turning interface to class
|
||||
if (!javaClass.isInterface && template.isInterface) {
|
||||
val implementsList = factory.createReferenceListWithRole(
|
||||
template.extendsList?.referenceElements ?: PsiJavaCodeReferenceElement.EMPTY_ARRAY,
|
||||
PsiReferenceList.Role.IMPLEMENTS_LIST
|
||||
)
|
||||
implementsList?.let { javaClass.implementsList?.replace(it) }
|
||||
}
|
||||
else {
|
||||
val extendsList = factory.createReferenceListWithRole(
|
||||
template.extendsList?.referenceElements ?: PsiJavaCodeReferenceElement.EMPTY_ARRAY,
|
||||
PsiReferenceList.Role.EXTENDS_LIST
|
||||
)
|
||||
extendsList?.let { javaClass.extendsList?.replace(it) }
|
||||
|
||||
val implementsList = factory.createReferenceListWithRole(
|
||||
template.implementsList?.referenceElements ?: PsiJavaCodeReferenceElement.EMPTY_ARRAY,
|
||||
PsiReferenceList.Role.IMPLEMENTS_LIST
|
||||
)
|
||||
implementsList?.let { javaClass.implementsList?.replace(it) }
|
||||
}
|
||||
|
||||
for (method in template.methods) {
|
||||
val hasParams = method.parameterList.parametersCount > 0
|
||||
val needSuperCall = !template.isEnum &&
|
||||
(template.superClass?.constructors ?: PsiMethod.EMPTY_ARRAY).all {
|
||||
it.parameterList.parametersCount > 0
|
||||
}
|
||||
if (method.isConstructor && !(hasParams || needSuperCall)) continue
|
||||
with(createJavaMethod(method, javaClass)) {
|
||||
if (isConstructor && needSuperCall) {
|
||||
body!!.add(factory.createStatementFromText("super();", this))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return javaClass
|
||||
}
|
||||
|
||||
fun PsiElement.j2kText(): String? {
|
||||
if (language != JavaLanguage.INSTANCE) return null
|
||||
|
||||
val j2kConverter = JavaToKotlinConverter(project,
|
||||
ConverterSettings.Companion.defaultSettings,
|
||||
IdeaJavaToKotlinServices)
|
||||
return j2kConverter.elementsToKotlin(listOf(this)).results.single()?.text ?: return null //TODO: insert imports
|
||||
}
|
||||
|
||||
fun PsiExpression.j2k(): KtExpression? {
|
||||
val text = j2kText() ?: return null
|
||||
return KtPsiFactory(project).createExpression(text)
|
||||
}
|
||||
|
||||
fun PsiMember.j2k(): KtNamedDeclaration? {
|
||||
val text = j2kText() ?: return null
|
||||
return KtPsiFactory(project).createDeclaration(text)
|
||||
}
|
||||
|
||||
fun (() -> Any).runRefactoringWithPostprocessing(
|
||||
project: Project,
|
||||
targetRefactoringId: String,
|
||||
finishAction: () -> Unit
|
||||
) {
|
||||
val connection = project.messageBus.connect()
|
||||
connection.subscribe(RefactoringEventListener.REFACTORING_EVENT_TOPIC,
|
||||
object : RefactoringEventListener {
|
||||
override fun undoRefactoring(refactoringId: String) {
|
||||
|
||||
}
|
||||
|
||||
override fun refactoringStarted(refactoringId: String, beforeData: RefactoringEventData?) {
|
||||
|
||||
}
|
||||
|
||||
override fun conflictsDetected(refactoringId: String, conflictsData: RefactoringEventData) {
|
||||
|
||||
}
|
||||
|
||||
override fun refactoringDone(refactoringId: String, afterData: RefactoringEventData?) {
|
||||
if (refactoringId == targetRefactoringId) {
|
||||
try {
|
||||
finishAction()
|
||||
}
|
||||
finally {
|
||||
connection.disconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
this()
|
||||
}
|
||||
|
||||
@Throws(ConfigurationException::class) fun KtElement?.validateElement(errorMessage: String) {
|
||||
if (this == null) throw ConfigurationException(errorMessage)
|
||||
|
||||
try {
|
||||
AnalyzingUtils.checkForSyntacticErrors(this)
|
||||
}
|
||||
catch(e: Exception) {
|
||||
throw ConfigurationException(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
fun invokeOnceOnCommandFinish(action: () -> Unit) {
|
||||
val commandProcessor = CommandProcessor.getInstance()
|
||||
val listener = object : CommandAdapter() {
|
||||
override fun beforeCommandFinished(event: CommandEvent?) {
|
||||
action()
|
||||
commandProcessor.removeCommandListener(this)
|
||||
}
|
||||
}
|
||||
commandProcessor.addCommandListener(listener)
|
||||
}
|
||||
|
||||
fun FqNameUnsafe.hasIdentifiersOnly(): Boolean = pathSegments().all { it.asString().quoteIfNeeded().isIdentifier() }
|
||||
|
||||
fun FqName.hasIdentifiersOnly(): Boolean = pathSegments().all { it.asString().quoteIfNeeded().isIdentifier() }
|
||||
|
||||
fun PsiNamedElement.isInterfaceClass(): Boolean = when (this) {
|
||||
is KtClass -> isInterface()
|
||||
is PsiClass -> isInterface
|
||||
is KtPsiClassWrapper -> psiClass.isInterface
|
||||
else -> false
|
||||
}
|
||||
|
||||
fun KtNamedDeclaration.isAbstract(): Boolean {
|
||||
if (hasModifier(KtTokens.ABSTRACT_KEYWORD)) return true
|
||||
if (!(containingClassOrObject?.isInterfaceClass() ?: false)) return false
|
||||
return when (this) {
|
||||
is KtProperty -> initializer == null && delegate == null && accessors.isEmpty()
|
||||
is KtNamedFunction -> !hasBody()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
fun KtNamedDeclaration.isConstructorDeclaredProperty() = this is KtParameter && ownerFunction is KtPrimaryConstructor && hasValOrVar()
|
||||
|
||||
fun <ListType : KtElement> replaceListPsiAndKeepDelimiters(
|
||||
originalList: ListType,
|
||||
newList: ListType,
|
||||
@Suppress("UNCHECKED_CAST") listReplacer: ListType.(ListType) -> ListType = { replace(it) as ListType },
|
||||
itemsFun: ListType.() -> List<KtElement>
|
||||
): ListType {
|
||||
originalList.children.takeWhile { it is PsiErrorElement }.forEach { it.delete() }
|
||||
|
||||
val oldParameters = originalList.itemsFun().toMutableList()
|
||||
val newParameters = newList.itemsFun()
|
||||
val oldCount = oldParameters.size
|
||||
val newCount = newParameters.size
|
||||
|
||||
val commonCount = Math.min(oldCount, newCount)
|
||||
for (i in 0..commonCount - 1) {
|
||||
oldParameters[i] = oldParameters[i].replace(newParameters[i]) as KtElement
|
||||
}
|
||||
|
||||
if (commonCount == 0) return originalList.listReplacer(newList)
|
||||
|
||||
val lastOriginalParameter = oldParameters.last()
|
||||
|
||||
if (oldCount > commonCount) {
|
||||
originalList.deleteChildRange(oldParameters[commonCount - 1].nextSibling, lastOriginalParameter)
|
||||
}
|
||||
else if (newCount > commonCount) {
|
||||
val psiBeforeLastParameter = lastOriginalParameter.prevSibling
|
||||
val withMultiline = (psiBeforeLastParameter is PsiWhiteSpace || psiBeforeLastParameter is PsiComment) && psiBeforeLastParameter.textContains('\n')
|
||||
val extraSpace = if (withMultiline) KtPsiFactory(originalList).createNewLine() else null
|
||||
originalList.addRangeAfter(newParameters[commonCount - 1].nextSibling, newParameters.last(), lastOriginalParameter)
|
||||
if (extraSpace != null) {
|
||||
val addedItems = originalList.itemsFun().subList(commonCount, newCount)
|
||||
for (addedItem in addedItems) {
|
||||
val elementBefore = addedItem.prevSibling
|
||||
if ((elementBefore !is PsiWhiteSpace && elementBefore !is PsiComment) || !elementBefore.textContains('\n')) {
|
||||
addedItem.parent.addBefore(extraSpace, addedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return originalList
|
||||
}
|
||||
|
||||
fun <T> Pass(body: (T) -> Unit) = object : Pass<T>() {
|
||||
override fun pass(t: T) = body(t)
|
||||
}
|
||||
|
||||
fun KtExpression.removeTemplateEntryBracesIfPossible(): KtExpression {
|
||||
val parent = parent as? KtBlockStringTemplateEntry ?: return this
|
||||
val newEntry = if (parent.canDropBraces()) parent.dropBraces() else parent
|
||||
return newEntry.expression!!
|
||||
}
|
||||
|
||||
fun dropOverrideKeywordIfNecessary(element: KtNamedDeclaration) {
|
||||
val callableDescriptor = element.resolveToDescriptorIfAny() as? CallableDescriptor ?: return
|
||||
if (callableDescriptor.overriddenDescriptors.isEmpty()) {
|
||||
element.removeModifier(KtTokens.OVERRIDE_KEYWORD)
|
||||
}
|
||||
}
|
||||
|
||||
fun dropOperatorKeywordIfNecessary(element: KtNamedDeclaration) {
|
||||
val callableDescriptor = element.resolveToDescriptorIfAny() as? CallableDescriptor ?: return
|
||||
val diagnosticHolder = BindingTraceContext()
|
||||
OperatorModifierChecker.check(element, callableDescriptor, diagnosticHolder, element.languageVersionSettings)
|
||||
if (diagnosticHolder.bindingContext.diagnostics.any { it.factory == Errors.INAPPLICABLE_OPERATOR_MODIFIER }) {
|
||||
element.removeModifier(KtTokens.OPERATOR_KEYWORD)
|
||||
}
|
||||
}
|
||||
|
||||
fun getQualifiedTypeArgumentList(initializer: KtExpression): KtTypeArgumentList? {
|
||||
val call = initializer.resolveToCall() ?: return null
|
||||
val typeArgumentMap = call.typeArguments
|
||||
val typeArguments = call.candidateDescriptor.typeParameters.mapNotNull { typeArgumentMap[it] }
|
||||
val renderedList = typeArguments.joinToString(prefix = "<", postfix = ">") {
|
||||
IdeDescriptorRenderers.SOURCE_CODE_NOT_NULL_TYPE_APPROXIMATION.renderType(it)
|
||||
}
|
||||
return KtPsiFactory(initializer).createTypeArguments(renderedList)
|
||||
}
|
||||
|
||||
fun addTypeArgumentsIfNeeded(expression: KtExpression, typeArgumentList: KtTypeArgumentList) {
|
||||
val context = expression.analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
|
||||
val call = expression.getCallWithAssert(context)
|
||||
val callElement = call.callElement as? KtCallExpression ?: return
|
||||
if (call.typeArgumentList != null) return
|
||||
val callee = call.calleeExpression ?: return
|
||||
if (context.diagnostics.forElement(callee).all { it.factory != Errors.TYPE_INFERENCE_NO_INFORMATION_FOR_PARAMETER }) return
|
||||
|
||||
callElement.addAfter(typeArgumentList, callElement.calleeExpression)
|
||||
ShortenReferences.DEFAULT.process(callElement.typeArgumentList!!)
|
||||
}
|
||||
|
||||
internal fun DeclarationDescriptor.getThisLabelName(): String {
|
||||
if (!name.isSpecial) return name.asString()
|
||||
if (this is AnonymousFunctionDescriptor) {
|
||||
val function = source.getPsi() as? KtFunction
|
||||
val argument = function?.parent as? KtValueArgument
|
||||
?: (function?.parent as? KtLambdaExpression)?.parent as? KtValueArgument
|
||||
val callElement = argument?.getStrictParentOfType<KtCallElement>()
|
||||
val callee = callElement?.calleeExpression as? KtSimpleNameExpression
|
||||
if (callee != null) return callee.text
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
internal fun DeclarationDescriptor.explicateAsTextForReceiver(): String {
|
||||
val labelName = getThisLabelName()
|
||||
return if (labelName.isEmpty()) "this" else "this@$labelName"
|
||||
}
|
||||
|
||||
internal fun ImplicitReceiver.explicateAsText(): String {
|
||||
return declarationDescriptor.explicateAsTextForReceiver()
|
||||
}
|
||||
|
||||
val PsiFile.isInjectedFragment: Boolean
|
||||
get() = InjectedLanguageManager.getInstance(project).isInjectedFragment(this)
|
||||
|
||||
val PsiElement.isInsideInjectedFragment: Boolean
|
||||
get() = containingFile.isInjectedFragment
|
||||
|
||||
fun checkSuperMethods(
|
||||
declaration: KtDeclaration,
|
||||
ignore: Collection<PsiElement>?,
|
||||
actionString: String
|
||||
): List<PsiElement> {
|
||||
fun getClassDescriptions(overriddenElementsToDescriptor: Map<PsiElement, CallableDescriptor>): List<String> {
|
||||
return overriddenElementsToDescriptor.entries.map { entry ->
|
||||
val (element, descriptor) = entry
|
||||
val description = when (element) {
|
||||
is KtNamedFunction, is KtProperty, is KtParameter -> formatClassDescriptor(descriptor.containingDeclaration)
|
||||
is PsiMethod -> {
|
||||
val psiClass = element.containingClass ?: error("Invalid element: ${element.getText()}")
|
||||
formatPsiClass(psiClass, true, false)
|
||||
}
|
||||
else -> error("Unexpected element: ${element.getElementTextWithContext()}")
|
||||
}
|
||||
" $description\n"
|
||||
}
|
||||
}
|
||||
|
||||
fun askUserForMethodsToSearch(
|
||||
declarationDescriptor: CallableDescriptor,
|
||||
overriddenElementsToDescriptor: Map<PsiElement, CallableDescriptor>
|
||||
): List<PsiElement> {
|
||||
val superClassDescriptions = getClassDescriptions(overriddenElementsToDescriptor)
|
||||
|
||||
val message = KotlinBundle.message(
|
||||
"x.overrides.y.in.class.list",
|
||||
DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(declarationDescriptor),
|
||||
"\n${superClassDescriptions.joinToString(separator = "")}",
|
||||
actionString
|
||||
)
|
||||
|
||||
val exitCode = showYesNoCancelDialog(
|
||||
CHECK_SUPER_METHODS_YES_NO_DIALOG,
|
||||
declaration.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon(), Messages.YES)
|
||||
return when (exitCode) {
|
||||
Messages.YES -> overriddenElementsToDescriptor.keys.toList()
|
||||
Messages.NO -> listOf(declaration)
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val declarationDescriptor = declaration.unsafeResolveToDescriptor() as CallableDescriptor
|
||||
|
||||
if (declarationDescriptor is LocalVariableDescriptor) return listOf(declaration)
|
||||
|
||||
val project = declaration.project
|
||||
val overriddenElementsToDescriptor = HashMap<PsiElement, CallableDescriptor>()
|
||||
for (overriddenDescriptor in DescriptorUtils.getAllOverriddenDescriptors(declarationDescriptor)) {
|
||||
val overriddenDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, overriddenDescriptor) ?: continue
|
||||
if (overriddenDeclaration is KtNamedFunction || overriddenDeclaration is KtProperty || overriddenDeclaration is PsiMethod || overriddenDeclaration is KtParameter) {
|
||||
overriddenElementsToDescriptor[overriddenDeclaration] = overriddenDescriptor
|
||||
}
|
||||
}
|
||||
if (ignore != null) {
|
||||
overriddenElementsToDescriptor.keys.removeAll(ignore)
|
||||
}
|
||||
|
||||
if (overriddenElementsToDescriptor.isEmpty()) return listOf(declaration)
|
||||
|
||||
return askUserForMethodsToSearch(declarationDescriptor, overriddenElementsToDescriptor)
|
||||
}
|
||||
|
||||
fun checkSuperMethodsWithPopup(
|
||||
declaration: KtNamedDeclaration,
|
||||
deepestSuperMethods: List<PsiElement>,
|
||||
actionString: String,
|
||||
editor: Editor,
|
||||
action: (List<PsiElement>) -> Unit
|
||||
) {
|
||||
if (deepestSuperMethods.isEmpty()) return action(listOf(declaration))
|
||||
|
||||
val superMethod = deepestSuperMethods.first()
|
||||
|
||||
val (superClass, isAbstract) = when (superMethod) {
|
||||
is PsiMember -> superMethod.containingClass to superMethod.hasModifierProperty(PsiModifier.ABSTRACT)
|
||||
is KtNamedDeclaration -> superMethod.containingClassOrObject to superMethod.isAbstract()
|
||||
else -> null
|
||||
} ?: return action(listOf(declaration))
|
||||
if (superClass == null) return action(listOf(declaration))
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) return action(deepestSuperMethods)
|
||||
|
||||
val kind = when (declaration) {
|
||||
is KtNamedFunction -> "function"
|
||||
is KtProperty, is KtParameter -> "property"
|
||||
else -> return
|
||||
}
|
||||
|
||||
val unwrappedSupers = deepestSuperMethods.mapNotNull { it.namedUnwrappedElement }
|
||||
val hasJavaMethods = unwrappedSupers.any { it is PsiMethod }
|
||||
val hasKtMembers = unwrappedSupers.any { it is KtNamedDeclaration }
|
||||
val superKind = when {
|
||||
hasJavaMethods && hasKtMembers -> "member"
|
||||
hasJavaMethods -> "method"
|
||||
else -> kind
|
||||
}
|
||||
|
||||
val renameBase = actionString + " base $superKind" + (if (deepestSuperMethods.size > 1) "s" else "")
|
||||
val renameCurrent = actionString + " only current $kind"
|
||||
val title = buildString {
|
||||
append(declaration.name)
|
||||
append(if (isAbstract) " implements " else " overrides ")
|
||||
append(ElementDescriptionUtil.getElementDescription(superMethod, UsageViewTypeLocation.INSTANCE))
|
||||
append(" of ")
|
||||
append(SymbolPresentationUtil.getSymbolPresentableText(superClass))
|
||||
}
|
||||
val list = JBList<String>(renameBase, renameCurrent)
|
||||
JBPopupFactory.getInstance()
|
||||
.createListPopupBuilder(list)
|
||||
.setTitle(title)
|
||||
.setMovable(false)
|
||||
.setResizable(false)
|
||||
.setRequestFocus(true)
|
||||
.setItemChoosenCallback {
|
||||
val value = list.selectedValue ?: return@setItemChoosenCallback
|
||||
val chosenElements = if (value == renameBase) deepestSuperMethods + declaration else listOf(declaration)
|
||||
action(chosenElements)
|
||||
}
|
||||
.createPopup()
|
||||
.showInBestPositionFor(editor)
|
||||
}
|
||||
|
||||
fun KtNamedDeclaration.isCompanionMemberOf(klass: KtClassOrObject): Boolean {
|
||||
val containingObject = containingClassOrObject as? KtObjectDeclaration ?: return false
|
||||
return containingObject.isCompanion() && containingObject.containingClassOrObject == klass
|
||||
}
|
||||
|
||||
internal fun KtDeclaration.withExpectedActuals(): List<KtDeclaration> {
|
||||
val expect = liftToExpected() ?: return listOf(this)
|
||||
val actuals = expect.actualsForExpected()
|
||||
return listOf(expect) + actuals
|
||||
}
|
||||
|
||||
internal fun KtDeclaration.resolveToExpectedDescriptorIfPossible(): DeclarationDescriptor {
|
||||
val descriptor = unsafeResolveToDescriptor()
|
||||
return descriptor.liftToExpected() ?: descriptor
|
||||
}
|
||||
Reference in New Issue
Block a user