Drop bunch code by introduction wrapper for PopupChooserBuilder
This commit is contained in:
+1
@@ -115,6 +115,7 @@
|
|||||||
<Problem reference="com.intellij.openapi.extensions.ExtensionPointName#extensions(com.intellij.openapi.extensions.AreaInstance)" reason="Absent in 182." />
|
<Problem reference="com.intellij.openapi.extensions.ExtensionPointName#extensions(com.intellij.openapi.extensions.AreaInstance)" reason="Absent in 182." />
|
||||||
<Problem reference="com.intellij.openapi.extensions.ExtensionPointName#getPoint" reason="Absent in 182." />
|
<Problem reference="com.intellij.openapi.extensions.ExtensionPointName#getPoint" reason="Absent in 182." />
|
||||||
<Problem reference="com.intellij.openapi.extensions.ExtensionPointName#findExtensionOrFail" reason="Absent in 182." />
|
<Problem reference="com.intellij.openapi.extensions.ExtensionPointName#findExtensionOrFail" reason="Absent in 182." />
|
||||||
|
<Problem reference="com.intellij.openapi.ui.popup.PopupChooserBuilder#PopupChooserBuilder(javax.swing.JList)" reason="Generified in 182. Use PopupChooserBuilderWrapper instead." />
|
||||||
</list>
|
</list>
|
||||||
</option>
|
</option>
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
|
|||||||
+2
-1
@@ -48,6 +48,7 @@ import org.jetbrains.kotlin.idea.core.overrideImplement.generateUnsupportedOrSup
|
|||||||
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.setupEditorSelection
|
import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.setupEditorSelection
|
||||||
import org.jetbrains.kotlin.idea.refactoring.j2k
|
import org.jetbrains.kotlin.idea.refactoring.j2k
|
||||||
import org.jetbrains.kotlin.idea.testIntegration.findSuitableFrameworks
|
import org.jetbrains.kotlin.idea.testIntegration.findSuitableFrameworks
|
||||||
|
import org.jetbrains.kotlin.idea.util.PopupChooserBuilderWrapper
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.lexer.KtTokens
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
import org.jetbrains.kotlin.psi.*
|
import org.jetbrains.kotlin.psi.*
|
||||||
@@ -74,7 +75,7 @@ abstract class KotlinGenerateTestSupportActionBase(
|
|||||||
val list = JBList<TestFramework>(*frameworks.toTypedArray())
|
val list = JBList<TestFramework>(*frameworks.toTypedArray())
|
||||||
list.cellRenderer = TestFrameworkListCellRenderer()
|
list.cellRenderer = TestFrameworkListCellRenderer()
|
||||||
|
|
||||||
PopupChooserBuilder<TestFramework>(list)
|
PopupChooserBuilderWrapper<TestFramework>(list)
|
||||||
.setFilteringEnabled { (it as TestFramework).name }
|
.setFilteringEnabled { (it as TestFramework).name }
|
||||||
.setTitle("Choose Framework")
|
.setTitle("Choose Framework")
|
||||||
.setItemChoosenCallback { consumer(list.selectedValue as TestFramework) }
|
.setItemChoosenCallback { consumer(list.selectedValue as TestFramework) }
|
||||||
|
|||||||
-261
@@ -1,261 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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(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.FROM_TEMPLATE)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -45,6 +45,7 @@ import org.jetbrains.kotlin.idea.refactoring.isAbstract
|
|||||||
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
|
import org.jetbrains.kotlin.idea.runSynchronouslyWithProgress
|
||||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
|
import org.jetbrains.kotlin.idea.search.declarationsSearch.HierarchySearchRequest
|
||||||
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
|
import org.jetbrains.kotlin.idea.search.declarationsSearch.searchInheritors
|
||||||
|
import org.jetbrains.kotlin.idea.util.PopupChooserBuilderWrapper
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
import org.jetbrains.kotlin.idea.util.application.executeCommand
|
||||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||||
@@ -213,7 +214,7 @@ abstract class ImplementAbstractMemberIntentionBase :
|
|||||||
selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
|
selectionMode = ListSelectionModel.MULTIPLE_INTERVAL_SELECTION
|
||||||
cellRenderer = renderer
|
cellRenderer = renderer
|
||||||
}
|
}
|
||||||
val builder = PopupChooserBuilder<PsiElement>(list)
|
val builder = PopupChooserBuilderWrapper<PsiElement>(list)
|
||||||
renderer.installSpeedSearch(builder)
|
renderer.installSpeedSearch(builder)
|
||||||
builder
|
builder
|
||||||
.setTitle(CodeInsightBundle.message("intention.implement.abstract.method.class.chooser.title"))
|
.setTitle(CodeInsightBundle.message("intention.implement.abstract.method.class.chooser.title"))
|
||||||
|
|||||||
-268
@@ -1,268 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.FROM_TEMPLATE,
|
|
||||||
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(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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -74,10 +74,7 @@ import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
|||||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar
|
import org.jetbrains.kotlin.idea.refactoring.changeSignature.KotlinValVar
|
||||||
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
|
import org.jetbrains.kotlin.idea.refactoring.changeSignature.toValVar
|
||||||
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper
|
import org.jetbrains.kotlin.idea.refactoring.memberInfo.KtPsiClassWrapper
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
import org.jetbrains.kotlin.idea.util.*
|
||||||
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.idea.util.string.collapseSpaces
|
||||||
import org.jetbrains.kotlin.j2k.ConverterSettings
|
import org.jetbrains.kotlin.j2k.ConverterSettings
|
||||||
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
|
import org.jetbrains.kotlin.j2k.JavaToKotlinConverter
|
||||||
@@ -263,7 +260,7 @@ fun <T, E : PsiElement> getPsiElementPopup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return with(PopupChooserBuilder<E>(list)) {
|
return with(PopupChooserBuilderWrapper<E>(list)) {
|
||||||
title?.let { setTitle(it) }
|
title?.let { setTitle(it) }
|
||||||
renderer.installSpeedSearch(this, true)
|
renderer.installSpeedSearch(this, true)
|
||||||
setItemChoosenCallback {
|
setItemChoosenCallback {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 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.util
|
||||||
|
|
||||||
|
import com.intellij.openapi.ui.popup.PopupChooserBuilder
|
||||||
|
import javax.swing.JList
|
||||||
|
|
||||||
|
// BUNCH: 181
|
||||||
|
@Suppress("IncompatibleAPI")
|
||||||
|
class PopupChooserBuilderWrapper<T>(list: JList<T>): PopupChooserBuilder<T>(list)
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2019 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.util
|
||||||
|
|
||||||
|
import com.intellij.openapi.ui.popup.PopupChooserBuilder
|
||||||
|
import javax.swing.JList
|
||||||
|
|
||||||
|
// BUNCH: 181
|
||||||
|
@Suppress("IncompatibleAPI")
|
||||||
|
class PopupChooserBuilderWrapper<T>(list: JList<T>) : PopupChooserBuilder(list)
|
||||||
Reference in New Issue
Block a user