diff --git a/ChangeLog.md b/ChangeLog.md index 7cd79c90c3a..1540f4b4047 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -171,6 +171,7 @@ These artifacts include extensions for the types available in the latter JDKs, s - [`KT-12997`](https://youtrack.jetbrains.com/issue/KT-12997) Override/Implement Members: Support "Copy JavaDoc" options for library classes - [`KT-13207`](https://youtrack.jetbrains.com/issue/KT-13207) Exception on safe delete from intention in 2016.2 - [`KT-12887`](https://youtrack.jetbrains.com/issue/KT-12887) "Extend selection" should select call's invoked expression +- [`KT-6520`](https://youtrack.jetbrains.com/issue/KT-6520) Introduce 'maino' and 'psvmo' templates for generating main in object - Fix exception when choose Gradle configurer after project is synced - Allow configuring Kotlin in Gradle module without Kotlin sources - Show all Kotlin annotations when browsing hierarchy of "java.lang.Annotation" diff --git a/idea/idea-live-templates/src/org/jetbrains/kotlin/idea/liveTemplates/KotlinTemplateContextType.java b/idea/idea-live-templates/src/org/jetbrains/kotlin/idea/liveTemplates/KotlinTemplateContextType.java index 6fb81cf8b1d..cf01ca5c68d 100644 --- a/idea/idea-live-templates/src/org/jetbrains/kotlin/idea/liveTemplates/KotlinTemplateContextType.java +++ b/idea/idea-live-templates/src/org/jetbrains/kotlin/idea/liveTemplates/KotlinTemplateContextType.java @@ -26,7 +26,7 @@ import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.impl.source.tree.LeafPsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -35,44 +35,50 @@ import org.jetbrains.kotlin.lexer.KtTokens; import org.jetbrains.kotlin.psi.*; public abstract class KotlinTemplateContextType extends TemplateContextType { - protected KotlinTemplateContextType(@NotNull @NonNls String id, @NotNull String presentableName, @Nullable java.lang.Class baseContextType) { + private KotlinTemplateContextType( + @NotNull @NonNls String id, + @NotNull String presentableName, + @Nullable java.lang.Class baseContextType + ) { super(id, presentableName, baseContextType); } @Override public boolean isInContext(@NotNull PsiFile file, int offset) { - if (PsiUtilBase.getLanguageAtOffset(file, offset).isKindOf(KotlinLanguage.INSTANCE)) { - PsiElement element = file.findElementAt(offset); - if (element == null) { - element = file.findElementAt(offset - 1); - } - if (element instanceof PsiWhiteSpace) { - return false; - } - else if (PsiTreeUtil.getParentOfType(element, PsiComment.class, false) != null) { - return isCommentInContext(); - } - else if (PsiTreeUtil.getParentOfType(element, KtPackageDirective.class) != null - || PsiTreeUtil.getParentOfType(element, KtImportDirective.class) != null) { - return false; - } - else if (element instanceof LeafPsiElement) { - IElementType elementType = ((LeafPsiElement) element).getElementType(); - if (elementType == KtTokens.IDENTIFIER) { - PsiElement parent = element.getParent(); - if (parent instanceof KtReferenceExpression) { - PsiElement parentOfParent = parent.getParent(); - KtQualifiedExpression qualifiedExpression = PsiTreeUtil.getParentOfType(element, KtQualifiedExpression.class); - if (qualifiedExpression != null && qualifiedExpression.getSelectorExpression() == parentOfParent) { - return false; - } + if (!PsiUtilCore.getLanguageAtOffset(file, offset).isKindOf(KotlinLanguage.INSTANCE)) { + return false; + } + + PsiElement element = file.findElementAt(offset); + if (element == null) { + element = file.findElementAt(offset - 1); + } + + if (element instanceof PsiWhiteSpace) { + return false; + } + else if (PsiTreeUtil.getParentOfType(element, PsiComment.class, false) != null) { + return isCommentInContext(); + } + else if (PsiTreeUtil.getParentOfType(element, KtPackageDirective.class) != null + || PsiTreeUtil.getParentOfType(element, KtImportDirective.class) != null) { + return false; + } + else if (element instanceof LeafPsiElement) { + IElementType elementType = ((LeafPsiElement) element).getElementType(); + if (elementType == KtTokens.IDENTIFIER) { + PsiElement parent = element.getParent(); + if (parent instanceof KtReferenceExpression) { + PsiElement parentOfParent = parent.getParent(); + KtQualifiedExpression qualifiedExpression = PsiTreeUtil.getParentOfType(element, KtQualifiedExpression.class); + if (qualifiedExpression != null && qualifiedExpression.getSelectorExpression() == parentOfParent) { + return false; } } } - return element != null && isInContext(element); } - return false; + return element != null && isInContext(element); } protected boolean isCommentInContext() { @@ -114,8 +120,7 @@ public abstract class KotlinTemplateContextType extends TemplateContextType { } continue; } - if (e instanceof KtProperty || e instanceof KtNamedFunction - || e instanceof KtClassOrObject) { + if (e instanceof KtProperty || e instanceof KtNamedFunction || e instanceof KtClassOrObject) { return false; } if (e instanceof KtScriptInitializer) { @@ -127,6 +132,18 @@ public abstract class KotlinTemplateContextType extends TemplateContextType { } } + public static class ObjectDeclaration extends KotlinTemplateContextType { + public ObjectDeclaration() { + super("KOTLIN_OBJECT_DECLARATION", "Object declaration", Generic.class); + } + + @Override + protected boolean isInContext(@NotNull PsiElement element) { + KtObjectDeclaration objectDeclaration = getParentClassOrObject(element, KtObjectDeclaration.class); + return objectDeclaration != null && !objectDeclaration.isObjectLiteral(); + } + } + public static class Class extends KotlinTemplateContextType { public Class() { super("KOTLIN_CLASS", "Class", Generic.class); @@ -134,22 +151,7 @@ public abstract class KotlinTemplateContextType extends TemplateContextType { @Override protected boolean isInContext(@NotNull PsiElement element) { - PsiElement e = element; - while (e != null && !(e instanceof KtClassOrObject)) { - if (e instanceof KtModifierList) { - // skip property/function/class or object which is owner of modifier list - e = e.getParent(); - if (e != null) { - e = e.getParent(); - } - continue; - } - if (e instanceof KtProperty || e instanceof KtNamedFunction) { - return false; - } - e = e.getParent(); - } - return e != null; + return getParentClassOrObject(element, KtClassOrObject.class) != null; } } @@ -202,4 +204,25 @@ public abstract class KotlinTemplateContextType extends TemplateContextType { return true; } } + + private static T getParentClassOrObject(@NotNull PsiElement element, @NotNull java.lang.Class klass) { + PsiElement e = element; + while (e != null && !klass.isInstance(e)) { + if (e instanceof KtModifierList) { + // skip property/function/class or object which is owner of modifier list + e = e.getParent(); + if (e != null) { + e = e.getParent(); + } + continue; + } + if (e instanceof KtProperty || e instanceof KtNamedFunction) { + return null; + } + e = e.getParent(); + } + + //noinspection unchecked + return (T) e; + } } diff --git a/idea/idea-live-templates/testData/context/AnonymousObject.kt b/idea/idea-live-templates/testData/context/AnonymousObject.kt new file mode 100644 index 00000000000..2bf35b956b5 --- /dev/null +++ b/idea/idea-live-templates/testData/context/AnonymousObject.kt @@ -0,0 +1,3 @@ +val test = object { + tt +} \ No newline at end of file diff --git a/idea/idea-live-templates/testData/context/CompanionObject.kt b/idea/idea-live-templates/testData/context/CompanionObject.kt new file mode 100644 index 00000000000..4f962130e09 --- /dev/null +++ b/idea/idea-live-templates/testData/context/CompanionObject.kt @@ -0,0 +1,5 @@ +class Some { + companion object { + tt + } +} \ No newline at end of file diff --git a/idea/idea-live-templates/testData/context/LocalObject.kt b/idea/idea-live-templates/testData/context/LocalObject.kt new file mode 100644 index 00000000000..3b8556e8f1e --- /dev/null +++ b/idea/idea-live-templates/testData/context/LocalObject.kt @@ -0,0 +1,5 @@ +fun test() { + object Test { + tt + } +} \ No newline at end of file diff --git a/idea/idea-live-templates/testData/context/ObjectInClass.kt b/idea/idea-live-templates/testData/context/ObjectInClass.kt new file mode 100644 index 00000000000..07555b6b987 --- /dev/null +++ b/idea/idea-live-templates/testData/context/ObjectInClass.kt @@ -0,0 +1,5 @@ +class Some { + object Test { + tt + } +} \ No newline at end of file diff --git a/idea/idea-live-templates/testData/context/ObjectInObject.kt b/idea/idea-live-templates/testData/context/ObjectInObject.kt new file mode 100644 index 00000000000..0ac3ad5bd46 --- /dev/null +++ b/idea/idea-live-templates/testData/context/ObjectInObject.kt @@ -0,0 +1,5 @@ +object Some { + object Test { + tt + } +} \ No newline at end of file diff --git a/idea/idea-live-templates/testData/context/TopLevelObject.kt b/idea/idea-live-templates/testData/context/TopLevelObject.kt new file mode 100644 index 00000000000..86d443d0ba5 --- /dev/null +++ b/idea/idea-live-templates/testData/context/TopLevelObject.kt @@ -0,0 +1,3 @@ +object Some { + tt +} \ No newline at end of file diff --git a/idea/idea-live-templates/tests/org/jetbrains/kotlin/idea/liveTemplates/LiveTemplatesContextTest.kt b/idea/idea-live-templates/tests/org/jetbrains/kotlin/idea/liveTemplates/LiveTemplatesContextTest.kt index 4e57dde7d13..d2edce7de59 100644 --- a/idea/idea-live-templates/tests/org/jetbrains/kotlin/idea/liveTemplates/LiveTemplatesContextTest.kt +++ b/idea/idea-live-templates/tests/org/jetbrains/kotlin/idea/liveTemplates/LiveTemplatesContextTest.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.idea.liveTemplates import com.intellij.codeInsight.template.TemplateContextType import com.intellij.testFramework.UsefulTestCase +import org.jetbrains.kotlin.idea.liveTemplates.KotlinTemplateContextType.* import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase import java.io.File @@ -26,27 +27,43 @@ class LiveTemplatesContextTest : KotlinLightCodeInsightFixtureTestCase() { File(TEST_DATA_BASE_PATH, "/context").path + File.separator fun testInDocComment() { - myFixture.configureByFile(getTestName(false) + ".kt") - assertInContexts( - KotlinTemplateContextType.Generic::class.java, - KotlinTemplateContextType.Comment::class.java) + assertInContexts(Generic::class.java, Comment::class.java) } fun testTopLevel() { - myFixture.configureByFile(getTestName(false) + ".kt") - assertInContexts( - KotlinTemplateContextType.Generic::class.java, - KotlinTemplateContextType.TopLevel::class.java) + assertInContexts(Generic::class.java, TopLevel::class.java) } fun testInExpression() { - myFixture.configureByFile(getTestName(false) + ".kt") - assertInContexts( - KotlinTemplateContextType.Generic::class.java, - KotlinTemplateContextType.Expression::class.java) + assertInContexts(Generic::class.java, Expression::class.java) } - private fun assertInContexts(vararg expectedContexts: Class) { + fun testAnonymousObject() { + assertInContexts(Generic::class.java, Class::class.java) + } + + fun testCompanionObject() { + assertInContexts(Generic::class.java, Class::class.java, ObjectDeclaration::class.java) + } + + fun testLocalObject() { + assertInContexts(Generic::class.java, Class::class.java, ObjectDeclaration::class.java) + } + + fun testObjectInClass() { + assertInContexts(Generic::class.java, Class::class.java, ObjectDeclaration::class.java) + } + + fun testObjectInObject() { + assertInContexts(Generic::class.java, Class::class.java, ObjectDeclaration::class.java) + } + + fun testTopLevelObject() { + assertInContexts(Generic::class.java, Class::class.java, ObjectDeclaration::class.java) + } + + private fun assertInContexts(vararg expectedContexts: java.lang.Class) { + myFixture.configureByFile(getTestName(false) + ".kt") val allContexts = TemplateContextType.EP_NAME.extensions.filter { it is KotlinTemplateContextType } val enabledContexts = allContexts.filter { it.isInContext(myFixture.file, myFixture.caretOffset) }.map { it.javaClass } UsefulTestCase.assertSameElements(enabledContexts, *expectedContexts) diff --git a/idea/resources/liveTemplates/Kotlin.xml b/idea/resources/liveTemplates/Kotlin.xml index 7b155dbb76d..dc97046b580 100644 --- a/idea/resources/liveTemplates/Kotlin.xml +++ b/idea/resources/liveTemplates/Kotlin.xml @@ -8,6 +8,14 @@ + + + +