|
|
|
@@ -14,585 +14,497 @@
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package org.jetbrains.kotlin.idea.refactoring;
|
|
|
|
|
package org.jetbrains.kotlin.idea.refactoring
|
|
|
|
|
|
|
|
|
|
import com.intellij.codeInsight.unwrap.ScopeHighlighter;
|
|
|
|
|
import com.intellij.ide.IdeBundle;
|
|
|
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
|
|
|
import com.intellij.openapi.editor.Editor;
|
|
|
|
|
import com.intellij.openapi.project.Project;
|
|
|
|
|
import com.intellij.openapi.ui.Messages;
|
|
|
|
|
import com.intellij.openapi.ui.popup.JBPopupAdapter;
|
|
|
|
|
import com.intellij.openapi.ui.popup.JBPopupFactory;
|
|
|
|
|
import com.intellij.openapi.ui.popup.LightweightWindowEvent;
|
|
|
|
|
import com.intellij.openapi.util.text.StringUtil;
|
|
|
|
|
import com.intellij.psi.*;
|
|
|
|
|
import com.intellij.psi.search.searches.OverridingMethodsSearch;
|
|
|
|
|
import com.intellij.psi.util.PsiFormatUtil;
|
|
|
|
|
import com.intellij.psi.util.PsiFormatUtilBase;
|
|
|
|
|
import com.intellij.psi.util.PsiTreeUtil;
|
|
|
|
|
import com.intellij.ui.components.JBList;
|
|
|
|
|
import com.intellij.util.Function;
|
|
|
|
|
import com.intellij.util.containers.ContainerUtil;
|
|
|
|
|
import kotlin.collections.CollectionsKt;
|
|
|
|
|
import kotlin.jvm.functions.Function1;
|
|
|
|
|
import org.jetbrains.annotations.NotNull;
|
|
|
|
|
import org.jetbrains.annotations.Nullable;
|
|
|
|
|
import org.jetbrains.kotlin.asJava.LightClassUtilsKt;
|
|
|
|
|
import org.jetbrains.kotlin.asJava.elements.KtLightMethod;
|
|
|
|
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor;
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor;
|
|
|
|
|
import org.jetbrains.kotlin.idea.KotlinBundle;
|
|
|
|
|
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
|
|
|
|
|
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils;
|
|
|
|
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde;
|
|
|
|
|
import org.jetbrains.kotlin.idea.refactoring.introduce.IntroduceUtilKt;
|
|
|
|
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
|
|
|
|
|
import org.jetbrains.kotlin.psi.*;
|
|
|
|
|
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
|
|
|
|
|
import org.jetbrains.kotlin.psi.psiUtil.PsiUtilsKt;
|
|
|
|
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
|
|
|
|
|
import org.jetbrains.kotlin.resolve.BindingContext;
|
|
|
|
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
|
|
|
|
|
import org.jetbrains.kotlin.resolve.DescriptorUtils;
|
|
|
|
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
|
|
|
|
|
import org.jetbrains.kotlin.types.KotlinType;
|
|
|
|
|
import com.intellij.codeInsight.unwrap.ScopeHighlighter
|
|
|
|
|
import com.intellij.ide.IdeBundle
|
|
|
|
|
import com.intellij.openapi.application.ApplicationManager
|
|
|
|
|
import com.intellij.openapi.editor.Editor
|
|
|
|
|
import com.intellij.openapi.ui.Messages
|
|
|
|
|
import com.intellij.openapi.ui.popup.JBPopupAdapter
|
|
|
|
|
import com.intellij.openapi.ui.popup.JBPopupFactory
|
|
|
|
|
import com.intellij.openapi.ui.popup.LightweightWindowEvent
|
|
|
|
|
import com.intellij.openapi.util.text.StringUtil
|
|
|
|
|
import com.intellij.psi.*
|
|
|
|
|
import com.intellij.psi.search.searches.OverridingMethodsSearch
|
|
|
|
|
import com.intellij.psi.util.PsiFormatUtil
|
|
|
|
|
import com.intellij.psi.util.PsiFormatUtilBase
|
|
|
|
|
import com.intellij.psi.util.PsiTreeUtil
|
|
|
|
|
import com.intellij.ui.components.JBList
|
|
|
|
|
import com.intellij.util.containers.ContainerUtil
|
|
|
|
|
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
|
|
|
|
|
import org.jetbrains.kotlin.asJava.unwrapped
|
|
|
|
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
|
|
|
|
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
|
|
|
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
|
|
|
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
|
|
|
|
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
|
|
|
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
|
|
|
|
import org.jetbrains.kotlin.idea.refactoring.introduce.findExpressionOrStringFragment
|
|
|
|
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
|
|
|
|
import org.jetbrains.kotlin.psi.*
|
|
|
|
|
import org.jetbrains.kotlin.psi.psiUtil.getNextSiblingIgnoringWhitespaceAndComments
|
|
|
|
|
import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
|
|
|
|
|
import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
|
|
|
|
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
|
|
|
|
import org.jetbrains.kotlin.resolve.BindingContext
|
|
|
|
|
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
|
|
|
|
|
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
|
|
|
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
|
|
|
|
import java.awt.Component
|
|
|
|
|
import java.lang.RuntimeException
|
|
|
|
|
import java.util.*
|
|
|
|
|
import javax.swing.DefaultListCellRenderer
|
|
|
|
|
import javax.swing.DefaultListModel
|
|
|
|
|
import javax.swing.JList
|
|
|
|
|
import javax.swing.event.ListSelectionListener
|
|
|
|
|
|
|
|
|
|
import javax.swing.*;
|
|
|
|
|
import javax.swing.event.ListSelectionEvent;
|
|
|
|
|
import javax.swing.event.ListSelectionListener;
|
|
|
|
|
import java.awt.*;
|
|
|
|
|
import java.util.*;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
object KotlinRefactoringUtil2 {
|
|
|
|
|
|
|
|
|
|
public class KotlinRefactoringUtil2 {
|
|
|
|
|
private KotlinRefactoringUtil2() {
|
|
|
|
|
fun wrapOrSkip(s: String, inCode: Boolean): String {
|
|
|
|
|
return if (inCode) "<code>$s</code>" else s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
public static String wrapOrSkip(@NotNull String s, boolean inCode) {
|
|
|
|
|
return inCode ? "<code>" + s + "</code>" : s;
|
|
|
|
|
fun formatClassDescriptor(classDescriptor: DeclarationDescriptor): String {
|
|
|
|
|
return IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.render(classDescriptor)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
public static String formatClassDescriptor(@NotNull DeclarationDescriptor classDescriptor) {
|
|
|
|
|
return IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.render(classDescriptor);
|
|
|
|
|
}
|
|
|
|
|
fun formatPsiClass(
|
|
|
|
|
psiClass: PsiClass,
|
|
|
|
|
markAsJava: Boolean,
|
|
|
|
|
inCode: Boolean): String {
|
|
|
|
|
var description: String
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
public static String formatPsiClass(
|
|
|
|
|
@NotNull PsiClass psiClass,
|
|
|
|
|
boolean markAsJava,
|
|
|
|
|
boolean inCode
|
|
|
|
|
) {
|
|
|
|
|
String description;
|
|
|
|
|
|
|
|
|
|
String kind = psiClass.isInterface() ? "interface " : "class ";
|
|
|
|
|
val kind = if (psiClass.isInterface) "interface " else "class "
|
|
|
|
|
description = kind + PsiFormatUtil.formatClass(
|
|
|
|
|
psiClass,
|
|
|
|
|
PsiFormatUtilBase.SHOW_CONTAINING_CLASS
|
|
|
|
|
| PsiFormatUtilBase.SHOW_NAME
|
|
|
|
|
| PsiFormatUtilBase.SHOW_PARAMETERS
|
|
|
|
|
| PsiFormatUtilBase.SHOW_TYPE
|
|
|
|
|
);
|
|
|
|
|
description = wrapOrSkip(description, inCode);
|
|
|
|
|
PsiFormatUtilBase.SHOW_CONTAINING_CLASS or PsiFormatUtilBase.SHOW_NAME or PsiFormatUtilBase.SHOW_PARAMETERS or PsiFormatUtilBase.SHOW_TYPE)
|
|
|
|
|
description = wrapOrSkip(description, inCode)
|
|
|
|
|
|
|
|
|
|
return markAsJava ? "[Java] " + description : description;
|
|
|
|
|
return if (markAsJava) "[Java] " + description else description
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
public static List<? extends PsiElement> checkSuperMethods(
|
|
|
|
|
@NotNull KtDeclaration declaration,
|
|
|
|
|
@Nullable Collection<PsiElement> ignore,
|
|
|
|
|
@NotNull String actionStringKey
|
|
|
|
|
) {
|
|
|
|
|
BindingContext bindingContext = ResolutionUtils.analyze(declaration, BodyResolveMode.FULL);
|
|
|
|
|
fun checkSuperMethods(
|
|
|
|
|
declaration: KtDeclaration,
|
|
|
|
|
ignore: Collection<PsiElement>?,
|
|
|
|
|
actionStringKey: String): List<PsiElement> {
|
|
|
|
|
val bindingContext = declaration.analyze(BodyResolveMode.FULL)
|
|
|
|
|
|
|
|
|
|
CallableDescriptor declarationDescriptor =
|
|
|
|
|
(CallableDescriptor)bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration);
|
|
|
|
|
val declarationDescriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) as CallableDescriptor?
|
|
|
|
|
|
|
|
|
|
if (declarationDescriptor == null || declarationDescriptor instanceof LocalVariableDescriptor) {
|
|
|
|
|
return Collections.singletonList(declaration);
|
|
|
|
|
if (declarationDescriptor == null || declarationDescriptor is LocalVariableDescriptor) {
|
|
|
|
|
return listOf(declaration)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Project project = declaration.getProject();
|
|
|
|
|
Map<PsiElement, CallableDescriptor> overriddenElementsToDescriptor = new HashMap<PsiElement, CallableDescriptor>();
|
|
|
|
|
for (CallableDescriptor overriddenDescriptor : DescriptorUtils.getAllOverriddenDescriptors(declarationDescriptor)) {
|
|
|
|
|
PsiElement overriddenDeclaration = DescriptorToSourceUtilsIde.INSTANCE.getAnyDeclaration(project, overriddenDescriptor);
|
|
|
|
|
if (PsiTreeUtil.instanceOf(overriddenDeclaration, KtNamedFunction.class, KtProperty.class, PsiMethod.class)) {
|
|
|
|
|
overriddenElementsToDescriptor.put(overriddenDeclaration, overriddenDescriptor);
|
|
|
|
|
val project = declaration.project
|
|
|
|
|
val overriddenElementsToDescriptor = HashMap<PsiElement, CallableDescriptor>()
|
|
|
|
|
for (overriddenDescriptor in DescriptorUtils.getAllOverriddenDescriptors(declarationDescriptor)) {
|
|
|
|
|
val overriddenDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, overriddenDescriptor) ?: continue
|
|
|
|
|
if (PsiTreeUtil.instanceOf(overriddenDeclaration, KtNamedFunction::class.java, KtProperty::class.java, PsiMethod::class.java)) {
|
|
|
|
|
overriddenElementsToDescriptor.put(overriddenDeclaration, overriddenDescriptor)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (ignore != null) {
|
|
|
|
|
overriddenElementsToDescriptor.keySet().removeAll(ignore);
|
|
|
|
|
overriddenElementsToDescriptor.keys.removeAll(ignore)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (overriddenElementsToDescriptor.isEmpty()) return Collections.singletonList(declaration);
|
|
|
|
|
if (overriddenElementsToDescriptor.isEmpty()) return listOf(declaration)
|
|
|
|
|
|
|
|
|
|
List<String> superClasses = getClassDescriptions(overriddenElementsToDescriptor);
|
|
|
|
|
return askUserForMethodsToSearch(declaration, declarationDescriptor, overriddenElementsToDescriptor, superClasses, actionStringKey);
|
|
|
|
|
val superClasses = getClassDescriptions(overriddenElementsToDescriptor)
|
|
|
|
|
return askUserForMethodsToSearch(declaration, declarationDescriptor, overriddenElementsToDescriptor, superClasses, actionStringKey)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
private static List<? extends PsiElement> askUserForMethodsToSearch(
|
|
|
|
|
@NotNull KtDeclaration declaration,
|
|
|
|
|
@NotNull CallableDescriptor declarationDescriptor,
|
|
|
|
|
@NotNull Map<PsiElement, CallableDescriptor> overriddenElementsToDescriptor,
|
|
|
|
|
@NotNull List<String> superClasses,
|
|
|
|
|
@NotNull String actionStringKey
|
|
|
|
|
) {
|
|
|
|
|
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
|
|
|
|
return ContainerUtil.newArrayList(overriddenElementsToDescriptor.keySet());
|
|
|
|
|
private fun askUserForMethodsToSearch(
|
|
|
|
|
declaration: KtDeclaration,
|
|
|
|
|
declarationDescriptor: CallableDescriptor,
|
|
|
|
|
overriddenElementsToDescriptor: Map<PsiElement, CallableDescriptor>,
|
|
|
|
|
superClasses: List<String>,
|
|
|
|
|
actionStringKey: String): List<PsiElement> {
|
|
|
|
|
if (ApplicationManager.getApplication().isUnitTestMode) {
|
|
|
|
|
return ContainerUtil.newArrayList(overriddenElementsToDescriptor.keys)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String superClassesStr = "\n" + StringUtil.join(superClasses, "");
|
|
|
|
|
String message = KotlinBundle.message(
|
|
|
|
|
val superClassesStr = "\n" + StringUtil.join(superClasses, "")
|
|
|
|
|
val message = KotlinBundle.message(
|
|
|
|
|
"x.overrides.y.in.class.list",
|
|
|
|
|
DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(declarationDescriptor),
|
|
|
|
|
superClassesStr,
|
|
|
|
|
KotlinBundle.message(actionStringKey)
|
|
|
|
|
);
|
|
|
|
|
KotlinBundle.message(actionStringKey))
|
|
|
|
|
|
|
|
|
|
int exitCode = Messages.showYesNoCancelDialog(declaration.getProject(), message, IdeBundle.message("title.warning"), Messages.getQuestionIcon());
|
|
|
|
|
switch (exitCode) {
|
|
|
|
|
case Messages.YES:
|
|
|
|
|
return ContainerUtil.newArrayList(overriddenElementsToDescriptor.keySet());
|
|
|
|
|
case Messages.NO:
|
|
|
|
|
return Collections.singletonList(declaration);
|
|
|
|
|
default:
|
|
|
|
|
return Collections.emptyList();
|
|
|
|
|
val exitCode = Messages.showYesNoCancelDialog(declaration.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon())
|
|
|
|
|
when (exitCode) {
|
|
|
|
|
Messages.YES -> return ContainerUtil.newArrayList(overriddenElementsToDescriptor.keys)
|
|
|
|
|
Messages.NO -> return listOf(declaration)
|
|
|
|
|
else -> return emptyList()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
private static List<String> getClassDescriptions(@NotNull Map<PsiElement, CallableDescriptor> overriddenElementsToDescriptor) {
|
|
|
|
|
return ContainerUtil.map(
|
|
|
|
|
overriddenElementsToDescriptor.entrySet(),
|
|
|
|
|
new Function<Map.Entry<PsiElement, CallableDescriptor>, String>() {
|
|
|
|
|
@Override
|
|
|
|
|
public String fun(Map.Entry<PsiElement, CallableDescriptor> entry) {
|
|
|
|
|
String description;
|
|
|
|
|
private fun getClassDescriptions(overriddenElementsToDescriptor: Map<PsiElement, CallableDescriptor>): List<String> {
|
|
|
|
|
return overriddenElementsToDescriptor.entries.map { entry ->
|
|
|
|
|
val description: String
|
|
|
|
|
|
|
|
|
|
PsiElement element = entry.getKey();
|
|
|
|
|
CallableDescriptor descriptor = entry.getValue();
|
|
|
|
|
if (element instanceof KtNamedFunction || element instanceof KtProperty) {
|
|
|
|
|
description = formatClassDescriptor(descriptor.getContainingDeclaration());
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
assert element instanceof PsiMethod : "Invalid element: " + element.getText();
|
|
|
|
|
|
|
|
|
|
PsiClass psiClass = ((PsiMethod) element).getContainingClass();
|
|
|
|
|
assert psiClass != null : "Invalid element: " + element.getText();
|
|
|
|
|
|
|
|
|
|
description = formatPsiClass(psiClass, true, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return " " + description + "\n";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
public static String formatClass(@NotNull DeclarationDescriptor classDescriptor, boolean inCode) {
|
|
|
|
|
PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(classDescriptor);
|
|
|
|
|
if (element instanceof PsiClass) {
|
|
|
|
|
return formatPsiClass((PsiClass) element, false, inCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wrapOrSkip(formatClassDescriptor(classDescriptor), inCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
public static String formatFunction(@NotNull DeclarationDescriptor functionDescriptor, boolean inCode) {
|
|
|
|
|
PsiElement element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor);
|
|
|
|
|
if (element instanceof PsiMethod) {
|
|
|
|
|
return formatPsiMethod((PsiMethod) element, false, inCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wrapOrSkip(formatFunctionDescriptor(functionDescriptor), inCode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
private static String formatFunctionDescriptor(@NotNull DeclarationDescriptor functionDescriptor) {
|
|
|
|
|
return DescriptorRenderer.COMPACT.render(functionDescriptor);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
public static String formatPsiMethod(
|
|
|
|
|
@NotNull PsiMethod psiMethod,
|
|
|
|
|
boolean showContainingClass,
|
|
|
|
|
boolean inCode) {
|
|
|
|
|
int options = PsiFormatUtilBase.SHOW_NAME | PsiFormatUtilBase.SHOW_PARAMETERS | PsiFormatUtilBase.SHOW_TYPE;
|
|
|
|
|
if (showContainingClass) {
|
|
|
|
|
//noinspection ConstantConditions
|
|
|
|
|
options |= PsiFormatUtilBase.SHOW_CONTAINING_CLASS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String description = PsiFormatUtil.formatMethod(psiMethod, PsiSubstitutor.EMPTY, options, PsiFormatUtilBase.SHOW_TYPE);
|
|
|
|
|
description = wrapOrSkip(description, inCode);
|
|
|
|
|
|
|
|
|
|
return "[Java] " + description;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
public static String formatJavaOrLightMethod(@NotNull PsiMethod method) {
|
|
|
|
|
PsiElement originalDeclaration = LightClassUtilsKt.getUnwrapped(method);
|
|
|
|
|
if (originalDeclaration instanceof KtDeclaration) {
|
|
|
|
|
KtDeclaration ktDeclaration = (KtDeclaration) originalDeclaration;
|
|
|
|
|
BindingContext bindingContext = ResolutionUtils.analyze(ktDeclaration, BodyResolveMode.FULL);
|
|
|
|
|
DeclarationDescriptor descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, ktDeclaration);
|
|
|
|
|
|
|
|
|
|
if (descriptor != null) return formatFunctionDescriptor(descriptor);
|
|
|
|
|
}
|
|
|
|
|
return formatPsiMethod(method, false, false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@NotNull
|
|
|
|
|
public static String formatClass(@NotNull KtClassOrObject classOrObject) {
|
|
|
|
|
BindingContext bindingContext = ResolutionUtils.analyze(classOrObject, BodyResolveMode.FULL);
|
|
|
|
|
DeclarationDescriptor descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject);
|
|
|
|
|
|
|
|
|
|
if (descriptor instanceof ClassDescriptor) return formatClassDescriptor(descriptor);
|
|
|
|
|
return "class " + classOrObject.getName();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
|
public static Collection<? extends PsiElement> checkParametersInMethodHierarchy(@NotNull PsiParameter parameter) {
|
|
|
|
|
PsiMethod method = (PsiMethod)parameter.getDeclarationScope();
|
|
|
|
|
|
|
|
|
|
Set<PsiElement> parametersToDelete = collectParametersHierarchy(method, parameter);
|
|
|
|
|
if (parametersToDelete.size() > 1) {
|
|
|
|
|
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
|
|
|
|
return parametersToDelete;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String message =
|
|
|
|
|
KotlinBundle.message("delete.param.in.method.hierarchy", formatJavaOrLightMethod(method));
|
|
|
|
|
int exitCode = Messages.showOkCancelDialog(
|
|
|
|
|
parameter.getProject(), message, IdeBundle.message("title.warning"), Messages.getQuestionIcon()
|
|
|
|
|
);
|
|
|
|
|
if (exitCode == Messages.OK) {
|
|
|
|
|
return parametersToDelete;
|
|
|
|
|
val element = entry.key
|
|
|
|
|
val descriptor = entry.value
|
|
|
|
|
if (element is KtNamedFunction || element is KtProperty) {
|
|
|
|
|
description = formatClassDescriptor(descriptor.getContainingDeclaration())
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return null;
|
|
|
|
|
assert(element is PsiMethod) { "Invalid element: " + element.getText() }
|
|
|
|
|
|
|
|
|
|
val psiClass = (element as PsiMethod).containingClass ?: error("Invalid element: " + element.getText())
|
|
|
|
|
|
|
|
|
|
description = formatPsiClass(psiClass, true, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
" " + description + "\n"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun formatClass(classDescriptor: DeclarationDescriptor, inCode: Boolean): String {
|
|
|
|
|
val element = DescriptorToSourceUtils.descriptorToDeclaration(classDescriptor)
|
|
|
|
|
if (element is PsiClass) {
|
|
|
|
|
return formatPsiClass((element as PsiClass?)!!, false, inCode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wrapOrSkip(formatClassDescriptor(classDescriptor), inCode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun formatFunction(functionDescriptor: DeclarationDescriptor, inCode: Boolean): String {
|
|
|
|
|
val element = DescriptorToSourceUtils.descriptorToDeclaration(functionDescriptor)
|
|
|
|
|
if (element is PsiMethod) {
|
|
|
|
|
return formatPsiMethod((element as PsiMethod?)!!, false, inCode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return wrapOrSkip(formatFunctionDescriptor(functionDescriptor), inCode)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun formatFunctionDescriptor(functionDescriptor: DeclarationDescriptor): String {
|
|
|
|
|
return DescriptorRenderer.COMPACT.render(functionDescriptor)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun formatPsiMethod(
|
|
|
|
|
psiMethod: PsiMethod,
|
|
|
|
|
showContainingClass: Boolean,
|
|
|
|
|
inCode: Boolean): String {
|
|
|
|
|
var options = PsiFormatUtilBase.SHOW_NAME or PsiFormatUtilBase.SHOW_PARAMETERS or PsiFormatUtilBase.SHOW_TYPE
|
|
|
|
|
if (showContainingClass) {
|
|
|
|
|
//noinspection ConstantConditions
|
|
|
|
|
options = options or PsiFormatUtilBase.SHOW_CONTAINING_CLASS
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var description = PsiFormatUtil.formatMethod(psiMethod, PsiSubstitutor.EMPTY, options, PsiFormatUtilBase.SHOW_TYPE)
|
|
|
|
|
description = wrapOrSkip(description, inCode)
|
|
|
|
|
|
|
|
|
|
return "[Java] " + description
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun formatJavaOrLightMethod(method: PsiMethod): String {
|
|
|
|
|
val originalDeclaration = method.unwrapped
|
|
|
|
|
if (originalDeclaration is KtDeclaration) {
|
|
|
|
|
val bindingContext = originalDeclaration.analyze(BodyResolveMode.FULL)
|
|
|
|
|
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, originalDeclaration)
|
|
|
|
|
|
|
|
|
|
if (descriptor != null) return formatFunctionDescriptor(descriptor)
|
|
|
|
|
}
|
|
|
|
|
return formatPsiMethod(method, false, false)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun formatClass(classOrObject: KtClassOrObject): String {
|
|
|
|
|
val bindingContext = classOrObject.analyze(BodyResolveMode.FULL)
|
|
|
|
|
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject)
|
|
|
|
|
|
|
|
|
|
if (descriptor is ClassDescriptor) return formatClassDescriptor(descriptor)
|
|
|
|
|
return "class " + classOrObject.name!!
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fun checkParametersInMethodHierarchy(parameter: PsiParameter): Collection<PsiElement>? {
|
|
|
|
|
val method = parameter.declarationScope as PsiMethod
|
|
|
|
|
|
|
|
|
|
val parametersToDelete = collectParametersHierarchy(method, parameter)
|
|
|
|
|
if (parametersToDelete.size > 1) {
|
|
|
|
|
if (ApplicationManager.getApplication().isUnitTestMode) {
|
|
|
|
|
return parametersToDelete
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val message = KotlinBundle.message("delete.param.in.method.hierarchy", formatJavaOrLightMethod(method))
|
|
|
|
|
val exitCode = Messages.showOkCancelDialog(
|
|
|
|
|
parameter.project, message, IdeBundle.message("title.warning"), Messages.getQuestionIcon())
|
|
|
|
|
if (exitCode == Messages.OK) {
|
|
|
|
|
return parametersToDelete
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return parametersToDelete;
|
|
|
|
|
return parametersToDelete
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO: generalize breadth-first search
|
|
|
|
|
@NotNull
|
|
|
|
|
private static Set<PsiElement> collectParametersHierarchy(@NotNull PsiMethod method, @NotNull PsiParameter parameter) {
|
|
|
|
|
Deque<PsiMethod> queue = new ArrayDeque<PsiMethod>();
|
|
|
|
|
Set<PsiMethod> visited = new HashSet<PsiMethod>();
|
|
|
|
|
Set<PsiElement> parametersToDelete = new HashSet<PsiElement>();
|
|
|
|
|
private fun collectParametersHierarchy(method: PsiMethod, parameter: PsiParameter): Set<PsiElement> {
|
|
|
|
|
val queue = ArrayDeque<PsiMethod>()
|
|
|
|
|
val visited = HashSet<PsiMethod>()
|
|
|
|
|
val parametersToDelete = HashSet<PsiElement>()
|
|
|
|
|
|
|
|
|
|
queue.add(method);
|
|
|
|
|
queue.add(method)
|
|
|
|
|
while (!queue.isEmpty()) {
|
|
|
|
|
PsiMethod currentMethod = queue.poll();
|
|
|
|
|
val currentMethod = queue.poll()
|
|
|
|
|
|
|
|
|
|
visited.add(currentMethod);
|
|
|
|
|
addParameter(currentMethod, parametersToDelete, parameter);
|
|
|
|
|
visited.add(currentMethod)
|
|
|
|
|
addParameter(currentMethod, parametersToDelete, parameter)
|
|
|
|
|
|
|
|
|
|
for (PsiMethod superMethod : currentMethod.findSuperMethods(true)) {
|
|
|
|
|
for (superMethod in currentMethod.findSuperMethods(true)) {
|
|
|
|
|
if (!visited.contains(superMethod)) {
|
|
|
|
|
queue.offer(superMethod);
|
|
|
|
|
queue.offer(superMethod)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (PsiMethod overrider : OverridingMethodsSearch.search(currentMethod)) {
|
|
|
|
|
for (overrider in OverridingMethodsSearch.search(currentMethod)) {
|
|
|
|
|
if (!visited.contains(overrider)) {
|
|
|
|
|
queue.offer(overrider);
|
|
|
|
|
queue.offer(overrider)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return parametersToDelete;
|
|
|
|
|
return parametersToDelete
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void addParameter(@NotNull PsiMethod method, @NotNull Set<PsiElement> result, @NotNull PsiParameter parameter) {
|
|
|
|
|
int parameterIndex = KtPsiUtilKt.parameterIndex(LightClassUtilsKt.getUnwrapped(parameter));
|
|
|
|
|
private fun addParameter(method: PsiMethod, result: MutableSet<PsiElement>, parameter: PsiParameter) {
|
|
|
|
|
val parameterIndex = parameter.unwrapped!!.parameterIndex()
|
|
|
|
|
|
|
|
|
|
if (method instanceof KtLightMethod) {
|
|
|
|
|
KtDeclaration declaration = ((KtLightMethod) method).getKotlinOrigin();
|
|
|
|
|
if (declaration instanceof KtFunction) {
|
|
|
|
|
result.add(((KtFunction) declaration).getValueParameters().get(parameterIndex));
|
|
|
|
|
if (method is KtLightMethod) {
|
|
|
|
|
val declaration = method.kotlinOrigin
|
|
|
|
|
if (declaration is KtFunction) {
|
|
|
|
|
result.add(declaration.valueParameters[parameterIndex])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
result.add(method.getParameterList().getParameters()[parameterIndex]);
|
|
|
|
|
result.add(method.parameterList.parameters[parameterIndex])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public interface SelectElementCallback {
|
|
|
|
|
void run(@Nullable PsiElement element);
|
|
|
|
|
@Throws(IntroduceRefactoringException::class)
|
|
|
|
|
fun selectElement(
|
|
|
|
|
editor: Editor,
|
|
|
|
|
file: KtFile,
|
|
|
|
|
elementKinds: Collection<CodeInsightUtils.ElementKind>,
|
|
|
|
|
callback: (PsiElement?) -> Unit) {
|
|
|
|
|
selectElement(editor, file, true, elementKinds, callback)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static void selectElement(
|
|
|
|
|
@NotNull Editor editor,
|
|
|
|
|
@NotNull KtFile file,
|
|
|
|
|
@NotNull Collection<CodeInsightUtils.ElementKind> elementKinds,
|
|
|
|
|
@NotNull SelectElementCallback callback
|
|
|
|
|
) throws IntroduceRefactoringException {
|
|
|
|
|
selectElement(editor, file, true, elementKinds, callback);
|
|
|
|
|
}
|
|
|
|
|
@Throws(IntroduceRefactoringException::class)
|
|
|
|
|
fun selectElement(editor: Editor,
|
|
|
|
|
file: KtFile,
|
|
|
|
|
failOnEmptySuggestion: Boolean,
|
|
|
|
|
elementKinds: Collection<CodeInsightUtils.ElementKind>,
|
|
|
|
|
callback: (PsiElement?) -> Unit) {
|
|
|
|
|
if (editor.selectionModel.hasSelection()) {
|
|
|
|
|
var selectionStart = editor.selectionModel.selectionStart
|
|
|
|
|
var selectionEnd = editor.selectionModel.selectionEnd
|
|
|
|
|
|
|
|
|
|
public static void selectElement(@NotNull Editor editor,
|
|
|
|
|
@NotNull KtFile file,
|
|
|
|
|
boolean failOnEmptySuggestion,
|
|
|
|
|
@NotNull Collection<CodeInsightUtils.ElementKind> elementKinds,
|
|
|
|
|
@NotNull SelectElementCallback callback
|
|
|
|
|
) throws IntroduceRefactoringException {
|
|
|
|
|
if (editor.getSelectionModel().hasSelection()) {
|
|
|
|
|
int selectionStart = editor.getSelectionModel().getSelectionStart();
|
|
|
|
|
int selectionEnd = editor.getSelectionModel().getSelectionEnd();
|
|
|
|
|
var firstElement: PsiElement = file.findElementAt(selectionStart)!!
|
|
|
|
|
var lastElement: PsiElement = file.findElementAt(selectionEnd - 1)!!
|
|
|
|
|
|
|
|
|
|
PsiElement firstElement = file.findElementAt(selectionStart);
|
|
|
|
|
PsiElement lastElement = file.findElementAt(selectionEnd - 1);
|
|
|
|
|
|
|
|
|
|
if (PsiTreeUtil.getParentOfType(firstElement, KtLiteralStringTemplateEntry.class, KtEscapeStringTemplateEntry.class) == null
|
|
|
|
|
&& PsiTreeUtil.getParentOfType(lastElement, KtLiteralStringTemplateEntry.class, KtEscapeStringTemplateEntry.class) == null) {
|
|
|
|
|
firstElement = PsiUtilsKt.getNextSiblingIgnoringWhitespaceAndComments(firstElement, true);
|
|
|
|
|
lastElement = PsiUtilsKt.getPrevSiblingIgnoringWhitespaceAndComments(lastElement, true);
|
|
|
|
|
selectionStart = firstElement.getTextRange().getStartOffset();
|
|
|
|
|
selectionEnd = lastElement.getTextRange().getEndOffset();
|
|
|
|
|
if (PsiTreeUtil.getParentOfType(firstElement, KtLiteralStringTemplateEntry::class.java, KtEscapeStringTemplateEntry::class.java) == null && PsiTreeUtil.getParentOfType(lastElement, KtLiteralStringTemplateEntry::class.java, KtEscapeStringTemplateEntry::class.java) == null) {
|
|
|
|
|
firstElement = firstElement.getNextSiblingIgnoringWhitespaceAndComments(true)!!
|
|
|
|
|
lastElement = lastElement.getPrevSiblingIgnoringWhitespaceAndComments(true)!!
|
|
|
|
|
selectionStart = firstElement.textRange.startOffset
|
|
|
|
|
selectionEnd = lastElement.textRange.endOffset
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (CodeInsightUtils.ElementKind elementKind : elementKinds) {
|
|
|
|
|
PsiElement element = findElement(file, selectionStart, selectionEnd, failOnEmptySuggestion, elementKind);
|
|
|
|
|
for (elementKind in elementKinds) {
|
|
|
|
|
val element = findElement(file, selectionStart, selectionEnd, failOnEmptySuggestion, elementKind)
|
|
|
|
|
if (element != null) {
|
|
|
|
|
callback.run(element);
|
|
|
|
|
return;
|
|
|
|
|
callback(element)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
callback.run(null);
|
|
|
|
|
callback(null)
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
int offset = editor.getCaretModel().getOffset();
|
|
|
|
|
smartSelectElement(editor, file, offset, failOnEmptySuggestion, elementKinds, callback);
|
|
|
|
|
val offset = editor.caretModel.offset
|
|
|
|
|
smartSelectElement(editor, file, offset, failOnEmptySuggestion, elementKinds, callback)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static List<KtElement> getSmartSelectSuggestions(
|
|
|
|
|
@NotNull PsiFile file,
|
|
|
|
|
int offset,
|
|
|
|
|
@NotNull CodeInsightUtils.ElementKind elementKind
|
|
|
|
|
) throws IntroduceRefactoringException {
|
|
|
|
|
@Throws(IntroduceRefactoringException::class)
|
|
|
|
|
fun getSmartSelectSuggestions(
|
|
|
|
|
file: PsiFile,
|
|
|
|
|
offset: Int,
|
|
|
|
|
elementKind: CodeInsightUtils.ElementKind): List<KtElement> {
|
|
|
|
|
if (offset < 0) {
|
|
|
|
|
return new ArrayList<KtElement>();
|
|
|
|
|
return ArrayList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PsiElement element = file.findElementAt(offset);
|
|
|
|
|
if (element == null) {
|
|
|
|
|
return new ArrayList<KtElement>();
|
|
|
|
|
}
|
|
|
|
|
if (element instanceof PsiWhiteSpace) {
|
|
|
|
|
return getSmartSelectSuggestions(file, offset - 1, elementKind);
|
|
|
|
|
var element: PsiElement? = file.findElementAt(offset) ?: return ArrayList()
|
|
|
|
|
if (element is PsiWhiteSpace) {
|
|
|
|
|
return getSmartSelectSuggestions(file, offset - 1, elementKind)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
List<KtElement> elements = new ArrayList<KtElement>();
|
|
|
|
|
while (element != null && !(element instanceof KtBlockExpression && !(element.getParent() instanceof KtFunctionLiteral)) &&
|
|
|
|
|
!(element instanceof KtNamedFunction)
|
|
|
|
|
&& !(element instanceof KtClassBody)) {
|
|
|
|
|
boolean addElement = false;
|
|
|
|
|
boolean keepPrevious = true;
|
|
|
|
|
val elements = ArrayList<KtElement>()
|
|
|
|
|
while (element != null && !(element is KtBlockExpression && element.parent !is KtFunctionLiteral) &&
|
|
|
|
|
element !is KtNamedFunction
|
|
|
|
|
&& element !is KtClassBody) {
|
|
|
|
|
var addElement = false
|
|
|
|
|
var keepPrevious = true
|
|
|
|
|
|
|
|
|
|
if (element instanceof KtTypeElement) {
|
|
|
|
|
addElement = elementKind == CodeInsightUtils.ElementKind.TYPE_ELEMENT;
|
|
|
|
|
if (element is KtTypeElement) {
|
|
|
|
|
addElement = elementKind == CodeInsightUtils.ElementKind.TYPE_ELEMENT
|
|
|
|
|
if (!addElement) {
|
|
|
|
|
keepPrevious = false;
|
|
|
|
|
keepPrevious = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (element instanceof KtExpression && !(element instanceof KtStatementExpression)) {
|
|
|
|
|
addElement = elementKind == CodeInsightUtils.ElementKind.EXPRESSION;
|
|
|
|
|
else if (element is KtExpression && element !is KtStatementExpression) {
|
|
|
|
|
addElement = elementKind == CodeInsightUtils.ElementKind.EXPRESSION
|
|
|
|
|
|
|
|
|
|
if (addElement) {
|
|
|
|
|
if (element instanceof KtParenthesizedExpression) {
|
|
|
|
|
addElement = false;
|
|
|
|
|
if (element is KtParenthesizedExpression) {
|
|
|
|
|
addElement = false
|
|
|
|
|
}
|
|
|
|
|
else if (KtPsiUtil.isLabelIdentifierExpression(element)) {
|
|
|
|
|
addElement = false;
|
|
|
|
|
addElement = false
|
|
|
|
|
}
|
|
|
|
|
else if (element.getParent() instanceof KtQualifiedExpression) {
|
|
|
|
|
KtQualifiedExpression qualifiedExpression = (KtQualifiedExpression) element.getParent();
|
|
|
|
|
if (qualifiedExpression.getReceiverExpression() != element) {
|
|
|
|
|
addElement = false;
|
|
|
|
|
else if (element.parent is KtQualifiedExpression) {
|
|
|
|
|
val qualifiedExpression = element.parent as KtQualifiedExpression
|
|
|
|
|
if (qualifiedExpression.receiverExpression !== element) {
|
|
|
|
|
addElement = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (element.getParent() instanceof KtCallElement
|
|
|
|
|
|| element.getParent() instanceof KtThisExpression
|
|
|
|
|
|| PsiTreeUtil.getParentOfType(element, KtSuperExpression.class) != null) {
|
|
|
|
|
addElement = false;
|
|
|
|
|
else if (element.parent is KtCallElement
|
|
|
|
|
|| element.parent is KtThisExpression
|
|
|
|
|
|| PsiTreeUtil.getParentOfType(element, KtSuperExpression::class.java) != null) {
|
|
|
|
|
addElement = false
|
|
|
|
|
}
|
|
|
|
|
else if (element.getParent() instanceof KtOperationExpression) {
|
|
|
|
|
KtOperationExpression operationExpression = (KtOperationExpression) element.getParent();
|
|
|
|
|
if (operationExpression.getOperationReference() == element) {
|
|
|
|
|
addElement = false;
|
|
|
|
|
else if (element.parent is KtOperationExpression) {
|
|
|
|
|
val operationExpression = element.parent as KtOperationExpression
|
|
|
|
|
if (operationExpression.operationReference === element) {
|
|
|
|
|
addElement = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (addElement) {
|
|
|
|
|
KtExpression expression = (KtExpression)element;
|
|
|
|
|
BindingContext bindingContext = ResolutionUtils.analyze(expression, BodyResolveMode.FULL);
|
|
|
|
|
KotlinType expressionType = bindingContext.getType(expression);
|
|
|
|
|
val bindingContext = element.analyze(BodyResolveMode.FULL)
|
|
|
|
|
val expressionType = bindingContext.getType(element)
|
|
|
|
|
if (expressionType == null || KotlinBuiltIns.isUnit(expressionType)) {
|
|
|
|
|
addElement = false;
|
|
|
|
|
addElement = false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (addElement) {
|
|
|
|
|
elements.add((KtElement) element);
|
|
|
|
|
elements.add(element as KtElement)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!keepPrevious) {
|
|
|
|
|
elements.clear();
|
|
|
|
|
elements.clear()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
element = element.getParent();
|
|
|
|
|
element = element.parent
|
|
|
|
|
}
|
|
|
|
|
return elements;
|
|
|
|
|
return elements
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void smartSelectElement(
|
|
|
|
|
@NotNull Editor editor,
|
|
|
|
|
@NotNull final PsiFile file,
|
|
|
|
|
final int offset,
|
|
|
|
|
boolean failOnEmptySuggestion,
|
|
|
|
|
@NotNull Collection<CodeInsightUtils.ElementKind> elementKinds,
|
|
|
|
|
@NotNull final SelectElementCallback callback
|
|
|
|
|
) throws IntroduceRefactoringException {
|
|
|
|
|
List<KtElement> elements = CollectionsKt.flatMap(
|
|
|
|
|
elementKinds,
|
|
|
|
|
new Function1<CodeInsightUtils.ElementKind, Iterable<? extends KtElement>>() {
|
|
|
|
|
@Override
|
|
|
|
|
public Iterable<? extends KtElement> invoke(CodeInsightUtils.ElementKind kind) {
|
|
|
|
|
return getSmartSelectSuggestions(file, offset, kind);
|
|
|
|
|
}
|
|
|
|
|
@Throws(IntroduceRefactoringException::class)
|
|
|
|
|
private fun smartSelectElement(
|
|
|
|
|
editor: Editor,
|
|
|
|
|
file: PsiFile,
|
|
|
|
|
offset: Int,
|
|
|
|
|
failOnEmptySuggestion: Boolean,
|
|
|
|
|
elementKinds: Collection<CodeInsightUtils.ElementKind>,
|
|
|
|
|
callback: (PsiElement?) -> Unit) {
|
|
|
|
|
val elements = elementKinds.flatMap { kind -> getSmartSelectSuggestions(file, offset, kind) }
|
|
|
|
|
if (elements.size == 0) {
|
|
|
|
|
if (failOnEmptySuggestion)
|
|
|
|
|
throw IntroduceRefactoringException(
|
|
|
|
|
KotlinRefactoringBundle.message("cannot.refactor.not.expression"))
|
|
|
|
|
callback(null)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (elements.size == 1 || ApplicationManager.getApplication().isUnitTestMode) {
|
|
|
|
|
callback(elements[0])
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val model = DefaultListModel<PsiElement>()
|
|
|
|
|
for (element in elements) {
|
|
|
|
|
model.addElement(element)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val highlighter = ScopeHighlighter(editor)
|
|
|
|
|
|
|
|
|
|
val list = JBList(model)
|
|
|
|
|
|
|
|
|
|
list.setCellRenderer(object : DefaultListCellRenderer() {
|
|
|
|
|
override fun getListCellRendererComponent(list: JList<*>, value: Any?, index: Int, isSelected: Boolean, cellHasFocus: Boolean): Component {
|
|
|
|
|
val rendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus)
|
|
|
|
|
val element = value as KtElement?
|
|
|
|
|
if (element!!.isValid) {
|
|
|
|
|
text = getExpressionShortText(element)
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
if (elements.size() == 0) {
|
|
|
|
|
if (failOnEmptySuggestion) throw new IntroduceRefactoringException(
|
|
|
|
|
KotlinRefactoringBundle.message("cannot.refactor.not.expression"));
|
|
|
|
|
callback.run(null);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (elements.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
|
|
|
|
|
callback.run(elements.get(0));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final DefaultListModel model = new DefaultListModel();
|
|
|
|
|
for (PsiElement element : elements) {
|
|
|
|
|
model.addElement(element);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final ScopeHighlighter highlighter = new ScopeHighlighter(editor);
|
|
|
|
|
|
|
|
|
|
final JList list = new JBList(model);
|
|
|
|
|
|
|
|
|
|
list.setCellRenderer(new DefaultListCellRenderer() {
|
|
|
|
|
@Override
|
|
|
|
|
public Component getListCellRendererComponent(@NotNull JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
|
|
|
|
|
Component rendererComponent = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
|
|
|
|
|
KtElement element = (KtElement) value;
|
|
|
|
|
if (element.isValid()) {
|
|
|
|
|
setText(getExpressionShortText(element));
|
|
|
|
|
}
|
|
|
|
|
return rendererComponent;
|
|
|
|
|
return rendererComponent
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
list.addListSelectionListener(new ListSelectionListener() {
|
|
|
|
|
@Override
|
|
|
|
|
public void valueChanged(@NotNull ListSelectionEvent e) {
|
|
|
|
|
highlighter.dropHighlight();
|
|
|
|
|
int selectedIndex = list.getSelectedIndex();
|
|
|
|
|
if (selectedIndex < 0) return;
|
|
|
|
|
KtElement expression = (KtElement) model.get(selectedIndex);
|
|
|
|
|
List<PsiElement> toExtract = new ArrayList<PsiElement>();
|
|
|
|
|
toExtract.add(expression);
|
|
|
|
|
highlighter.highlight(expression, toExtract);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
list.addListSelectionListener(ListSelectionListener {
|
|
|
|
|
highlighter.dropHighlight()
|
|
|
|
|
val selectedIndex = list.getSelectedIndex()
|
|
|
|
|
if (selectedIndex < 0) return@ListSelectionListener
|
|
|
|
|
val toExtract = ArrayList<PsiElement>()
|
|
|
|
|
toExtract.add(model.get(selectedIndex))
|
|
|
|
|
highlighter.highlight(model.get(selectedIndex), toExtract)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
String title = "Elements";
|
|
|
|
|
if (elementKinds.size() == 1) {
|
|
|
|
|
switch (elementKinds.iterator().next()) {
|
|
|
|
|
case EXPRESSION:
|
|
|
|
|
title = "Expressions";
|
|
|
|
|
break;
|
|
|
|
|
case TYPE_ELEMENT:
|
|
|
|
|
case TYPE_CONSTRUCTOR:
|
|
|
|
|
title = "Types";
|
|
|
|
|
break;
|
|
|
|
|
var title = "Elements"
|
|
|
|
|
if (elementKinds.size == 1) {
|
|
|
|
|
when (elementKinds.iterator().next()) {
|
|
|
|
|
CodeInsightUtils.ElementKind.EXPRESSION -> title = "Expressions"
|
|
|
|
|
CodeInsightUtils.ElementKind.TYPE_ELEMENT, CodeInsightUtils.ElementKind.TYPE_CONSTRUCTOR -> title = "Types"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JBPopupFactory.getInstance().createListPopupBuilder(list).
|
|
|
|
|
setTitle(title).setMovable(false).setResizable(false).
|
|
|
|
|
setRequestFocus(true).setItemChoosenCallback(new Runnable() {
|
|
|
|
|
@Override
|
|
|
|
|
public void run() {
|
|
|
|
|
callback.run((KtElement) list.getSelectedValue());
|
|
|
|
|
JBPopupFactory.getInstance().createListPopupBuilder(list).setTitle(title).setMovable(false).setResizable(false).setRequestFocus(true).setItemChoosenCallback { callback(list.getSelectedValue() as KtElement) }.addListener(object : JBPopupAdapter() {
|
|
|
|
|
override fun onClosed(event: LightweightWindowEvent?) {
|
|
|
|
|
highlighter.dropHighlight()
|
|
|
|
|
}
|
|
|
|
|
}).addListener(new JBPopupAdapter() {
|
|
|
|
|
@Override
|
|
|
|
|
public void onClosed(LightweightWindowEvent event) {
|
|
|
|
|
highlighter.dropHighlight();
|
|
|
|
|
}
|
|
|
|
|
}).createPopup().showInBestPositionFor(editor);
|
|
|
|
|
}).createPopup().showInBestPositionFor(editor)
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static String getExpressionShortText(@NotNull KtElement element) {
|
|
|
|
|
String text = ElementRenderingUtilsKt.renderTrimmed(element);
|
|
|
|
|
int firstNewLinePos = text.indexOf('\n');
|
|
|
|
|
String trimmedText = text.substring(0, firstNewLinePos != -1 ? firstNewLinePos : Math.min(100, text.length()));
|
|
|
|
|
if (trimmedText.length() != text.length()) trimmedText += " ...";
|
|
|
|
|
return trimmedText;
|
|
|
|
|
fun getExpressionShortText(element: KtElement): String {
|
|
|
|
|
val text = element.renderTrimmed()
|
|
|
|
|
val firstNewLinePos = text.indexOf('\n')
|
|
|
|
|
var trimmedText = text.substring(0, if (firstNewLinePos != -1) firstNewLinePos else Math.min(100, text.length))
|
|
|
|
|
if (trimmedText.length != text.length) trimmedText += " ..."
|
|
|
|
|
return trimmedText
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Nullable
|
|
|
|
|
private static PsiElement findElement(
|
|
|
|
|
@NotNull KtFile file,
|
|
|
|
|
int startOffset,
|
|
|
|
|
int endOffset,
|
|
|
|
|
boolean failOnNoExpression,
|
|
|
|
|
@NotNull CodeInsightUtils.ElementKind elementKind
|
|
|
|
|
) throws IntroduceRefactoringException {
|
|
|
|
|
PsiElement element = CodeInsightUtils.findElement(file, startOffset, endOffset, elementKind);
|
|
|
|
|
@Throws(IntroduceRefactoringException::class)
|
|
|
|
|
private fun findElement(
|
|
|
|
|
file: KtFile,
|
|
|
|
|
startOffset: Int,
|
|
|
|
|
endOffset: Int,
|
|
|
|
|
failOnNoExpression: Boolean,
|
|
|
|
|
elementKind: CodeInsightUtils.ElementKind): PsiElement? {
|
|
|
|
|
var element = CodeInsightUtils.findElement(file, startOffset, endOffset, elementKind)
|
|
|
|
|
if (element == null && elementKind == CodeInsightUtils.ElementKind.EXPRESSION) {
|
|
|
|
|
element = IntroduceUtilKt.findExpressionOrStringFragment(file, startOffset, endOffset);
|
|
|
|
|
element = findExpressionOrStringFragment(file, startOffset, endOffset)
|
|
|
|
|
}
|
|
|
|
|
if (element == null) {
|
|
|
|
|
//todo: if it's infix expression => add (), then commit document then return new created expression
|
|
|
|
|
|
|
|
|
|
if (failOnNoExpression) {
|
|
|
|
|
throw new IntroduceRefactoringException(KotlinRefactoringBundle.message("cannot.refactor.not.expression"));
|
|
|
|
|
throw IntroduceRefactoringException(KotlinRefactoringBundle.message("cannot.refactor.not.expression"))
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
return element;
|
|
|
|
|
return element
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static class IntroduceRefactoringException extends RuntimeException {
|
|
|
|
|
private final String myMessage;
|
|
|
|
|
|
|
|
|
|
public IntroduceRefactoringException(String message) {
|
|
|
|
|
myMessage = message;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String getMessage() {
|
|
|
|
|
return myMessage;
|
|
|
|
|
}
|
|
|
|
|
class IntroduceRefactoringException(private val myMessage: String) : RuntimeException() {
|
|
|
|
|
override val message: String?
|
|
|
|
|
get() = myMessage
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|