OverrideImplementMethodsHandler: J2K
This commit is contained in:
@@ -26,7 +26,7 @@ import org.jetbrains.kotlin.psi.JetWithExpressionInitializer
|
|||||||
import org.jetbrains.kotlin.psi.JetProperty
|
import org.jetbrains.kotlin.psi.JetProperty
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||||
|
|
||||||
private fun moveCaretIntoGeneratedElement(editor: Editor, element: PsiElement): Boolean {
|
public fun moveCaretIntoGeneratedElement(editor: Editor, element: PsiElement): Boolean {
|
||||||
// Inspired by GenerateMembersUtils.positionCaret()
|
// Inspired by GenerateMembersUtils.positionCaret()
|
||||||
|
|
||||||
if (element is JetDeclarationWithBody && element.hasBody()) {
|
if (element is JetDeclarationWithBody && element.hasBody()) {
|
||||||
|
|||||||
+258
-299
@@ -14,339 +14,298 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.idea.core.codeInsight;
|
package org.jetbrains.kotlin.idea.core.codeInsight
|
||||||
|
|
||||||
import com.intellij.codeInsight.hint.HintManager;
|
import com.intellij.codeInsight.hint.HintManager
|
||||||
import com.intellij.ide.util.MemberChooser;
|
import com.intellij.ide.util.MemberChooser
|
||||||
import com.intellij.lang.ASTNode;
|
import com.intellij.lang.ASTNode
|
||||||
import com.intellij.lang.LanguageCodeInsightActionHandler;
|
import com.intellij.lang.LanguageCodeInsightActionHandler
|
||||||
import com.intellij.openapi.application.ApplicationManager;
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.ui.DialogWrapper;
|
import com.intellij.openapi.ui.DialogWrapper
|
||||||
import com.intellij.openapi.util.Condition;
|
import com.intellij.openapi.util.Condition
|
||||||
import com.intellij.openapi.util.text.StringUtil;
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.psi.*;
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
|
import org.jetbrains.kotlin.idea.caches.resolve.*
|
||||||
import org.jetbrains.kotlin.descriptors.*;
|
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
|
import org.jetbrains.kotlin.idea.quickfix.*
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde;
|
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||||
import org.jetbrains.kotlin.idea.quickfix.QuickfixPackage;
|
import org.jetbrains.kotlin.idea.util.ShortenReferences
|
||||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers;
|
import org.jetbrains.kotlin.psi.*
|
||||||
import org.jetbrains.kotlin.idea.util.ShortenReferences;
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||||
import org.jetbrains.kotlin.psi.*;
|
import org.jetbrains.kotlin.renderer.DescriptorRendererBuilder
|
||||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
|
import org.jetbrains.kotlin.renderer.NameShortness
|
||||||
import org.jetbrains.kotlin.renderer.DescriptorRendererBuilder;
|
import org.jetbrains.kotlin.types.JetType
|
||||||
import org.jetbrains.kotlin.renderer.NameShortness;
|
|
||||||
import org.jetbrains.kotlin.types.JetType;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList
|
||||||
import java.util.Collections;
|
import java.util.Collections
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.jetbrains.kotlin.psi.PsiPackage.JetPsiFactory;
|
import org.jetbrains.kotlin.psi.JetPsiFactory
|
||||||
|
|
||||||
public abstract class OverrideImplementMethodsHandler implements LanguageCodeInsightActionHandler {
|
public abstract class OverrideImplementMethodsHandler : LanguageCodeInsightActionHandler {
|
||||||
|
|
||||||
private static final DescriptorRenderer OVERRIDE_RENDERER = new DescriptorRendererBuilder()
|
public fun collectMethodsToGenerate(classOrObject: JetClassOrObject): Set<CallableMemberDescriptor> {
|
||||||
.setRenderDefaultValues(false)
|
val descriptor = classOrObject.resolveToDescriptor()
|
||||||
.setModifiers(DescriptorRenderer.Modifier.OVERRIDE)
|
if (descriptor is ClassDescriptor) {
|
||||||
.setWithDefinedIn(false)
|
return collectMethodsToGenerate(descriptor)
|
||||||
.setNameShortness(NameShortness.SOURCE_CODE_QUALIFIED)
|
|
||||||
.setOverrideRenderingPolicy(DescriptorRenderer.OverrideRenderingPolicy.RENDER_OVERRIDE)
|
|
||||||
.setUnitReturnType(false)
|
|
||||||
.setTypeNormalizer(IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private static final Logger LOG = Logger.getInstance(OverrideImplementMethodsHandler.class.getCanonicalName());
|
|
||||||
|
|
||||||
public static List<DescriptorClassMember> membersFromDescriptors(
|
|
||||||
JetFile file, Iterable<CallableMemberDescriptor> missingImplementations
|
|
||||||
) {
|
|
||||||
List<DescriptorClassMember> members = new ArrayList<DescriptorClassMember>();
|
|
||||||
for (CallableMemberDescriptor memberDescriptor : missingImplementations) {
|
|
||||||
PsiElement declaration = DescriptorToSourceUtilsIde.INSTANCE$.getAnyDeclaration(file.getProject(), memberDescriptor);
|
|
||||||
if (declaration == null) {
|
|
||||||
LOG.error("Can not find declaration for descriptor " + memberDescriptor);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
DescriptorClassMember member = new DescriptorClassMember(declaration, memberDescriptor);
|
|
||||||
members.add(member);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return members;
|
return emptySet()
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void generateMethods(
|
protected abstract fun collectMethodsToGenerate(descriptor: ClassDescriptor): Set<CallableMemberDescriptor>
|
||||||
@NotNull final Editor editor,
|
|
||||||
@NotNull final JetClassOrObject classOrObject,
|
|
||||||
@NotNull final List<DescriptorClassMember> selectedElements
|
|
||||||
) {
|
|
||||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
JetClassBody body = classOrObject.getBody();
|
|
||||||
if (body == null) {
|
|
||||||
JetPsiFactory psiFactory = JetPsiFactory(classOrObject);
|
|
||||||
classOrObject.add(psiFactory.createWhiteSpace());
|
|
||||||
body = (JetClassBody) classOrObject.add(psiFactory.createEmptyClassBody());
|
|
||||||
}
|
|
||||||
|
|
||||||
PsiElement afterAnchor = findInsertAfterAnchor(editor, body);
|
private fun showOverrideImplementChooser(project: Project, members: Array<DescriptorClassMember>): MemberChooser<DescriptorClassMember>? {
|
||||||
|
val chooser = MemberChooser(members, true, true, project)
|
||||||
if (afterAnchor == null) return;
|
chooser.setTitle(getChooserTitle())
|
||||||
|
chooser.show()
|
||||||
PsiElement firstGenerated = null;
|
if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) return null
|
||||||
|
return chooser
|
||||||
List<JetElement> elementsToCompact = new ArrayList<JetElement>();
|
|
||||||
for (JetElement element : generateOverridingMembers(selectedElements, classOrObject)) {
|
|
||||||
PsiElement added = body.addAfter(element, afterAnchor);
|
|
||||||
|
|
||||||
if (firstGenerated == null) {
|
|
||||||
firstGenerated = added;
|
|
||||||
}
|
|
||||||
|
|
||||||
afterAnchor = added;
|
|
||||||
elementsToCompact.add((JetElement) added);
|
|
||||||
}
|
|
||||||
|
|
||||||
ShortenReferences.DEFAULT.process(elementsToCompact);
|
|
||||||
|
|
||||||
if (firstGenerated == null) return;
|
|
||||||
|
|
||||||
Project project = classOrObject.getProject();
|
|
||||||
SmartPsiElementPointer<PsiElement> pointer = SmartPointerManager.getInstance(project).createSmartPsiElementPointer(firstGenerated);
|
|
||||||
|
|
||||||
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument());
|
|
||||||
|
|
||||||
PsiElement element = pointer.getElement();
|
|
||||||
if (element != null) {
|
|
||||||
QuickfixPackage.moveCaretIntoGeneratedElement(editor, element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
protected abstract fun getChooserTitle(): String
|
||||||
private static PsiElement findInsertAfterAnchor(Editor editor, final JetClassBody body) {
|
|
||||||
PsiElement afterAnchor = body.getLBrace();
|
|
||||||
if (afterAnchor == null) return null;
|
|
||||||
|
|
||||||
int offset = editor.getCaretModel().getOffset();
|
override fun isValidFor(editor: Editor, file: PsiFile): Boolean {
|
||||||
PsiElement offsetCursorElement = PsiTreeUtil.findFirstParent(
|
if (file !is JetFile) {
|
||||||
body.getContainingFile().findElementAt(offset),
|
return false
|
||||||
new Condition<PsiElement>() {
|
|
||||||
@Override
|
|
||||||
public boolean value(PsiElement element) {
|
|
||||||
return element.getParent() == body;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (offsetCursorElement instanceof PsiWhiteSpace) {
|
|
||||||
return removeAfterOffset(offset, (PsiWhiteSpace) offsetCursorElement);
|
|
||||||
}
|
}
|
||||||
|
val elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset())
|
||||||
if (offsetCursorElement != null && offsetCursorElement != body.getRBrace()) {
|
val classOrObject = PsiTreeUtil.getParentOfType<JetClassOrObject>(elementAtCaret, javaClass<JetClassOrObject>())
|
||||||
return offsetCursorElement;
|
return classOrObject != null
|
||||||
}
|
|
||||||
|
|
||||||
return afterAnchor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static PsiElement removeAfterOffset(int offset, PsiWhiteSpace whiteSpace) {
|
protected abstract fun getNoMethodsFoundHint(): String
|
||||||
ASTNode spaceNode = whiteSpace.getNode();
|
|
||||||
if (spaceNode.getTextRange().contains(offset)) {
|
|
||||||
String beforeWhiteSpaceText = spaceNode.getText().substring(0, offset - spaceNode.getStartOffset());
|
|
||||||
if (!StringUtil.containsLineBreak(beforeWhiteSpaceText)) {
|
|
||||||
// Prevent insertion on same line
|
|
||||||
beforeWhiteSpaceText += "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
JetPsiFactory factory = JetPsiFactory(whiteSpace.getProject());
|
public fun invoke(project: Project, editor: Editor, file: PsiFile, implementAll: Boolean) {
|
||||||
|
val elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset())
|
||||||
|
val classOrObject = PsiTreeUtil.getParentOfType<JetClassOrObject>(elementAtCaret, javaClass<JetClassOrObject>())
|
||||||
|
|
||||||
PsiElement insertAfter = whiteSpace.getPrevSibling();
|
assert(classOrObject != null)
|
||||||
whiteSpace.delete();
|
|
||||||
|
|
||||||
PsiElement beforeSpace = factory.createWhiteSpace(beforeWhiteSpaceText);
|
val missingImplementations = collectMethodsToGenerate(classOrObject)
|
||||||
insertAfter.getParent().addAfter(beforeSpace, insertAfter);
|
|
||||||
|
|
||||||
return insertAfter.getNextSibling();
|
|
||||||
}
|
|
||||||
|
|
||||||
return whiteSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<JetElement> generateOverridingMembers(List<DescriptorClassMember> selectedElements, JetClassOrObject classOrObject) {
|
|
||||||
List<JetElement> overridingMembers = new ArrayList<JetElement>();
|
|
||||||
for (DescriptorClassMember selectedElement : selectedElements) {
|
|
||||||
DeclarationDescriptor descriptor = selectedElement.getDescriptor();
|
|
||||||
if (descriptor instanceof SimpleFunctionDescriptor) {
|
|
||||||
overridingMembers.add(overrideFunction(classOrObject, (SimpleFunctionDescriptor) descriptor));
|
|
||||||
}
|
|
||||||
else if (descriptor instanceof PropertyDescriptor) {
|
|
||||||
overridingMembers.add(overrideProperty(classOrObject, (PropertyDescriptor) descriptor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return overridingMembers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static JetElement overrideProperty(@NotNull JetClassOrObject classOrObject, @NotNull PropertyDescriptor descriptor) {
|
|
||||||
PropertyDescriptor newDescriptor = (PropertyDescriptor) descriptor.copy(
|
|
||||||
descriptor.getContainingDeclaration(),
|
|
||||||
Modality.OPEN,
|
|
||||||
descriptor.getVisibility(),
|
|
||||||
descriptor.getKind(),
|
|
||||||
/* copyOverrides = */ true);
|
|
||||||
newDescriptor.addOverriddenDescriptor(descriptor);
|
|
||||||
|
|
||||||
StringBuilder body = new StringBuilder();
|
|
||||||
body.append("\nget()");
|
|
||||||
body.append(" = ");
|
|
||||||
body.append(generateUnsupportedOrSuperCall(classOrObject, descriptor));
|
|
||||||
if (descriptor.isVar()) {
|
|
||||||
body.append("\nset(value) {}");
|
|
||||||
}
|
|
||||||
return JetPsiFactory(classOrObject.getProject()).createProperty(OVERRIDE_RENDERER.render(newDescriptor) + body);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private static JetNamedFunction overrideFunction(@NotNull JetClassOrObject classOrObject, @NotNull FunctionDescriptor descriptor) {
|
|
||||||
FunctionDescriptor newDescriptor = descriptor.copy(
|
|
||||||
descriptor.getContainingDeclaration(),
|
|
||||||
Modality.OPEN,
|
|
||||||
descriptor.getVisibility(),
|
|
||||||
descriptor.getKind(),
|
|
||||||
/* copyOverrides = */ true);
|
|
||||||
newDescriptor.addOverriddenDescriptor(descriptor);
|
|
||||||
|
|
||||||
|
|
||||||
JetType returnType = descriptor.getReturnType();
|
|
||||||
KotlinBuiltIns builtIns = KotlinBuiltIns.getInstance();
|
|
||||||
|
|
||||||
boolean returnsNotUnit = returnType != null && !builtIns.getUnitType().equals(returnType);
|
|
||||||
boolean isAbstract = descriptor.getModality() == Modality.ABSTRACT;
|
|
||||||
|
|
||||||
String delegation = generateUnsupportedOrSuperCall(classOrObject, descriptor);
|
|
||||||
|
|
||||||
String body = "{" + (returnsNotUnit && !isAbstract ? "return " : "") + delegation + "}";
|
|
||||||
|
|
||||||
return JetPsiFactory(classOrObject.getProject()).createFunction(OVERRIDE_RENDERER.render(newDescriptor) + body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String generateUnsupportedOrSuperCall(@NotNull JetClassOrObject classOrObject, @NotNull CallableMemberDescriptor descriptor) {
|
|
||||||
boolean isAbstract = descriptor.getModality() == Modality.ABSTRACT;
|
|
||||||
if (isAbstract) {
|
|
||||||
return "throw UnsupportedOperationException()";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
builder.append("super");
|
|
||||||
if (classOrObject.getDelegationSpecifiers().size() > 1) {
|
|
||||||
builder.append("<").append(DescriptorRenderer.COMPACT.renderName(descriptor.getContainingDeclaration().getName())).append(">");
|
|
||||||
}
|
|
||||||
builder.append(".").append(DescriptorRenderer.COMPACT.renderName(descriptor.getName()));
|
|
||||||
|
|
||||||
if (descriptor instanceof FunctionDescriptor) {
|
|
||||||
builder.append("(");
|
|
||||||
boolean first = true;
|
|
||||||
for (ValueParameterDescriptor parameterDescriptor : descriptor.getValueParameters()) {
|
|
||||||
if (!first) {
|
|
||||||
builder.append(", ");
|
|
||||||
}
|
|
||||||
first = false;
|
|
||||||
builder.append(DescriptorRenderer.COMPACT.renderName(parameterDescriptor.getName()));
|
|
||||||
}
|
|
||||||
builder.append(")");
|
|
||||||
}
|
|
||||||
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Set<CallableMemberDescriptor> collectMethodsToGenerate(@NotNull JetClassOrObject classOrObject) {
|
|
||||||
DeclarationDescriptor descriptor = ResolvePackage.resolveToDescriptor(classOrObject);
|
|
||||||
if (descriptor instanceof ClassDescriptor) {
|
|
||||||
return collectMethodsToGenerate((ClassDescriptor) descriptor);
|
|
||||||
}
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract Set<CallableMemberDescriptor> collectMethodsToGenerate(@NotNull ClassDescriptor descriptor);
|
|
||||||
|
|
||||||
private MemberChooser<DescriptorClassMember> showOverrideImplementChooser(
|
|
||||||
Project project,
|
|
||||||
DescriptorClassMember[] members
|
|
||||||
) {
|
|
||||||
MemberChooser<DescriptorClassMember> chooser = new MemberChooser<DescriptorClassMember>(members, true, true, project);
|
|
||||||
chooser.setTitle(getChooserTitle());
|
|
||||||
chooser.show();
|
|
||||||
if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) return null;
|
|
||||||
return chooser;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract String getChooserTitle();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isValidFor(Editor editor, PsiFile file) {
|
|
||||||
if (!(file instanceof JetFile)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset());
|
|
||||||
JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(elementAtCaret, JetClassOrObject.class);
|
|
||||||
return classOrObject != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract String getNoMethodsFoundHint();
|
|
||||||
|
|
||||||
public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file, boolean implementAll) {
|
|
||||||
PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset());
|
|
||||||
JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(elementAtCaret, JetClassOrObject.class);
|
|
||||||
|
|
||||||
assert classOrObject != null;
|
|
||||||
|
|
||||||
Set<CallableMemberDescriptor> missingImplementations = collectMethodsToGenerate(classOrObject);
|
|
||||||
if (missingImplementations.isEmpty() && !implementAll) {
|
if (missingImplementations.isEmpty() && !implementAll) {
|
||||||
HintManager.getInstance().showErrorHint(editor, getNoMethodsFoundHint());
|
HintManager.getInstance().showErrorHint(editor, getNoMethodsFoundHint())
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
List<DescriptorClassMember> members = membersFromDescriptors((JetFile) file, missingImplementations);
|
val members = membersFromDescriptors(file as JetFile, missingImplementations)
|
||||||
|
|
||||||
List<DescriptorClassMember> selectedElements;
|
val selectedElements: List<DescriptorClassMember>?
|
||||||
if (implementAll) {
|
if (implementAll) {
|
||||||
selectedElements = members;
|
selectedElements = members
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
MemberChooser<DescriptorClassMember> chooser = showOverrideImplementChooser(
|
val chooser = showOverrideImplementChooser(project, members.toTypedArray())
|
||||||
project,
|
|
||||||
members.toArray(new DescriptorClassMember[members.size()]));
|
|
||||||
|
|
||||||
if (chooser == null) {
|
if (chooser == null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedElements = chooser.getSelectedElements();
|
selectedElements = chooser.getSelectedElements()
|
||||||
if (selectedElements == null || selectedElements.isEmpty()) return;
|
if (selectedElements == null || selectedElements.isEmpty()) return
|
||||||
}
|
}
|
||||||
|
|
||||||
PsiDocumentManager.getInstance(project).commitAllDocuments();
|
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||||
|
|
||||||
generateMethods(editor, classOrObject, selectedElements);
|
generateMethods(editor, classOrObject, selectedElements)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun invoke(project: Project, editor: Editor, file: PsiFile) {
|
||||||
public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) {
|
invoke(project, editor, file, false)
|
||||||
invoke(project, editor, file, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun startInWriteAction(): Boolean {
|
||||||
public boolean startInWriteAction() {
|
return false
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private val OVERRIDE_RENDERER = DescriptorRendererBuilder().setRenderDefaultValues(false).setModifiers(DescriptorRenderer.Modifier.OVERRIDE).setWithDefinedIn(false).setNameShortness(NameShortness.SOURCE_CODE_QUALIFIED).setOverrideRenderingPolicy(DescriptorRenderer.OverrideRenderingPolicy.RENDER_OVERRIDE).setUnitReturnType(false).setTypeNormalizer(IdeDescriptorRenderers.APPROXIMATE_FLEXIBLE_TYPES).build()
|
||||||
|
|
||||||
|
private val LOG = Logger.getInstance(javaClass<OverrideImplementMethodsHandler>().getCanonicalName())
|
||||||
|
|
||||||
|
public fun membersFromDescriptors(file: JetFile, missingImplementations: Iterable<CallableMemberDescriptor>): List<DescriptorClassMember> {
|
||||||
|
val members = ArrayList<DescriptorClassMember>()
|
||||||
|
for (memberDescriptor in missingImplementations) {
|
||||||
|
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.getProject(), memberDescriptor)
|
||||||
|
if (declaration == null) {
|
||||||
|
LOG.error("Can not find declaration for descriptor " + memberDescriptor)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val member = DescriptorClassMember(declaration, memberDescriptor)
|
||||||
|
members.add(member)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return members
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun generateMethods(editor: Editor, classOrObject: JetClassOrObject, selectedElements: List<DescriptorClassMember>) {
|
||||||
|
ApplicationManager.getApplication().runWriteAction(object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
var body = classOrObject.getBody()
|
||||||
|
if (body == null) {
|
||||||
|
val psiFactory = JetPsiFactory(classOrObject)
|
||||||
|
classOrObject.add(psiFactory.createWhiteSpace())
|
||||||
|
body = classOrObject.add(psiFactory.createEmptyClassBody()) as JetClassBody
|
||||||
|
}
|
||||||
|
|
||||||
|
var afterAnchor = findInsertAfterAnchor(editor, body)
|
||||||
|
|
||||||
|
if (afterAnchor == null) return
|
||||||
|
|
||||||
|
var firstGenerated: PsiElement? = null
|
||||||
|
|
||||||
|
val elementsToCompact = ArrayList<JetElement>()
|
||||||
|
for (element in generateOverridingMembers(selectedElements, classOrObject)) {
|
||||||
|
val added = body!!.addAfter(element, afterAnchor)
|
||||||
|
|
||||||
|
if (firstGenerated == null) {
|
||||||
|
firstGenerated = added
|
||||||
|
}
|
||||||
|
|
||||||
|
afterAnchor = added
|
||||||
|
elementsToCompact.add(added as JetElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
ShortenReferences.DEFAULT.process(elementsToCompact)
|
||||||
|
|
||||||
|
if (firstGenerated == null) return
|
||||||
|
|
||||||
|
val project = classOrObject.getProject()
|
||||||
|
val pointer = SmartPointerManager.getInstance(project).createSmartPsiElementPointer<PsiElement>(firstGenerated)
|
||||||
|
|
||||||
|
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument())
|
||||||
|
|
||||||
|
val element = pointer.getElement()
|
||||||
|
if (element != null) {
|
||||||
|
moveCaretIntoGeneratedElement(editor, element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findInsertAfterAnchor(editor: Editor, body: JetClassBody): PsiElement? {
|
||||||
|
val afterAnchor = body.getLBrace()
|
||||||
|
if (afterAnchor == null) return null
|
||||||
|
|
||||||
|
val offset = editor.getCaretModel().getOffset()
|
||||||
|
val offsetCursorElement = PsiTreeUtil.findFirstParent(body.getContainingFile().findElementAt(offset), object : Condition<PsiElement> {
|
||||||
|
override fun value(element: PsiElement): Boolean {
|
||||||
|
return element.getParent() == body
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (offsetCursorElement is PsiWhiteSpace) {
|
||||||
|
return removeAfterOffset(offset, offsetCursorElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offsetCursorElement != null && offsetCursorElement != body.getRBrace()) {
|
||||||
|
return offsetCursorElement
|
||||||
|
}
|
||||||
|
|
||||||
|
return afterAnchor
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeAfterOffset(offset: Int, whiteSpace: PsiWhiteSpace): PsiElement {
|
||||||
|
val spaceNode = whiteSpace.getNode()
|
||||||
|
if (spaceNode.getTextRange().contains(offset)) {
|
||||||
|
var beforeWhiteSpaceText = spaceNode.getText().substring(0, offset - spaceNode.getStartOffset())
|
||||||
|
if (!StringUtil.containsLineBreak(beforeWhiteSpaceText)) {
|
||||||
|
// Prevent insertion on same line
|
||||||
|
beforeWhiteSpaceText += "\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
val factory = JetPsiFactory(whiteSpace.getProject())
|
||||||
|
|
||||||
|
val insertAfter = whiteSpace.getPrevSibling()
|
||||||
|
whiteSpace.delete()
|
||||||
|
|
||||||
|
val beforeSpace = factory.createWhiteSpace(beforeWhiteSpaceText)
|
||||||
|
insertAfter.getParent().addAfter(beforeSpace, insertAfter)
|
||||||
|
|
||||||
|
return insertAfter.getNextSibling()
|
||||||
|
}
|
||||||
|
|
||||||
|
return whiteSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateOverridingMembers(selectedElements: List<DescriptorClassMember>, classOrObject: JetClassOrObject): List<JetElement> {
|
||||||
|
val overridingMembers = ArrayList<JetElement>()
|
||||||
|
for (selectedElement in selectedElements) {
|
||||||
|
val descriptor = selectedElement.getDescriptor()
|
||||||
|
if (descriptor is SimpleFunctionDescriptor) {
|
||||||
|
overridingMembers.add(overrideFunction(classOrObject, descriptor))
|
||||||
|
}
|
||||||
|
else if (descriptor is PropertyDescriptor) {
|
||||||
|
overridingMembers.add(overrideProperty(classOrObject, descriptor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return overridingMembers
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun overrideProperty(classOrObject: JetClassOrObject, descriptor: PropertyDescriptor): JetElement {
|
||||||
|
val newDescriptor = descriptor.copy(descriptor.getContainingDeclaration(), Modality.OPEN, descriptor.getVisibility(), descriptor.getKind(), /* copyOverrides = */ true) as PropertyDescriptor
|
||||||
|
newDescriptor.addOverriddenDescriptor(descriptor)
|
||||||
|
|
||||||
|
val body = StringBuilder()
|
||||||
|
body.append("\nget()")
|
||||||
|
body.append(" = ")
|
||||||
|
body.append(generateUnsupportedOrSuperCall(classOrObject, descriptor))
|
||||||
|
if (descriptor.isVar()) {
|
||||||
|
body.append("\nset(value) {}")
|
||||||
|
}
|
||||||
|
return JetPsiFactory(classOrObject.getProject()).createProperty(OVERRIDE_RENDERER.render(newDescriptor) + body)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun overrideFunction(classOrObject: JetClassOrObject, descriptor: FunctionDescriptor): JetNamedFunction {
|
||||||
|
val newDescriptor = descriptor.copy(descriptor.getContainingDeclaration(), Modality.OPEN, descriptor.getVisibility(), descriptor.getKind(), /* copyOverrides = */ true)
|
||||||
|
newDescriptor.addOverriddenDescriptor(descriptor)
|
||||||
|
|
||||||
|
|
||||||
|
val returnType = descriptor.getReturnType()
|
||||||
|
val builtIns = KotlinBuiltIns.getInstance()
|
||||||
|
|
||||||
|
val returnsNotUnit = returnType != null && builtIns.getUnitType() != returnType
|
||||||
|
val isAbstract = descriptor.getModality() == Modality.ABSTRACT
|
||||||
|
|
||||||
|
val delegation = generateUnsupportedOrSuperCall(classOrObject, descriptor)
|
||||||
|
|
||||||
|
val body = "{" + (if (returnsNotUnit && !isAbstract) "return " else "") + delegation + "}"
|
||||||
|
|
||||||
|
return JetPsiFactory(classOrObject.getProject()).createFunction(OVERRIDE_RENDERER.render(newDescriptor) + body)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateUnsupportedOrSuperCall(classOrObject: JetClassOrObject, descriptor: CallableMemberDescriptor): String {
|
||||||
|
val isAbstract = descriptor.getModality() == Modality.ABSTRACT
|
||||||
|
if (isAbstract) {
|
||||||
|
return "throw UnsupportedOperationException()"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val builder = StringBuilder()
|
||||||
|
builder.append("super")
|
||||||
|
if (classOrObject.getDelegationSpecifiers().size() > 1) {
|
||||||
|
builder.append("<").append(DescriptorRenderer.COMPACT.renderName(descriptor.getContainingDeclaration().getName())).append(">")
|
||||||
|
}
|
||||||
|
builder.append(".").append(DescriptorRenderer.COMPACT.renderName(descriptor.getName()))
|
||||||
|
|
||||||
|
if (descriptor is FunctionDescriptor) {
|
||||||
|
builder.append("(")
|
||||||
|
var first = true
|
||||||
|
for (parameterDescriptor in descriptor.getValueParameters()) {
|
||||||
|
if (!first) {
|
||||||
|
builder.append(", ")
|
||||||
|
}
|
||||||
|
first = false
|
||||||
|
builder.append(DescriptorRenderer.COMPACT.renderName(parameterDescriptor.getName()))
|
||||||
|
}
|
||||||
|
builder.append(")")
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ public abstract class AbstractOverrideImplementTest extends JetLightCodeInsightF
|
|||||||
}
|
}
|
||||||
|
|
||||||
performGenerateCommand(classOrObject,
|
performGenerateCommand(classOrObject,
|
||||||
OverrideImplementMethodsHandler.membersFromDescriptors(jetFile, Collections.singletonList(singleToOverride)));
|
OverrideImplementMethodsHandler.Companion.membersFromDescriptors(jetFile, Collections.singletonList(singleToOverride)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void doMultiOverrideImplement(OverrideImplementMethodsHandler handler) {
|
private void doMultiOverrideImplement(OverrideImplementMethodsHandler handler) {
|
||||||
@@ -183,7 +183,7 @@ public abstract class AbstractOverrideImplementTest extends JetLightCodeInsightF
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
performGenerateCommand(classOrObject, OverrideImplementMethodsHandler.membersFromDescriptors(jetFile, descriptorsList));
|
performGenerateCommand(classOrObject, OverrideImplementMethodsHandler.Companion.membersFromDescriptors(jetFile, descriptorsList));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void generateImplementation(@NotNull final PsiMethod method) {
|
private void generateImplementation(@NotNull final PsiMethod method) {
|
||||||
@@ -210,7 +210,7 @@ public abstract class AbstractOverrideImplementTest extends JetLightCodeInsightF
|
|||||||
new WriteCommandAction(myFixture.getProject(), myFixture.getFile()) {
|
new WriteCommandAction(myFixture.getProject(), myFixture.getFile()) {
|
||||||
@Override
|
@Override
|
||||||
protected void run(@NotNull Result result) throws Throwable {
|
protected void run(@NotNull Result result) throws Throwable {
|
||||||
OverrideImplementMethodsHandler.generateMethods(myFixture.getEditor(), classOrObject, descriptorsToGenerate);
|
OverrideImplementMethodsHandler.Companion.generateMethods(myFixture.getEditor(), classOrObject, descriptorsToGenerate);
|
||||||
}
|
}
|
||||||
}.performCommand();
|
}.performCommand();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user