Moved live templates support into separate module

This commit is contained in:
Valentin Kipyatkov
2015-11-19 13:36:05 +01:00
parent c6df1b6dce
commit b8dadeb4cc
18 changed files with 32 additions and 7 deletions
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-runtime" level="project" />
<orderEntry type="module" module-name="idea-core" />
<orderEntry type="module" module-name="descriptors" />
<orderEntry type="library" name="idea-full" level="project" />
<orderEntry type="module" module-name="idea-analysis" />
<orderEntry type="module" module-name="frontend" />
<orderEntry type="module" module-name="idea-test-framework" scope="TEST" />
<orderEntry type="module" module-name="idea" scope="RUNTIME" />
<orderEntry type="library" scope="RUNTIME" name="junit-plugin" level="project" />
</component>
</module>
@@ -0,0 +1,31 @@
/*
* 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.liveTemplates;
import com.intellij.codeInsight.template.impl.DefaultLiveTemplatesProvider;
public class KotlinLiveTemplatesProvider implements DefaultLiveTemplatesProvider {
@Override
public String[] getDefaultLiveTemplateFiles() {
return new String[]{"liveTemplates/Kotlin"};
}
@Override
public String[] getHiddenLiveTemplateFiles() {
return new String[0];
}
}
@@ -0,0 +1,53 @@
/*
* 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.liveTemplates
import com.intellij.codeInsight.template.impl.TemplateOptionalProcessor
import com.intellij.openapi.project.Project
import com.intellij.codeInsight.template.Template
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.Editor
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.idea.util.ShortenReferences
import com.intellij.psi.util.PsiUtilBase
import org.jetbrains.kotlin.psi.KtFile
public class KotlinShortenFQNamesProcessor : TemplateOptionalProcessor {
override fun processText(project: Project, template: Template, document: Document, templateRange: RangeMarker, editor: Editor) {
if (!template.isToShortenLongNames()) return
PsiDocumentManager.getInstance(project).commitDocument(document)
val file = PsiUtilBase.getPsiFileInEditor(editor, project) as? KtFile ?: return
ShortenReferences.DEFAULT.process(file, templateRange.getStartOffset(), templateRange.getEndOffset())
PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(document)
}
override fun getOptionName(): String {
return CodeInsightBundle.message("dialog.edit.template.checkbox.shorten.fq.names")!!
}
override fun isEnabled(template: Template) = template.isToShortenLongNames()
override fun setEnabled(template: Template, value: Boolean) {
}
override fun isVisible(template: Template) = false
}
@@ -0,0 +1,202 @@
/*
* 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.liveTemplates;
import com.intellij.codeInsight.template.EverywhereContextType;
import com.intellij.codeInsight.template.TemplateContextType;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
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 org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.KotlinLanguage;
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<? extends TemplateContextType> 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;
}
}
}
}
return element != null && isInContext(element);
}
return false;
}
protected boolean isCommentInContext() {
return false;
}
protected abstract boolean isInContext(@NotNull PsiElement element);
public static class Generic extends KotlinTemplateContextType {
public Generic() {
super("KOTLIN", KotlinLanguage.NAME, EverywhereContextType.class);
}
@Override
protected boolean isInContext(@NotNull PsiElement element) {
return true;
}
@Override
protected boolean isCommentInContext() {
return true;
}
}
public static class TopLevel extends KotlinTemplateContextType {
public TopLevel() {
super("KOTLIN_TOPLEVEL", "Top-level", Generic.class);
}
@Override
protected boolean isInContext(@NotNull PsiElement element) {
PsiElement e = element;
while (e != null) {
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
|| e instanceof KtClassOrObject) {
return false;
}
e = e.getParent();
}
return true;
}
}
public static class Class extends KotlinTemplateContextType {
public Class() {
super("KOTLIN_CLASS", "Class", Generic.class);
}
@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;
}
}
public static class Statement extends KotlinTemplateContextType {
public Statement() {
super("KOTLIN_STATEMENT", "Statement", Generic.class);
}
@Override
protected boolean isInContext(@NotNull PsiElement element) {
PsiElement parentStatement = PsiTreeUtil.findFirstParent(element, new Condition<PsiElement>() {
@Override
public boolean value(PsiElement element) {
return element instanceof KtExpression && (element.getParent() instanceof KtBlockExpression);
}
});
if (parentStatement == null) return false;
// We are in the leftmost position in parentStatement
return element.getTextOffset() == parentStatement.getTextOffset();
}
}
public static class Expression extends KotlinTemplateContextType {
public Expression() {
super("KOTLIN_EXPRESSION", "Expression", Generic.class);
}
@Override
protected boolean isInContext(@NotNull PsiElement element) {
return element.getParent() instanceof KtExpression && !(element.getParent() instanceof KtConstantExpression) &&
!(element.getParent().getParent() instanceof KtDotQualifiedExpression)
&& !(element.getParent() instanceof KtParameter);
}
}
public static class Comment extends KotlinTemplateContextType {
public Comment() {
super("KOTLIN_COMMENT", "Comment", Generic.class);
}
@Override
protected boolean isInContext(@NotNull PsiElement element) {
return false;
}
@Override
protected boolean isCommentInContext() {
return true;
}
}
}
@@ -0,0 +1,79 @@
/*
* 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.liveTemplates.macro
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.template.Expression
import com.intellij.codeInsight.template.ExpressionContext
import com.intellij.codeInsight.template.Macro
import com.intellij.codeInsight.template.Result
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiNamedElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.utils.collectDescriptorsFiltered
class AnonymousSuperMacro : Macro() {
override fun getName() = "anonymousSuper"
override fun getPresentableName() = "anonymousSuper()"
override fun calculateResult(params: Array<Expression>, context: ExpressionContext): Result? {
val editor = context.editor
if (editor != null) {
AnonymousTemplateEditingListener.registerListener(editor, context.project)
}
val vars = getSupertypes(params, context)
if (vars.isEmpty()) return null
return KotlinPsiElementResult(vars.first())
}
override fun calculateLookupItems(params: Array<Expression>, context: ExpressionContext): Array<LookupElement>? {
val superTypes = getSupertypes(params, context)
if (superTypes.size < 2) return null
return superTypes.map { LookupElementBuilder.create(it) }.toTypedArray()
}
private fun getSupertypes(params: Array<Expression>, context: ExpressionContext): Collection<PsiNamedElement> {
if (params.size != 0) return emptyList()
val psiDocumentManager = PsiDocumentManager.getInstance(context.project)
psiDocumentManager.commitAllDocuments()
val psiFile = psiDocumentManager.getPsiFile(context.editor!!.document) as? KtFile ?: return emptyList()
val expression = PsiTreeUtil.getParentOfType(psiFile.findElementAt(context.startOffset), KtExpression::class.java) ?: return emptyList()
val bindingContext = expression.analyze(BodyResolveMode.FULL)
val resolutionScope = expression.getResolutionScope(bindingContext, expression.getResolutionFacade())
return resolutionScope
.collectDescriptorsFiltered(DescriptorKindFilter.NON_SINGLETON_CLASSIFIERS)
.filter { it is ClassDescriptor && it.modality.isOverridable && (it.kind == ClassKind.CLASS || it.kind == ClassKind.INTERFACE) }
.mapNotNull { DescriptorToSourceUtils.descriptorToDeclaration(it) as PsiNamedElement? }
}
}
@@ -0,0 +1,94 @@
/*
* 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.liveTemplates.macro
import com.intellij.codeInsight.template.Template
import com.intellij.codeInsight.template.TemplateEditingAdapter
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiFile
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.core.overrideImplement.ImplementMembersHandler
import org.jetbrains.kotlin.psi.KtReferenceExpression
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
internal class AnonymousTemplateEditingListener(private val psiFile: PsiFile, private val editor: Editor) : TemplateEditingAdapter() {
private var classRef: KtReferenceExpression? = null
private var classDescriptor: ClassDescriptor? = null
override fun currentVariableChanged(templateState: TemplateState?, template: Template?, oldIndex: Int, newIndex: Int) {
assert(templateState!!.template != null)
val variableRange = templateState.getVariableRange("SUPERTYPE") ?: return
val name = psiFile.findElementAt(variableRange.startOffset)
if (name != null && name.parent is KtReferenceExpression) {
val ref = name.parent as KtReferenceExpression
val descriptor = ref.analyze(BodyResolveMode.FULL).get(BindingContext.REFERENCE_TARGET, ref)
if (descriptor is ClassDescriptor) {
classRef = ref
classDescriptor = descriptor
}
}
}
override fun templateFinished(template: Template?, brokenOff: Boolean) {
editor.putUserData(LISTENER_KEY, null)
if (brokenOff) return
if (classDescriptor != null) {
if (classDescriptor!!.kind == ClassKind.CLASS) {
val placeToInsert = classRef!!.textRange.endOffset
PsiDocumentManager.getInstance(psiFile.project).getDocument(psiFile)!!.insertString(placeToInsert, "()")
var hasConstructorsParameters = false
for (cd in classDescriptor!!.constructors) {
// TODO check for visibility
hasConstructorsParameters = hasConstructorsParameters or (cd.valueParameters.size != 0)
}
if (hasConstructorsParameters) {
editor.caretModel.moveToOffset(placeToInsert + 1)
}
}
ImplementMembersHandler().invoke(psiFile.project, editor, psiFile, true)
}
}
companion object {
private val LISTENER_KEY = Key.create<AnonymousTemplateEditingListener>("kotlin.AnonymousTemplateEditingListener")
fun registerListener(editor: Editor, project: Project) {
if (editor.getUserData(LISTENER_KEY) != null) return
val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.document)!!
val templateState = TemplateManagerImpl.getTemplateState(editor)
if (templateState != null) {
val listener = AnonymousTemplateEditingListener(psiFile, editor)
editor.putUserData(LISTENER_KEY, listener)
templateState.addTemplateStateListener(listener)
}
}
}
}
@@ -0,0 +1,28 @@
/*
* 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.liveTemplates.macro
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.UserDataHolder
import org.jetbrains.kotlin.descriptors.VariableDescriptor
class AnyVariableMacro : BaseKotlinVariableMacro() {
override fun getName() = "kotlinAnyVariable"
override fun getPresentableName() = "kotlinAnyVariable()"
override fun isSuitable(variableDescriptor: VariableDescriptor, project: Project, userData: UserDataHolder) = true
}
@@ -0,0 +1,101 @@
/*
* 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.liveTemplates.macro
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.template.Expression
import com.intellij.codeInsight.template.ExpressionContext
import com.intellij.codeInsight.template.Macro
import com.intellij.codeInsight.template.Result
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.UserDataHolder
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiNamedElement
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithVisibility
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.codeInsight.ReferenceVariantsHelper
import org.jetbrains.kotlin.idea.core.isVisible
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.util.slicedMap.UserDataHolderImpl
import java.util.*
abstract class BaseKotlinVariableMacro : Macro() {
private fun getVariables(params: Array<Expression>, context: ExpressionContext): Collection<PsiNamedElement> {
if (params.size != 0) return emptyList()
val project = context.project
val psiDocumentManager = PsiDocumentManager.getInstance(project)
psiDocumentManager.commitAllDocuments()
val psiFile = psiDocumentManager.getPsiFile(context.editor!!.document) as? KtFile ?: return emptyList()
val contextElement = psiFile.findElementAt(context.startOffset)?.getNonStrictParentOfType<KtElement>() ?: return emptyList()
val resolutionFacade = psiFile.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(contextElement, BodyResolveMode.PARTIAL_FOR_COMPLETION)
fun isVisible(descriptor: DeclarationDescriptor): Boolean {
return descriptor !is DeclarationDescriptorWithVisibility || descriptor.isVisible(contextElement, null, bindingContext, resolutionFacade)
}
val userData = UserDataHolderImpl()
initUserData(userData, contextElement, bindingContext)
val helper = ReferenceVariantsHelper(bindingContext, resolutionFacade, resolutionFacade.moduleDescriptor, ::isVisible)
val variants = helper
.getReferenceVariants(contextElement, CallTypeAndReceiver.DEFAULT, DescriptorKindFilter.VARIABLES, { true })
.filter { isSuitable(it as VariableDescriptor, project, userData) }
val declarations = ArrayList<PsiNamedElement>()
for (descriptor in variants) {
val declaration = DescriptorToSourceUtils.descriptorToDeclaration(descriptor) as? PsiNamedElement ?: continue
declarations.add(declaration)
}
return declarations
}
protected open fun initUserData(userData: UserDataHolder, contextElement: KtElement, bindingContext: BindingContext) {
}
protected abstract fun isSuitable(
variableDescriptor: VariableDescriptor,
project: Project,
userData: UserDataHolder): Boolean
override fun calculateResult(params: Array<Expression>, context: ExpressionContext): Result? {
val vars = getVariables(params, context)
if (vars.isEmpty()) return null
return KotlinPsiElementResult(vars.first())
}
override fun calculateLookupItems(params: Array<Expression>, context: ExpressionContext): Array<LookupElement>? {
val vars = getVariables(params, context)
if (vars.size < 2) return null
return vars.map { LookupElementBuilder.create(it) }.toTypedArray()
}
}
@@ -0,0 +1,52 @@
/*
* 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.liveTemplates.macro
import com.intellij.codeInsight.template.*
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.psi.KtFunction
import java.util.*
class FunctionParametersMacro : Macro() {
override fun getName() = "functionParameters"
override fun getPresentableName() = "functionParameters()"
override fun calculateResult(params: Array<Expression>, context: ExpressionContext): Result? {
val project = context.project
val templateStartOffset = context.templateStartOffset
val offset = if (templateStartOffset > 0) context.templateStartOffset - 1 else context.templateStartOffset
PsiDocumentManager.getInstance(project).commitAllDocuments()
val file = PsiDocumentManager.getInstance(project).getPsiFile(context.editor!!.document) ?: return null
var place = file.findElementAt(offset)
while (place != null) {
if (place is KtFunction) {
val result = ArrayList<Result>()
for (param in place.valueParameters) {
result.add(TextResult(param.name!!))
}
return ListResult(result)
}
place = place.parent
}
return null
}
override fun isAcceptableInContext(context: TemplateContextType?) = context is JavaCodeContextType
}
@@ -0,0 +1,24 @@
/*
* 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.liveTemplates.macro
import com.intellij.codeInsight.template.JavaPsiElementResult
import com.intellij.psi.PsiNamedElement
class KotlinPsiElementResult(element: PsiNamedElement) : JavaPsiElementResult(element) {
override fun toString() = (element as PsiNamedElement).name ?: ""
}
@@ -0,0 +1,88 @@
/*
* 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.liveTemplates.macro
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.template.*
import com.intellij.psi.PsiDocumentManager
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
import org.jetbrains.kotlin.idea.core.IterableTypesDetection
import org.jetbrains.kotlin.idea.core.KotlinNameSuggester
import org.jetbrains.kotlin.idea.resolve.ideService
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.psi.KtWithExpressionInitializer
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
class SuggestVariableNameMacro : Macro() {
override fun getName() = "kotlinSuggestVariableName"
override fun getPresentableName() = "kotlinSuggestVariableName()"
override fun calculateResult(params: Array<out Expression>, context: ExpressionContext): Result? {
return nameSuggestions(context).firstOrNull()?.let { TextResult(it) }
}
override fun calculateLookupItems(params: Array<out Expression>, context: ExpressionContext): Array<out LookupElement>? {
val suggestions = nameSuggestions(context)
if (suggestions.size < 2) return null
return suggestions.map { LookupElementBuilder.create(it) }.toTypedArray()
}
private fun nameSuggestions(context: ExpressionContext): Collection<String> {
val project = context.project
val psiDocumentManager = PsiDocumentManager.getInstance(project)
psiDocumentManager.commitAllDocuments()
val psiFile = psiDocumentManager.getPsiFile(context.editor!!.document) as? KtFile ?: return emptyList()
val token = psiFile.findElementAt(context.startOffset) ?: return emptyList()
val declaration = token.parent as? KtCallableDeclaration ?: return emptyList()
if (token != declaration.nameIdentifier) return emptyList()
val nameValidator: (String) -> Boolean = { true }
val initializer = (declaration as? KtWithExpressionInitializer)?.initializer
if (initializer != null) {
val bindingContext = initializer.analyze(BodyResolveMode.PARTIAL)
return KotlinNameSuggester.suggestNamesByExpressionAndType(initializer, bindingContext, nameValidator, null)
}
val parent = declaration.parent
if (parent is KtForExpression && declaration == parent.loopParameter) {
iterationVariableNameSuggestions(parent, nameValidator)?.let { return it }
}
val descriptor = declaration.resolveToDescriptor() as? VariableDescriptor ?: return emptyList()
return KotlinNameSuggester.suggestNamesByType(descriptor.type, nameValidator, null)
}
private fun iterationVariableNameSuggestions(forExpression: KtForExpression, nameValidator: (String) -> Boolean): Collection<String>? {
val loopRange = forExpression.loopRange ?: return null
val resolutionFacade = forExpression.getResolutionFacade()
val bindingContext = resolutionFacade.analyze(loopRange, BodyResolveMode.PARTIAL)
val type = bindingContext.getType(loopRange) ?: return null
val scope = loopRange.getResolutionScope(bindingContext, resolutionFacade)
val detector = resolutionFacade.ideService<IterableTypesDetection>().createDetector(scope)
val elementType = detector.elementType(type)?.type ?: return null
return KotlinNameSuggester.suggestIterationVariableNames(loopRange, elementType, bindingContext, nameValidator, null)
}
}
@@ -0,0 +1,66 @@
/*
* 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.liveTemplates.macro
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
import org.jetbrains.kotlin.idea.core.ExpectedInfo
import org.jetbrains.kotlin.idea.core.ExpectedInfos
import org.jetbrains.kotlin.idea.core.SmartCastCalculator
import org.jetbrains.kotlin.idea.util.CallTypeAndReceiver
import org.jetbrains.kotlin.idea.util.FuzzyType
import org.jetbrains.kotlin.idea.util.getResolutionScope
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
import org.jetbrains.kotlin.resolve.BindingContext
class SuitableVariableMacro : BaseKotlinVariableMacro() {
private companion object {
val EXPECTED_INFOS_KEY = Key<Collection<ExpectedInfo>>("EXPECTED_INFOS_KEY")
val SMART_CAST_CALCULATOR_KEY = Key<SmartCastCalculator>("SMART_CAST_CALCULATOR_KEY")
}
override fun getName() = "kotlinVariable"
override fun getPresentableName() = "kotlinVariable()"
override fun initUserData(userData: UserDataHolder, contextElement: KtElement, bindingContext: BindingContext) {
val resolutionFacade = contextElement.getResolutionFacade()
if (contextElement is KtNameReferenceExpression) {
val callTypeAndReceiver = CallTypeAndReceiver.detect(contextElement)
if (callTypeAndReceiver is CallTypeAndReceiver.DEFAULT) {
val expectedInfos = ExpectedInfos(bindingContext, resolutionFacade).calculate(contextElement)
if (expectedInfos.isNotEmpty()) {
userData.putUserData(EXPECTED_INFOS_KEY, expectedInfos)
val scope = contextElement.getResolutionScope(bindingContext, resolutionFacade)
val smartCastCalculator = SmartCastCalculator(bindingContext, scope.ownerDescriptor, contextElement, null, resolutionFacade)
userData.putUserData(SMART_CAST_CALCULATOR_KEY, smartCastCalculator)
}
}
}
}
override fun isSuitable(variableDescriptor: VariableDescriptor, project: Project, userData: UserDataHolder): Boolean {
val expectedInfos = userData.getUserData(EXPECTED_INFOS_KEY) ?: return true
val smartCastCalculator = userData.getUserData(SMART_CAST_CALCULATOR_KEY)!!
val types = smartCastCalculator.types(variableDescriptor)
return expectedInfos.any { expectedInfo -> types.any { expectedInfo.filter.matchingSubstitutor(FuzzyType(it, emptyList())) != null } }
}
}
@@ -0,0 +1,55 @@
/*
* 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.liveTemplates
import com.intellij.codeInsight.template.TemplateContextType
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import java.io.File
public class LiveTemplatesContextTest : KotlinLightCodeInsightFixtureTestCase() {
override fun getTestDataPath(): String =
File(PluginTestCaseBase.getTestDataPathBase(), "/templates/context").getPath() + File.separator
public fun testInDocComment() {
myFixture.configureByFile(getTestName(false) + ".kt")
assertInContexts(
KotlinTemplateContextType.Generic::class.java,
KotlinTemplateContextType.Comment::class.java)
}
public fun testTopLevel() {
myFixture.configureByFile(getTestName(false) + ".kt")
assertInContexts(
KotlinTemplateContextType.Generic::class.java,
KotlinTemplateContextType.TopLevel::class.java)
}
public fun testInExpression() {
myFixture.configureByFile(getTestName(false) + ".kt")
assertInContexts(
KotlinTemplateContextType.Generic::class.java,
KotlinTemplateContextType.Expression::class.java)
}
private fun assertInContexts(vararg expectedContexts: Class<out KotlinTemplateContextType>) {
val allContexts = TemplateContextType.EP_NAME.getExtensions().filter { it is KotlinTemplateContextType }
val enabledContexts = allContexts.filter { it.isInContext(myFixture.getFile(), myFixture.getCaretOffset()) }.map { it.javaClass }
UsefulTestCase.assertSameElements(enabledContexts, *expectedContexts)
}
}
@@ -0,0 +1,337 @@
/*
* 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.liveTemplates;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupEx;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.codeInsight.template.TemplateManager;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.codeInsight.template.impl.TemplateState;
import com.intellij.ide.DataManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.UIUtil;
import junit.framework.TestCase;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase;
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor;
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
public class LiveTemplatesTest extends KotlinLightCodeInsightFixtureTestCase {
@Override
protected void setUp() {
super.setUp();
myFixture.setTestDataPath(new File(PluginTestCaseBase.getTestDataPathBase(), "/templates").getPath() + File.separator);
((TemplateManagerImpl) TemplateManager.getInstance(getProject())).setTemplateTesting(true);
}
@Override
protected void tearDown() {
((TemplateManagerImpl) TemplateManager.getInstance(getProject())).setTemplateTesting(false);
super.tearDown();
}
public void testSout() {
paremeterless();
}
public void testSout_BeforeCall() {
paremeterless();
}
public void testSout_BeforeCallSpace() {
paremeterless();
}
public void testSout_BeforeBinary() {
paremeterless();
}
public void testSout_InCallArguments() {
paremeterless();
}
public void testSout_BeforeQualifiedCall() {
paremeterless();
}
public void testSout_AfterSemicolon() {
paremeterless();
}
public void testSerr() {
paremeterless();
}
public void testMain() {
paremeterless();
}
public void testSoutv() {
start();
assertStringItems("args", "x", "y");
typeAndNextTab("y");
checkAfter();
}
public void testSoutp() {
paremeterless();
}
public void testFun0() {
start();
type("foo");
nextTab(2);
checkAfter();
}
public void testFun1() {
start();
type("foo");
nextTab(4);
checkAfter();
}
public void testFun2() {
start();
type("foo");
nextTab(6);
checkAfter();
}
public void testExfun() {
start();
typeAndNextTab("Int");
typeAndNextTab("foo");
typeAndNextTab("arg : Int");
nextTab();
checkAfter();
}
public void testExval() {
start();
typeAndNextTab("Int");
nextTab();
typeAndNextTab("Int");
checkAfter();
}
public void testExvar() {
start();
typeAndNextTab("Int");
nextTab();
typeAndNextTab("Int");
checkAfter();
}
public void testClosure() {
start();
typeAndNextTab("param");
nextTab();
checkAfter();
}
public void testInterface() {
start();
typeAndNextTab("SomeTrait");
checkAfter();
}
public void testSingleton() {
start();
typeAndNextTab("MySingleton");
checkAfter();
}
public void testVoid() {
start();
typeAndNextTab("foo");
typeAndNextTab("x : Int");
checkAfter();
}
public void testIter() {
start();
assertStringItems("args", "myList", "o", "str", "stream");
type("args");
nextTab(2);
checkAfter();
}
public void testAnonymous_1() {
start();
typeAndNextTab("Runnable");
checkAfter();
}
public void testAnonymous_2() {
start();
typeAndNextTab("Thread");
checkAfter();
}
private void doTestIfnInn() {
start();
assertStringItems("b", "t", "y");
typeAndNextTab("b");
checkAfter();
}
public void testIfn() {
doTestIfnInn();
}
public void testInn() {
doTestIfnInn();
}
private void paremeterless() {
start();
checkAfter();
}
private void start() {
myFixture.configureByFile(getTestName(true) + ".kt");
myFixture.type(getTemplateName());
doAction("ExpandLiveTemplateByTab");
}
private String getTemplateName() {
String testName = getTestName(true);
if (testName.contains("_")) {
return testName.substring(0, testName.indexOf("_"));
}
return testName;
}
private void checkAfter() {
TestCase.assertNull(getTemplateState());
myFixture.checkResultByFile(getTestName(true) + ".exp.kt", true);
}
private void typeAndNextTab(String s) {
type(s);
nextTab();
}
private void type(String s) {
myFixture.type(s);
}
private void nextTab() {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
CommandProcessor.getInstance().executeCommand(getProject(), new Runnable() {
@Override
public void run() {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
getTemplateState().nextTab();
}
});
}
}, "nextTab", null);
}
});
}
private void nextTab(int times) {
for (int i = 0; i < times; i++) {
nextTab();
}
}
private TemplateState getTemplateState() {
return TemplateManagerImpl.getTemplateState(myFixture.getEditor());
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE;
}
private void doAction(@NotNull String actionId) {
EditorActionManager actionManager = EditorActionManager.getInstance();
EditorActionHandler actionHandler = actionManager.getActionHandler(actionId);
actionHandler.execute(myFixture.getEditor(), DataManager.getInstance().getDataContext(myFixture.getEditor().getComponent()));
}
private void assertStringItems(@NonNls String... items) {
TestCase.assertEquals(Arrays.asList(items), Arrays.asList(getItemStringsSorted()));
}
private String[] getItemStrings() {
LookupEx lookup = LookupManager.getActiveLookup(myFixture.getEditor());
TestCase.assertNotNull(lookup);
ArrayList<String> result = new ArrayList<String>();
for (LookupElement element : lookup.getItems()) {
result.add(element.getLookupString());
}
return ArrayUtil.toStringArray(result);
}
private String[] getItemStringsSorted() {
String[] items = getItemStrings();
Arrays.sort(items);
return items;
}
}