Code completion: template item for function taking lambda with 2 or more parameters (KT-4855) + better handling of type instantiation in smart completion

This commit is contained in:
Valentin Kipyatkov
2014-04-22 22:10:42 +04:00
parent 0057843c08
commit e8805f69bf
33 changed files with 428 additions and 269 deletions
@@ -54,6 +54,11 @@ public class JetCallExpression extends JetReferenceExpression implements JetCall
return (JetTypeArgumentList) findChildByType(JetNodeTypes.TYPE_ARGUMENT_LIST); return (JetTypeArgumentList) findChildByType(JetNodeTypes.TYPE_ARGUMENT_LIST);
} }
/**
* Normally there should be only one (or zero) function literal arguments.
* The returned value is a list for better handling of commonly made mistake of a function taking a lambda and returning another function.
* Most of users can simply ignore lists of more than one element.
*/
@Override @Override
@NotNull @NotNull
public List<JetExpression> getFunctionLiteralArguments() { public List<JetExpression> getFunctionLiteralArguments() {
@@ -37,14 +37,11 @@ import org.jetbrains.jet.lang.resolve.lazy.KotlinCodeAnalyzer;
import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.plugin.JetDescriptorIconProvider; import org.jetbrains.jet.plugin.JetDescriptorIconProvider;
import org.jetbrains.jet.plugin.completion.handlers.JetClassInsertHandler; import org.jetbrains.jet.plugin.completion.handlers.*;
import org.jetbrains.jet.plugin.completion.handlers.JetJavaClassInsertHandler;
import org.jetbrains.jet.renderer.DescriptorRenderer; import org.jetbrains.jet.renderer.DescriptorRenderer;
import java.util.List; import java.util.List;
import static org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler.*;
public final class DescriptorLookupConverter { public final class DescriptorLookupConverter {
private DescriptorLookupConverter() {} private DescriptorLookupConverter() {}
@@ -91,7 +88,11 @@ public final class DescriptorLookupConverter {
typeText = DescriptorRenderer.SHORT_NAMES_IN_TYPES.render(descriptor); typeText = DescriptorRenderer.SHORT_NAMES_IN_TYPES.render(descriptor);
} }
element = element.withInsertHandler(getInsertHandler(descriptor)); InsertHandler<LookupElement> insertHandler = getDefaultInsertHandler(descriptor);
element = element.withInsertHandler(insertHandler);
if (insertHandler instanceof JetFunctionInsertHandler && ((JetFunctionInsertHandler)insertHandler).getLambdaInfo() != null) {
element.putUserData(JetCompletionCharFilter.ACCEPT_OPENING_BRACE, true);
}
element = element.withTailText(tailText, true).withTypeText(typeText).withPresentableText(presentableText); element = element.withTailText(tailText, true).withTypeText(typeText).withPresentableText(presentableText);
element = element.withIcon(JetDescriptorIconProvider.getIcon(descriptor, Iconable.ICON_FLAG_VISIBILITY)); element = element.withIcon(JetDescriptorIconProvider.getIcon(descriptor, Iconable.ICON_FLAG_VISIBILITY));
element = element.withStrikeoutness(KotlinBuiltIns.getInstance().isDeprecated(descriptor)); element = element.withStrikeoutness(KotlinBuiltIns.getInstance().isDeprecated(descriptor));
@@ -100,20 +101,26 @@ public final class DescriptorLookupConverter {
} }
@Nullable @Nullable
public static InsertHandler<LookupElement> getInsertHandler(@NotNull DeclarationDescriptor descriptor) { public static InsertHandler<LookupElement> getDefaultInsertHandler(@NotNull DeclarationDescriptor descriptor) {
if (descriptor instanceof FunctionDescriptor) { if (descriptor instanceof FunctionDescriptor) {
FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor; FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor;
List<ValueParameterDescriptor> parameters = functionDescriptor.getValueParameters();
if (functionDescriptor.getValueParameters().isEmpty()) { if (parameters.isEmpty()) {
return EMPTY_FUNCTION_HANDLER; return JetFunctionInsertHandler.NO_PARAMETERS_HANDLER;
} }
if (functionDescriptor.getValueParameters().size() == 1 && if (parameters.size() == 1) {
KotlinBuiltIns.getInstance().isFunctionOrExtensionFunctionType(functionDescriptor.getValueParameters().get(0).getType())) { JetType parameterType = parameters.get(0).getType();
return PARAMS_BRACES_FUNCTION_HANDLER; if (KotlinBuiltIns.getInstance().isFunctionOrExtensionFunctionType(parameterType)) {
int parameterCount = KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(parameterType).size();
if (parameterCount <= 1) { // otherwise additional item with lambda template is to be added
return new JetFunctionInsertHandler(CaretPosition.IN_BRACKETS, new GenerateLambdaInfo(parameterType, false));
}
}
} }
return PARAMS_PARENTHESIS_FUNCTION_HANDLER; return JetFunctionInsertHandler.WITH_PARAMETERS_HANDLER;
} }
if (descriptor instanceof ClassDescriptor) { if (descriptor instanceof ClassDescriptor) {
@@ -136,7 +143,7 @@ public final class DescriptorLookupConverter {
PsiClass containingClass = ((PsiMember) declaration).getContainingClass(); PsiClass containingClass = ((PsiMember) declaration).getContainingClass();
if (containingClass != null && !JavaResolverPsiUtils.isCompiledKotlinClassOrPackageClass(containingClass)) { if (containingClass != null && !JavaResolverPsiUtils.isCompiledKotlinClassOrPackageClass(containingClass)) {
if (declaration instanceof PsiMethod) { if (declaration instanceof PsiMethod) {
InsertHandler<LookupElement> handler = getInsertHandler(descriptor); InsertHandler<LookupElement> handler = getDefaultInsertHandler(descriptor);
assert handler != null: assert handler != null:
"Special kotlin handler is expected for function: " + declaration.getText() + "Special kotlin handler is expected for function: " + declaration.getText() +
" and descriptor: " + DescriptorRenderer.FQNAMES_IN_TYPES.render(descriptor); " and descriptor: " + DescriptorRenderer.FQNAMES_IN_TYPES.render(descriptor);
@@ -1,4 +1,4 @@
package org.jetbrains.jet.plugin.completion.smart package org.jetbrains.jet.plugin.completion
import org.jetbrains.jet.lang.psi.JetExpression import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.lang.resolve.BindingContext
@@ -23,17 +23,25 @@ import org.jetbrains.jet.lang.descriptors.FunctionDescriptor
import java.util.HashSet import java.util.HashSet
import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall import org.jetbrains.jet.lang.resolve.calls.model.ResolvedCall
import org.jetbrains.jet.lang.descriptors.ModuleDescriptor import org.jetbrains.jet.lang.descriptors.ModuleDescriptor
import org.jetbrains.jet.lang.types.JetType
enum class Tail {
COMMA
PARENTHESIS
}
data class ExpectedInfo(val `type`: JetType, val tail: Tail?)
class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: ModuleDescriptor) { class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: ModuleDescriptor) {
public fun calculate(expressionWithType: JetExpression): Collection<ExpectedInfo>? { public fun calculate(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val expectedInfos = calculateForArgument(expressionWithType) val expectedInfos1 = calculateForArgument(expressionWithType)
if (expectedInfos != null) { if (expectedInfos1 != null) return expectedInfos1
return expectedInfos
} val expectedInfos2 = calculateForFunctionLiteralArgument(expressionWithType)
else { if (expectedInfos2 != null) return expectedInfos2
val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null
return listOf(ExpectedInfo(expectedType, null)) val expectedType = bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, expressionWithType] ?: return null
} return listOf(ExpectedInfo(expectedType, null))
} }
private fun calculateForArgument(expressionWithType: JetExpression): Collection<ExpectedInfo>? { private fun calculateForArgument(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
@@ -42,6 +50,21 @@ class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: Mo
val argumentList = argument.getParent() as JetValueArgumentList val argumentList = argument.getParent() as JetValueArgumentList
val argumentIndex = argumentList.getArguments().indexOf(argument) val argumentIndex = argumentList.getArguments().indexOf(argument)
val callExpression = argumentList.getParent() as? JetCallExpression ?: return null val callExpression = argumentList.getParent() as? JetCallExpression ?: return null
return calculateForArgument(callExpression, argumentIndex, false)
}
private fun calculateForFunctionLiteralArgument(expressionWithType: JetExpression): Collection<ExpectedInfo>? {
val callExpression = expressionWithType.getParent() as? JetCallExpression
if (callExpression != null) {
val arguments = callExpression.getFunctionLiteralArguments()
if (arguments.size > 0 && arguments[0] == expressionWithType) {
return calculateForArgument(callExpression, callExpression.getValueArguments().size, true)
}
}
return null
}
private fun calculateForArgument(callExpression: JetCallExpression, argumentIndex: Int, isFunctionLiteralArgument: Boolean): Collection<ExpectedInfo>? {
val calleeExpression = callExpression.getCalleeExpression() val calleeExpression = callExpression.getCalleeExpression()
val parent = callExpression.getParent() val parent = callExpression.getParent()
@@ -62,7 +85,7 @@ class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: Mo
val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, calleeExpression] ?: return null //TODO: discuss it val resolutionScope = bindingContext[BindingContext.RESOLUTION_SCOPE, calleeExpression] ?: return null //TODO: discuss it
val callResolutionContext = BasicCallResolutionContext.create( val callResolutionContext = BasicCallResolutionContext.create(
DelegatingBindingTrace(bindingContext, "Temporary trace for smart completion"), DelegatingBindingTrace(bindingContext, "Temporary trace for completion"),
resolutionScope, resolutionScope,
call, call,
bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression] ?: TypeUtils.NO_EXPECTED_TYPE, bindingContext[BindingContext.EXPECTED_EXPRESSION_TYPE, callExpression] ?: TypeUtils.NO_EXPECTED_TYPE,
@@ -71,15 +94,20 @@ class ExpectedInfos(val bindingContext: BindingContext, val moduleDescriptor: Mo
CheckValueArgumentsMode.ENABLED, CheckValueArgumentsMode.ENABLED,
CompositeExtension(listOf()), CompositeExtension(listOf()),
false).replaceCollectAllCandidates(true) false).replaceCollectAllCandidates(true)
val callResolver = InjectorForMacros(expressionWithType.getProject(), moduleDescriptor).getCallResolver()!! val callResolver = InjectorForMacros(callExpression.getProject(), moduleDescriptor).getCallResolver()!!
val results: OverloadResolutionResults<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext) val results: OverloadResolutionResults<FunctionDescriptor> = callResolver.resolveFunctionCall(callResolutionContext)
val expectedInfos = HashSet<ExpectedInfo>() val expectedInfos = HashSet<ExpectedInfo>()
for (candidate: ResolvedCall<FunctionDescriptor> in results.getAllCandidates()!!) { for (candidate: ResolvedCall<FunctionDescriptor> in results.getAllCandidates()!!) {
val parameters = candidate.getResultingDescriptor().getValueParameters() val parameters = candidate.getResultingDescriptor().getValueParameters()
if (parameters.size <= argumentIndex) continue if (isFunctionLiteralArgument) {
if (argumentIndex != parameters.size - 1) continue
}
else{
if (parameters.size <= argumentIndex) continue
}
val parameterDescriptor = parameters[argumentIndex] val parameterDescriptor = parameters[argumentIndex]
val tail = if (argumentIndex == parameters.size - 1) Tail.PARENTHESIS else Tail.COMMA val tail = if (isFunctionLiteralArgument) null else if (argumentIndex == parameters.size - 1) Tail.PARENTHESIS else Tail.COMMA
expectedInfos.add(ExpectedInfo(parameterDescriptor.getType(), tail)) expectedInfos.add(ExpectedInfo(parameterDescriptor.getType(), tail))
} }
return expectedInfos return expectedInfos
@@ -21,55 +21,37 @@ import com.intellij.codeInsight.lookup.Lookup
import com.intellij.codeInsight.lookup.CharFilter.Result import com.intellij.codeInsight.lookup.CharFilter.Result
import org.jetbrains.jet.lang.psi.JetFile import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler
import com.intellij.openapi.util.Key
public open class JetCompletionCharFilter() : CharFilter() { public open class JetCompletionCharFilter() : CharFilter() {
class object {
public val ACCEPT_OPENING_BRACE: Key<Boolean> = Key<Boolean>("JetCompletionCharFilter.ACCEPT_OPENNING_BRACE")
}
public override fun acceptChar(c : Char, prefixLength : Int, lookup : Lookup) : Result? { public override fun acceptChar(c : Char, prefixLength : Int, lookup : Lookup) : Result? {
if (lookup.getPsiFile() !is JetFile) return null if (lookup.getPsiFile() !is JetFile) return null
fun checkHideLookupForRangeOperator(): Result? { if (c == '.' && prefixLength == 0 && !lookup.isSelectionTouched()) {
if (c == '.' && prefixLength == 0 && !lookup.isSelectionTouched()) { val caret = lookup.getEditor().getCaretModel().getOffset()
val caret = lookup.getEditor().getCaretModel().getOffset() if (caret > 0 && (lookup.getEditor().getDocument().getCharsSequence().charAt(caret - 1)) == '.') {
if (caret > 0 && (lookup.getEditor().getDocument().getCharsSequence().charAt(caret - 1)) == '.') { return Result.HIDE_LOOKUP
return Result.HIDE_LOOKUP
}
} }
return null
} }
fun checkFinishCompletionForOpenBrace(): Result? { if (c == '{') {
if (c == '{') { val currentItem = lookup.getCurrentItem()
val currentItem = lookup.getCurrentItem() if (currentItem != null && currentItem.getUserData(ACCEPT_OPENING_BRACE) ?: false) {
if (currentItem != null && (currentItem.getObject() is JetLookupObject)) { return Result.SELECT_ITEM_AND_FINISH_LOOKUP
val lookupObject = (currentItem.getObject() as JetLookupObject)
val descriptor = lookupObject.getDescriptor()
if (descriptor != null) {
val handler = DescriptorLookupConverter.getInsertHandler(descriptor)
if (handler == JetFunctionInsertHandler.PARAMS_BRACES_FUNCTION_HANDLER) {
return Result.SELECT_ITEM_AND_FINISH_LOOKUP
}
}
}
} }
return null
} }
fun checkFinishCompletionForEqual(): Result? { if (c == '=') {
if (c == '=') { val currentItem = lookup.getCurrentItem()
val currentItem = lookup.getCurrentItem() if (currentItem != null && (currentItem.getObject() is KotlinNamedParametersContributor.NamedParameterLookupObject)) {
if (currentItem != null && (currentItem.getObject() is KotlinNamedParametersContributor.NamedParameterLookupObject)) { return Result.SELECT_ITEM_AND_FINISH_LOOKUP
return Result.SELECT_ITEM_AND_FINISH_LOOKUP
}
} }
return null
} }
return checkHideLookupForRangeOperator() ?: return null
checkFinishCompletionForOpenBrace() ?:
checkFinishCompletionForEqual() ?:
null
} }
} }
@@ -17,14 +17,27 @@
package org.jetbrains.jet.plugin.completion; package org.jetbrains.jet.plugin.completion;
import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementDecorator;
import com.intellij.codeInsight.lookup.LookupElementPresentation;
import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.Conditions;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.FunctionDescriptor;
import org.jetbrains.jet.lang.descriptors.ValueParameterDescriptor;
import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.plugin.completion.handlers.CaretPosition;
import org.jetbrains.jet.plugin.completion.handlers.GenerateLambdaInfo;
import org.jetbrains.jet.plugin.completion.handlers.HandlersPackage;
import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler;
import org.jetbrains.jet.plugin.project.ResolveSessionForBodies; import org.jetbrains.jet.plugin.project.ResolveSessionForBodies;
import java.util.List;
public class JetCompletionResultSet { public class JetCompletionResultSet {
private final ResolveSessionForBodies resolveSession; private final ResolveSessionForBodies resolveSession;
private final BindingContext bindingContext; private final BindingContext bindingContext;
@@ -74,12 +87,38 @@ public class JetCompletionResultSet {
} }
addElement(DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor)); addElement(DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor));
// add special item for function with one argument of function type with more than one parameter
if (descriptor instanceof FunctionDescriptor) {
FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor;
List<ValueParameterDescriptor> parameters = functionDescriptor.getValueParameters();
if (parameters.size() == 1) {
final JetType parameterType = parameters.get(0).getType();
if (KotlinBuiltIns.getInstance().isFunctionOrExtensionFunctionType(parameterType)) {
int parameterCount = KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(parameterType).size();
if (parameterCount > 1) {
LookupElement lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, descriptor);
addElement(new LookupElementDecorator<LookupElement>(lookupElement) {
@Override
public void renderElement(LookupElementPresentation presentation) {
super.renderElement(presentation);
presentation.setItemText(getLookupString() + " " + HandlersPackage.buildLambdaPresentation(parameterType));
}
@Override
public void handleInsert(InsertionContext context) {
new JetFunctionInsertHandler(CaretPosition.IN_BRACKETS, new GenerateLambdaInfo(parameterType, true))
.handleInsert(context, this);
}
});
}
}
}
}
} }
public void addElement(@NotNull LookupElement element) { public void addElement(@NotNull LookupElement element) {
if (!result.getPrefixMatcher().prefixMatches(element)) { if (!result.getPrefixMatcher().prefixMatches(element)) return;
return;
}
result.addElement(element); result.addElement(element);
isSomethingAdded = true; isSomethingAdded = true;
@@ -279,7 +279,7 @@ public open class JetKeywordCompletionContributor() : CompletionContributor() {
lookupElementBuilder.withInsertHandler(JetKeywordCompletionContributor.KEYWORDS_INSERT_HANDLER) lookupElementBuilder.withInsertHandler(JetKeywordCompletionContributor.KEYWORDS_INSERT_HANDLER)
} }
else { else {
lookupElementBuilder.withInsertHandler(JetFunctionInsertHandler.EMPTY_FUNCTION_HANDLER) lookupElementBuilder.withInsertHandler(JetFunctionInsertHandler.NO_PARAMETERS_HANDLER)
} }
} }
} }
@@ -0,0 +1,128 @@
package org.jetbrains.jet.plugin.completion.handlers
import com.intellij.openapi.command.CommandProcessor
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Computable
import com.intellij.codeInsight.template.TemplateManager
import com.intellij.openapi.util.TextRange
import com.intellij.codeInsight.template.Template
import org.jetbrains.jet.plugin.refactoring.JetNameValidator
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester
import org.jetbrains.jet.renderer.DescriptorRenderer
import com.intellij.codeInsight.template.Expression
import com.intellij.codeInsight.template.ExpressionContext
import com.intellij.codeInsight.template.TextResult
import com.intellij.codeInsight.template.Result
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.jet.lang.types.JetType
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.plugin.completion.ExpectedInfos
import org.jetbrains.jet.plugin.project.AnalyzerFacadeWithCache
import org.jetbrains.jet.lang.psi.JetFile
fun insertLambdaTemplate(context: InsertionContext, placeholderRange: TextRange, lambdaType: JetType) {
val explicitParameterTypes = needExplicitParameterTypes(context, placeholderRange, lambdaType)
// we start template later to not interfere with insertion of tail type
val commandProcessor = CommandProcessor.getInstance()
val commandName = commandProcessor.getCurrentCommandName()
val commandGroupId = commandProcessor.getCurrentCommandGroupId()
val rangeMarker = context.getDocument().createRangeMarker(placeholderRange)
context.setLaterRunnable {
commandProcessor.executeCommand(context.getProject(), {
ApplicationManager.getApplication()!!.runWriteAction(Computable<Unit> {
try {
if (rangeMarker.isValid()) {
context.getDocument().deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset())
context.getEditor().getCaretModel().moveToOffset(rangeMarker.getStartOffset())
val template = buildTemplate(lambdaType, explicitParameterTypes, context.getProject())
TemplateManager.getInstance(context.getProject()).startTemplate(context.getEditor(), template)
}
}
finally {
rangeMarker.dispose()
}
})
}, commandName, commandGroupId)
}
}
fun buildLambdaPresentation(lambdaType: JetType): String {
val parameterTypes = functionParameterTypes(lambdaType)
val parametersPresentation = parameterTypes.map { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it) }.makeString(", ")
fun wrap(s: String) = if (parameterTypes.size != 1) "($s)" else s
return "{ ${wrap(parametersPresentation)} -> ... }"
}
private fun needExplicitParameterTypes(context: InsertionContext, placeholderRange: TextRange, lambdaType: JetType): Boolean {
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments()
val file = context.getFile() as JetFile
val expression = PsiTreeUtil.findElementOfClassAtRange(file, placeholderRange.getStartOffset(), placeholderRange.getEndOffset(), javaClass<JetExpression>())
if (expression == null) return false
val resolveSession = AnalyzerFacadeWithCache.getLazyResolveSessionForFile(file)
val bindingContext = resolveSession.resolveToElement(expression)
val expectedInfos = ExpectedInfos(bindingContext, resolveSession.getModuleDescriptor()).calculate(expression) ?: return false
val functionTypes = expectedInfos.map { it.`type` }.filter { KotlinBuiltIns.getInstance().isExactFunctionOrExtensionFunctionType(it) }.toSet()
if (functionTypes.size <= 1) return false
val lambdaParameterCount = KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(lambdaType).size
return functionTypes.filter { KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(it).size == lambdaParameterCount}.size > 1
}
private fun buildTemplate(lambdaType: JetType, explicitParameterTypes: Boolean, project: Project): Template {
val parameterTypes = functionParameterTypes(lambdaType)
val nameValidator = JetNameValidator.getEmptyValidator(project) //TODO: check for names in scope
val useParenthesis = explicitParameterTypes || parameterTypes.size != 1
val manager = TemplateManager.getInstance(project)
val template = manager.createTemplate("", "")
template.setToShortenLongNames(true)
//template.setToReformat(true) //TODO
template.addTextSegment("{ ")
if (useParenthesis) {
template.addTextSegment("(")
}
for (i in parameterTypes.indices) {
val parameterType = parameterTypes[i]
if (i > 0) {
template.addTextSegment(", ")
}
template.addVariable(ParameterNameExpression(JetNameSuggester.suggestNames(parameterType, nameValidator, "p")), true)
if (explicitParameterTypes) {
template.addTextSegment(": " + DescriptorRenderer.SOURCE_CODE.renderType(parameterType))
}
}
if (useParenthesis) {
template.addTextSegment(")")
}
template.addTextSegment(" -> ")
template.addEndVariable()
template.addTextSegment(" }")
return template
}
private class ParameterNameExpression(val nameSuggestions: Array<String>) : Expression() {
override fun calculateResult(context: ExpressionContext?) = TextResult(nameSuggestions[0])
override fun calculateQuickResult(context: ExpressionContext?): Result? = null
override fun calculateLookupItems(context: ExpressionContext?)
= Array<LookupElement>(nameSuggestions.size, { LookupElementBuilder.create(nameSuggestions[it]) })
}
fun functionParameterTypes(functionType: JetType): List<JetType>
= KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(functionType).map { it.getType() }
@@ -33,22 +33,22 @@ import org.jetbrains.jet.plugin.completion.JetLookupObject
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor
import org.jetbrains.jet.lang.psi.JetQualifiedExpression import org.jetbrains.jet.lang.psi.JetQualifiedExpression
import org.jetbrains.jet.lang.resolve.DescriptorUtils import org.jetbrains.jet.lang.resolve.DescriptorUtils
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper
import com.intellij.openapi.editor.Document
import org.jetbrains.jet.lang.types.JetType
import com.intellij.openapi.util.TextRange
public enum class CaretPosition { public enum class CaretPosition {
IN_BRACKETS IN_BRACKETS
AFTER_BRACKETS AFTER_BRACKETS
} }
public enum class BracketType {
PARENTHESIS
BRACES
}
public class JetFunctionInsertHandler(val caretPosition : CaretPosition, val bracketType : BracketType) : InsertHandler<LookupElement> { public data class GenerateLambdaInfo(val lambdaType: JetType, val explicitParameters: Boolean)
public class JetFunctionInsertHandler(val caretPosition : CaretPosition, val lambdaInfo: GenerateLambdaInfo?) : InsertHandler<LookupElement> {
{ {
if (caretPosition == CaretPosition.AFTER_BRACKETS && bracketType == BracketType.BRACES) { if (caretPosition == CaretPosition.AFTER_BRACKETS && lambdaInfo != null) {
throw IllegalArgumentException("CaretPosition.AFTER_BRACKETS with bracketType == BracketType.BRACES combination is not supported") throw IllegalArgumentException("CaretPosition.AFTER_BRACKETS with lambdaInfo != null combination is not supported")
} }
} }
@@ -67,32 +67,29 @@ public class JetFunctionInsertHandler(val caretPosition : CaretPosition, val bra
addBrackets(context, element) addBrackets(context, element)
} }
addImport(context, item) addImports(context, item)
} }
private fun addBrackets(context : InsertionContext, offsetElement : PsiElement) { private fun addBrackets(context : InsertionContext, offsetElement : PsiElement) {
val offset = context.getSelectionEndOffset() val offset = context.getTailOffset()
val document = context.getDocument() val document = context.getDocument()
val completionChar = context.getCompletionChar() val completionChar = context.getCompletionChar()
var documentText = document.getText() val forceParenthesis = lambdaInfo != null && completionChar == '\t' && document.getCharsSequence().charAt(offset) == '('
val braces = lambdaInfo != null && completionChar != '(' && !forceParenthesis
val forceParenthesis = bracketType == BracketType.BRACES && completionChar == '\t' && documentText.charAt(offset) == '('
val braces = bracketType == BracketType.BRACES && completionChar != '(' && !forceParenthesis
val openingBracket = if (braces) '{' else '(' val openingBracket = if (braces) '{' else '('
val closingBracket = if (braces) '}' else ')' val closingBracket = if (braces) '}' else ')'
var openingBracketIndex = indexOfSkippingSpace(documentText, openingBracket, offset) var openingBracketOffset = indexOfSkippingSpace(document, openingBracket, offset)
var inBracketsShift = 0 var inBracketsShift = 0
if (openingBracketIndex == -1) { if (openingBracketOffset == -1) {
if (braces) { if (braces) {
if (completionChar == ' ' || completionChar == '{') { if (completionChar == ' ' || completionChar == '{') {
context.setAddCompletionChar(false) context.setAddCompletionChar(false)
} }
if (isInsertSpacesInOneLineFunctionEnabled(offsetElement.getProject())) { if (isInsertSpacesInOneLineFunctionEnabled(context.getProject())) {
document.insertString(offset, " { }") document.insertString(offset, " { }")
inBracketsShift = 1 inBracketsShift = 1
} }
@@ -104,74 +101,69 @@ public class JetFunctionInsertHandler(val caretPosition : CaretPosition, val bra
document.insertString(offset, "()") document.insertString(offset, "()")
} }
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document) PsiDocumentManager.getInstance(context.getProject()).commitDocument(document)
documentText = document.getText()
} }
openingBracketIndex = indexOfSkippingSpace(documentText, openingBracket, offset) openingBracketOffset = indexOfSkippingSpace(document, openingBracket, offset)
assert (openingBracketIndex != -1) { "If there wasn't open bracket it should already have been inserted" } assert (openingBracketOffset != -1) { "If there wasn't open bracket it should already have been inserted" }
val closeBracketIndex = indexOfSkippingSpace(documentText, closingBracket, openingBracketIndex + 1) val closeBracketOffset = indexOfSkippingSpace(document, closingBracket, openingBracketOffset + 1)
val editor = context.getEditor() val editor = context.getEditor()
var forcePlaceCaretIntoParentheses : Boolean = completionChar == '(' var forcePlaceCaretIntoParentheses : Boolean = completionChar == '('
if (caretPosition == CaretPosition.IN_BRACKETS || forcePlaceCaretIntoParentheses || closeBracketIndex == -1) { if (caretPosition == CaretPosition.IN_BRACKETS || forcePlaceCaretIntoParentheses || closeBracketOffset == -1) {
editor.getCaretModel().moveToOffset(openingBracketIndex + 1 + inBracketsShift) editor.getCaretModel().moveToOffset(openingBracketOffset + 1 + inBracketsShift)
AutoPopupController.getInstance(context.getProject())?.autoPopupParameterInfo(editor, offsetElement) AutoPopupController.getInstance(context.getProject())?.autoPopupParameterInfo(editor, offsetElement)
} }
else { else {
editor.getCaretModel().moveToOffset(closeBracketIndex + 1) editor.getCaretModel().moveToOffset(closeBracketOffset + 1)
} }
PsiDocumentManager.getInstance(context.getProject()).commitDocument(context.getDocument()) PsiDocumentManager.getInstance(context.getProject()).commitDocument(context.getDocument())
if (lambdaInfo != null && lambdaInfo.explicitParameters) {
insertLambdaTemplate(context, TextRange(openingBracketOffset, closeBracketOffset + 1), lambdaInfo.lambdaType)
}
} }
class object { class object {
public val EMPTY_FUNCTION_HANDLER: JetFunctionInsertHandler = JetFunctionInsertHandler(CaretPosition.AFTER_BRACKETS, BracketType.PARENTHESIS) public val NO_PARAMETERS_HANDLER: JetFunctionInsertHandler = JetFunctionInsertHandler(CaretPosition.AFTER_BRACKETS, null)
public val PARAMS_PARENTHESIS_FUNCTION_HANDLER: JetFunctionInsertHandler = JetFunctionInsertHandler(CaretPosition.IN_BRACKETS, BracketType.PARENTHESIS) public val WITH_PARAMETERS_HANDLER: JetFunctionInsertHandler = JetFunctionInsertHandler(CaretPosition.IN_BRACKETS, null)
public val PARAMS_BRACES_FUNCTION_HANDLER: JetFunctionInsertHandler = JetFunctionInsertHandler(CaretPosition.IN_BRACKETS, BracketType.BRACES)
private fun shouldAddBrackets(element : PsiElement) : Boolean { private fun shouldAddBrackets(element : PsiElement) : Boolean {
return PsiTreeUtil.getParentOfType(element, javaClass<JetImportDirective>()) == null return PsiTreeUtil.getParentOfType(element, javaClass<JetImportDirective>()) == null
} }
private fun indexOfSkippingSpace(str : String, ch : Char, startIndex : Int) : Int { private fun indexOfSkippingSpace(document: Document, ch : Char, startIndex : Int) : Int {
for (i in startIndex..str.length() - 1) { val text = document.getCharsSequence()
val currentChar = str.charAt(i) for (i in startIndex..text.length() - 1) {
if (ch == currentChar) { val currentChar = text.charAt(i)
return i if (ch == currentChar) return i
} if (!Character.isWhitespace(currentChar)) return -1
if (!Character.isWhitespace(currentChar)) {
return -1
}
} }
return -1 return -1
} }
private open fun isInsertSpacesInOneLineFunctionEnabled(project : Project) : Boolean { private open fun isInsertSpacesInOneLineFunctionEnabled(project : Project)
val settings = CodeStyleSettingsManager.getSettings(project) = CodeStyleSettingsManager.getSettings(project)
val jetSettings = settings.getCustomSettings(javaClass<JetCodeStyleSettings>())!! .getCustomSettings(javaClass<JetCodeStyleSettings>())!!.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD
return jetSettings.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD
}
private open fun addImport(context : InsertionContext, item : LookupElement) { private open fun addImports(context : InsertionContext, item : LookupElement) {
ApplicationManager.getApplication()?.runReadAction { () : Unit -> ApplicationManager.getApplication()?.runReadAction { () : Unit ->
val startOffset = context.getStartOffset() val startOffset = context.getStartOffset()
val element = context.getFile().findElementAt(startOffset) val element = context.getFile().findElementAt(startOffset)
if (element == null) return@runReadAction if (element == null) return@runReadAction
if (context.getFile() is JetFile && item.getObject() is JetLookupObject) { val file = context.getFile()
if (file is JetFile && item.getObject() is JetLookupObject) {
val descriptor = (item.getObject() as JetLookupObject).getDescriptor() val descriptor = (item.getObject() as JetLookupObject).getDescriptor()
if (descriptor is SimpleFunctionDescriptor) { if (descriptor is SimpleFunctionDescriptor) {
val file = context.getFile() as JetFile
val functionDescriptor = descriptor as SimpleFunctionDescriptor val functionDescriptor = descriptor as SimpleFunctionDescriptor
if (PsiTreeUtil.getParentOfType(element, javaClass<JetQualifiedExpression>()) != null && if (PsiTreeUtil.getParentOfType(element, javaClass<JetQualifiedExpression>()) != null &&
functionDescriptor.getReceiverParameter() == null) { functionDescriptor.getReceiverParameter() == null) {
return@runReadAction return@runReadAction
} }
@@ -3,7 +3,6 @@ package org.jetbrains.jet.plugin.completion.handlers
import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.openapi.editor.event.* import com.intellij.openapi.editor.event.*
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.codeInsight.AutoPopupController import com.intellij.codeInsight.AutoPopupController
@@ -46,7 +45,7 @@ abstract class WithTailInsertHandlerBase : InsertHandler<LookupElement> {
class WithTailCharInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : WithTailInsertHandlerBase() { class WithTailCharInsertHandler(val tailChar: Char, val spaceAfter: Boolean) : WithTailInsertHandlerBase() {
override fun insertTail(context: InsertionContext, offset: Int, moveCaret: Boolean) { override fun insertTail(context: InsertionContext, offset: Int, moveCaret: Boolean) {
val document = context.getDocument() val document = context.getDocument()
fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getText(TextRange(offset, offset + 1))[0] == c fun isCharAt(offset: Int, c: Char) = offset < document.getTextLength() && document.getCharsSequence().charAt(offset) == c
if (isCharAt(offset, tailChar)) { if (isCharAt(offset, tailChar)) {
document.deleteString(offset, offset + 1) document.deleteString(offset, offset + 1)
@@ -3,133 +3,45 @@ package org.jetbrains.jet.plugin.completion.smart
import com.intellij.codeInsight.lookup.LookupElement import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder import com.intellij.codeInsight.lookup.LookupElementBuilder
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
import org.jetbrains.jet.lang.types.JetType import org.jetbrains.jet.plugin.completion.handlers.functionParameterTypes
import org.jetbrains.jet.renderer.DescriptorRenderer import org.jetbrains.jet.renderer.DescriptorRenderer
import org.jetbrains.jet.plugin.refactoring.JetNameValidator import org.jetbrains.jet.plugin.completion.handlers.insertLambdaTemplate
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester import com.intellij.openapi.util.TextRange
import com.intellij.openapi.project.Project import org.jetbrains.jet.plugin.completion.JetCompletionCharFilter
import com.intellij.codeInsight.completion.InsertHandler import org.jetbrains.jet.plugin.completion.ExpectedInfo
import com.intellij.codeInsight.completion.InsertionContext import org.jetbrains.jet.plugin.completion.handlers.buildLambdaPresentation
import com.intellij.codeInsight.template.*
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.util.Computable
class LambdaItems(val project: Project) { object LambdaItems {
public fun addToCollection(collection: MutableCollection<LookupElement>, functionExpectedInfos: Collection<ExpectedInfo>) { public fun addToCollection(collection: MutableCollection<LookupElement>, functionExpectedInfos: Collection<ExpectedInfo>) {
val distinctTypes = functionExpectedInfos.map { it.`type` }.toSet() val distinctTypes = functionExpectedInfos.map { it.`type` }.toSet()
fun createLookupElement(lookupString: String, textBeforeCaret: String, textAfterCaret: String, shortenRefs: Boolean)
= LookupElementBuilder.create(lookupString)
.withInsertHandler(ArtificialElementInsertHandler(textBeforeCaret, textAfterCaret, shortenRefs))
.suppressAutoInsertion()
val singleType = if (distinctTypes.size == 1) distinctTypes.single() else null val singleType = if (distinctTypes.size == 1) distinctTypes.single() else null
val singleSignatureLength = singleType?.let { KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(it).size } val singleSignatureLength = singleType?.let { KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(it).size }
val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1 val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1
if (offerNoParametersLambda) { if (offerNoParametersLambda) {
val lookupElement = createLookupElement("{...}", "{ ", " }", shortenRefs = false) val lookupElement = LookupElementBuilder.create("{...}")
collection.add(lookupElement.addTail(functionExpectedInfos)) .withInsertHandler(ArtificialElementInsertHandler("{ ", " }", false))
.suppressAutoInsertion()
.addTail(functionExpectedInfos)
lookupElement.putUserData(JetCompletionCharFilter.ACCEPT_OPENING_BRACE, true)
collection.add(lookupElement)
} }
if (singleSignatureLength != 0) { if (singleSignatureLength != 0) {
for (functionType in distinctTypes) { for (functionType in distinctTypes) {
val parameterTypes = functionParameterTypes(functionType) val lookupString = buildLambdaPresentation(functionType)
val parametersPresentation = parameterTypes.map { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it) }.makeString(", ")
val useExplicitTypes = distinctTypes.stream().any { it != functionType && functionParameterTypes(it).size == parameterTypes.size }
fun wrap(s: String) = if (useParenthesis(useExplicitTypes, parameterTypes)) "($s)" else s
val lookupString = "{ ${wrap(parametersPresentation)} -> ... }"
val lookupElement = LookupElementBuilder.create(lookupString) val lookupElement = LookupElementBuilder.create(lookupString)
.withInsertHandler(LambdaInsertHandler(functionType, useExplicitTypes)) .withInsertHandler({ (context, lookupElement) ->
val offset = context.getStartOffset()
val placeholder = "{}"
context.getDocument().replaceString(offset, context.getTailOffset(), placeholder)
insertLambdaTemplate(context, TextRange(offset, offset + placeholder.length), functionType)
})
.suppressAutoInsertion() .suppressAutoInsertion()
collection.add(lookupElement.addTail(functionExpectedInfos.filter { it.`type` == functionType })) .addTail(functionExpectedInfos.filter { it.`type` == functionType })
lookupElement.putUserData(JetCompletionCharFilter.ACCEPT_OPENING_BRACE, true)
collection.add(lookupElement)
} }
} }
} }
private fun useParenthesis(useExplicitTypes: Boolean, parameterTypes: List<JetType>) = useExplicitTypes || parameterTypes.size != 1
private inner class LambdaInsertHandler(val functionType: JetType, val useExplicitTypes: Boolean) : InsertHandler<LookupElement> {
override fun handleInsert(context: InsertionContext, item: LookupElement) {
val document = context.getDocument()
val editor = context.getEditor()
val offset = context.getStartOffset()
val placeholder = "{}"
document.replaceString(offset, context.getTailOffset(), placeholder)
val rangeMarker = document.createRangeMarker(offset, offset + placeholder.length)
// we start template later to not interfere with insertion of tail type
val commandProcessor = CommandProcessor.getInstance()
val commandName = commandProcessor.getCurrentCommandName()
val commandGroupId = commandProcessor.getCurrentCommandGroupId()
context.setLaterRunnable {
commandProcessor.executeCommand(project, {
ApplicationManager.getApplication()!!.runWriteAction(Computable<Unit> {
try {
if (rangeMarker.isValid()) {
document.deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset())
editor.getCaretModel().moveToOffset(rangeMarker.getStartOffset())
TemplateManager.getInstance(project).startTemplate(editor, buildTemplate())
}
}
finally {
rangeMarker.dispose()
}
})
}, commandName, commandGroupId)
}
}
private fun buildTemplate(): Template {
val parameterTypes = functionParameterTypes(functionType)
val nameValidator = JetNameValidator.getEmptyValidator(project) //TODO: check for names in scope
val useParenthesis = useParenthesis(useExplicitTypes, parameterTypes)
val manager = TemplateManager.getInstance(project)
val template = manager.createTemplate("", "")
template.setToShortenLongNames(true)
//template.setToReformat(true) //TODO
template.addTextSegment("{ ")
if (useParenthesis) {
template.addTextSegment("(")
}
for (i in parameterTypes.indices) {
val parameterType = parameterTypes[i]
if (i > 0) {
template.addTextSegment(", ")
}
template.addVariable(ParameterNameExpression(JetNameSuggester.suggestNames(parameterType, nameValidator, "p")), true)
if (useExplicitTypes) {
template.addTextSegment(": " + DescriptorRenderer.SOURCE_CODE.renderType(parameterType))
}
}
if (useParenthesis) {
template.addTextSegment(")")
}
template.addTextSegment(" -> ")
template.addEndVariable()
template.addTextSegment(" }")
return template
}
}
private fun functionParameterTypes(functionType: JetType): List<JetType>
= KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(functionType).map { it.getType() }
private class ParameterNameExpression(val nameSuggestions: Array<String>) : Expression() {
override fun calculateResult(context: ExpressionContext?) = TextResult(nameSuggestions[0])
override fun calculateQuickResult(context: ExpressionContext?): Result? = null
override fun calculateLookupItems(context: ExpressionContext?)
= Array<LookupElement>(nameSuggestions.size, { LookupElementBuilder.create(nameSuggestions[it]) })
}
} }
@@ -9,16 +9,9 @@ import org.jetbrains.jet.lang.types.*
import com.intellij.codeInsight.lookup.* import com.intellij.codeInsight.lookup.*
import com.intellij.codeInsight.completion.* import com.intellij.codeInsight.completion.*
import java.util.* import java.util.*
import org.jetbrains.jet.plugin.completion.DescriptorLookupConverter import org.jetbrains.jet.plugin.completion.*
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns
enum class Tail {
COMMA
PARENTHESIS
}
data class ExpectedInfo(val `type`: JetType, val tail: Tail?)
class SmartCompletion(val expression: JetSimpleNameExpression, class SmartCompletion(val expression: JetSimpleNameExpression,
val resolveSession: ResolveSessionForBodies, val resolveSession: ResolveSessionForBodies,
val visibilityFilter: (DeclarationDescriptor) -> Boolean) { val visibilityFilter: (DeclarationDescriptor) -> Boolean) {
@@ -78,7 +71,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
ThisItems(bindingContext).addToCollection(result, expressionWithType, expectedInfos) ThisItems(bindingContext).addToCollection(result, expressionWithType, expectedInfos)
LambdaItems(project).addToCollection(result, functionExpectedInfos) LambdaItems.addToCollection(result, functionExpectedInfos)
} }
return result return result
@@ -21,6 +21,7 @@ import com.intellij.codeInsight.completion.InsertionContext
import org.jetbrains.jet.plugin.project.ResolveSessionForBodies import org.jetbrains.jet.plugin.project.ResolveSessionForBodies
import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.psi.JetExpression import org.jetbrains.jet.lang.psi.JetExpression
import org.jetbrains.jet.plugin.completion.ExpectedInfo
import java.util.ArrayList import java.util.ArrayList
// adds java static members, enum members and members from class object // adds java static members, enum members and members from class object
@@ -18,6 +18,7 @@ import org.jetbrains.jet.lang.psi.JetCallExpression
import org.jetbrains.jet.lang.psi.JetSimpleNameExpression import org.jetbrains.jet.lang.psi.JetSimpleNameExpression
import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.lang.types.TypeUtils import org.jetbrains.jet.lang.types.TypeUtils
import org.jetbrains.jet.plugin.completion.ExpectedInfo
class ThisItems(val bindingContext: BindingContext) { class ThisItems(val bindingContext: BindingContext) {
public fun addToCollection(collection: MutableCollection<LookupElement>, context: JetExpression, expectedInfos: Collection<ExpectedInfo>) { public fun addToCollection(collection: MutableCollection<LookupElement>, context: JetExpression, expectedInfos: Collection<ExpectedInfo>) {
@@ -13,13 +13,13 @@ import org.jetbrains.jet.lang.descriptors.Modality
import org.jetbrains.jet.lang.descriptors.ClassKind import org.jetbrains.jet.lang.descriptors.ClassKind
import org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler import org.jetbrains.jet.plugin.codeInsight.ImplementMethodsHandler
import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor import org.jetbrains.jet.lang.descriptors.ConstructorDescriptor
import org.jetbrains.jet.plugin.completion.handlers.CaretPosition
import com.intellij.codeInsight.lookup.LookupElementDecorator import com.intellij.codeInsight.lookup.LookupElementDecorator
import com.intellij.codeInsight.lookup.LookupElementPresentation import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.codeInsight.completion.InsertionContext import com.intellij.codeInsight.completion.InsertionContext
import org.jetbrains.jet.lang.resolve.BindingContext import org.jetbrains.jet.lang.resolve.BindingContext
import org.jetbrains.jet.plugin.project.ResolveSessionForBodies import org.jetbrains.jet.plugin.project.ResolveSessionForBodies
import com.intellij.codeInsight.AutoPopupController import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler
import org.jetbrains.jet.plugin.completion.*
class TypeInstantiationItems(val bindingContext: BindingContext, val resolveSession: ResolveSessionForBodies) { class TypeInstantiationItems(val bindingContext: BindingContext, val resolveSession: ResolveSessionForBodies) {
public fun addToCollection(collection: MutableCollection<LookupElement>, expectedInfos: Collection<ExpectedInfo>) { public fun addToCollection(collection: MutableCollection<LookupElement>, expectedInfos: Collection<ExpectedInfo>) {
@@ -65,26 +65,28 @@ class TypeInstantiationItems(val bindingContext: BindingContext, val resolveSess
lookupElement = lookupElement.suppressAutoInsertion() lookupElement = lookupElement.suppressAutoInsertion()
} }
else { else {
//TODO: when constructor has one parameter of lambda type with more than one parameter, generate special additional item
itemText += "()" itemText += "()"
val constructors: Collection<ConstructorDescriptor> = classifier.getConstructors() val constructors = classifier.getConstructors()
val caretPosition = val baseInsertHandler =
if (constructors.size == 0) if (constructors.size == 0)
CaretPosition.AFTER_BRACKETS JetFunctionInsertHandler.NO_PARAMETERS_HANDLER
else if (constructors.size == 1) else if (constructors.size == 1)
if (constructors.first().getValueParameters().isEmpty()) CaretPosition.AFTER_BRACKETS else CaretPosition.IN_BRACKETS DescriptorLookupConverter.getDefaultInsertHandler(constructors.first())!!
else else
CaretPosition.IN_BRACKETS JetFunctionInsertHandler.WITH_PARAMETERS_HANDLER
insertHandler = InsertHandler<LookupElement> {(context, item) -> insertHandler = object : InsertHandler<LookupElement> {
val editor = context.getEditor() override fun handleInsert(context: InsertionContext, item: LookupElement) {
val startOffset = context.getStartOffset() context.getDocument().replaceString(context.getStartOffset(), context.getTailOffset(), typeText)
val text = typeText + "()" context.setTailOffset(context.getStartOffset() + typeText.length)
editor.getDocument().replaceString(startOffset, context.getTailOffset(), text)
val endOffset = startOffset + text.length
editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset)
shortenReferences(context, startOffset, endOffset) baseInsertHandler.handleInsert(context, item)
AutoPopupController.getInstance(context.getProject())?.autoPopupParameterInfo(editor, null) shortenReferences(context, context.getStartOffset(), context.getTailOffset())
}
}
if ((baseInsertHandler as JetFunctionInsertHandler).lambdaInfo != null) {
lookupElement.putUserData(JetCompletionCharFilter.ACCEPT_OPENING_BRACE, true)
} }
} }
@@ -17,6 +17,7 @@ import org.jetbrains.jet.lang.types.checker.JetTypeChecker
import com.intellij.codeInsight.lookup.LookupElementPresentation import com.intellij.codeInsight.lookup.LookupElementPresentation
import org.jetbrains.jet.plugin.completion.handlers.WithTailStringInsertHandler import org.jetbrains.jet.plugin.completion.handlers.WithTailStringInsertHandler
import java.util.ArrayList import java.util.ArrayList
import org.jetbrains.jet.plugin.completion.*
class ArtificialElementInsertHandler( class ArtificialElementInsertHandler(
val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler<LookupElement>{ val textBeforeCaret: String, val textAfterCaret: String, val shortenRefs: Boolean) : InsertHandler<LookupElement>{
@@ -0,0 +1,8 @@
fun foo(p: (String, Char) -> Unit){}
fun test() {
fo<caret>
}
// EXIST: { lookupString:"foo", itemText: "foo(p: (String, Char) -> Unit)", typeText:"Unit" }
// EXIST: { lookupString:"foo", itemText: "foo { (String, Char) -> ... }", typeText:"Unit" }
@@ -0,0 +1,5 @@
fun foo(p : (String, Char) -> Boolean){}
fun main(args: Array<String>) {
fo<caret>
}
@@ -0,0 +1,5 @@
fun foo(p : (String, Char) -> Boolean){}
fun main(args: Array<String>) {
foo { (s, c) -> <caret> }
}
@@ -0,0 +1,5 @@
fun foo(p : (String, Char) -> Boolean){}
fun main(args: Array<String>) {
fo<caret>
}
@@ -0,0 +1,5 @@
fun foo(p : (String, Char) -> Boolean){}
fun main(args: Array<String>) {
foo(<caret>)
}
@@ -0,0 +1,6 @@
fun foo(p : (String, Char) -> Boolean){}
fun foo(p : (String, Boolean) -> Boolean){}
fun main(args: Array<String>) {
fo<caret>
}
@@ -0,0 +1,6 @@
fun foo(p : (String, Char) -> Boolean){}
fun foo(p : (String, Boolean) -> Boolean){}
fun main(args: Array<String>) {
foo { (s: String, c: Char) -> <caret> }
}
@@ -0,0 +1,3 @@
class Foo(p : () -> Unit)
var a : Foo = <caret>
@@ -0,0 +1,3 @@
class Foo(p : () -> Unit)
var a : Foo = Foo { <caret> }
@@ -0,0 +1,3 @@
class Foo(p : (String) -> Unit)
var a : Foo = <caret>
@@ -0,0 +1,3 @@
class Foo(p : (String) -> Unit)
var a : Foo = Foo { <caret> }
@@ -5,4 +5,4 @@ fun bar() {
foo(<caret>) foo(<caret>)
} }
// ELEMENT: "{ (Date) -> ... }" // ELEMENT: "{ Date -> ... }"
@@ -7,4 +7,4 @@ fun bar() {
foo({ (date: Date) -> <caret> }) foo({ (date: Date) -> <caret> })
} }
// ELEMENT: "{ (Date) -> ... }" // ELEMENT: "{ Date -> ... }"
+2 -2
View File
@@ -6,5 +6,5 @@ fun bar() {
} }
// ABSENT: "{...}" // ABSENT: "{...}"
// EXIST: "{ (String) -> ... }" // EXIST: "{ String -> ... }"
// EXIST: "{ (Int) -> ... }" // EXIST: "{ Int -> ... }"
@@ -189,6 +189,11 @@ public class JSBasicCompletionTestGenerated extends AbstractJSBasicCompletionTes
doTest("idea/testData/completion/basic/common/FunctionCompletionFormatting.kt"); doTest("idea/testData/completion/basic/common/FunctionCompletionFormatting.kt");
} }
@TestMetadata("HigherOrderFunction1.kt")
public void testHigherOrderFunction1() throws Exception {
doTest("idea/testData/completion/basic/common/HigherOrderFunction1.kt");
}
@TestMetadata("InCallExpression.kt") @TestMetadata("InCallExpression.kt")
public void testInCallExpression() throws Exception { public void testInCallExpression() throws Exception {
doTest("idea/testData/completion/basic/common/InCallExpression.kt"); doTest("idea/testData/completion/basic/common/InCallExpression.kt");
@@ -16,17 +16,14 @@
package org.jetbrains.jet.completion; package org.jetbrains.jet.completion;
import junit.framework.Assert;
import junit.framework.Test; import junit.framework.Test;
import junit.framework.TestSuite; import junit.framework.TestSuite;
import java.io.File;
import java.util.regex.Pattern;
import org.jetbrains.jet.JetTestUtils; import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses; import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata; import org.jetbrains.jet.test.TestMetadata;
import org.jetbrains.jet.completion.AbstractJvmBasicCompletionTest; import java.io.File;
import java.util.regex.Pattern;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ /** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -189,6 +186,11 @@ public class JvmBasicCompletionTestGenerated extends AbstractJvmBasicCompletionT
doTest("idea/testData/completion/basic/common/FunctionCompletionFormatting.kt"); doTest("idea/testData/completion/basic/common/FunctionCompletionFormatting.kt");
} }
@TestMetadata("HigherOrderFunction1.kt")
public void testHigherOrderFunction1() throws Exception {
doTest("idea/testData/completion/basic/common/HigherOrderFunction1.kt");
}
@TestMetadata("InCallExpression.kt") @TestMetadata("InCallExpression.kt")
public void testInCallExpression() throws Exception { public void testInCallExpression() throws Exception {
doTest("idea/testData/completion/basic/common/InCallExpression.kt"); doTest("idea/testData/completion/basic/common/InCallExpression.kt");
@@ -46,6 +46,12 @@ public class BasicCompletionHandlerTest : CompletionHandlerTestBase(){
fun testHigherOrderFunctionWithArg() = doTest(2, "filterNot", null, '\n') fun testHigherOrderFunctionWithArg() = doTest(2, "filterNot", null, '\n')
fun testHigherOrderFunctionWithArgs1() = doTest(1, "foo", "foo { (String, Char) -> ... }", null, '\n')
fun testHigherOrderFunctionWithArgs2() = doTest(1, "foo", "foo(p: (String, Char) -> Boolean)", null, '\n')
fun testHigherOrderFunctionWithArgs3() = doTest(1, "foo", "foo { (String, Char) -> ... }", null, '\n')
fun testForceParenthesisForTabChar() = doTest(0, "some", null, '\t') fun testForceParenthesisForTabChar() = doTest(0, "some", null, '\t')
fun testTabInsertAtTheFileEnd() = doTest(0, "vvvvv", null, '\t') fun testTabInsertAtTheFileEnd() = doTest(0, "vvvvv", null, '\t')
@@ -16,17 +16,11 @@
package org.jetbrains.jet.completion.handlers; package org.jetbrains.jet.completion.handlers;
import junit.framework.Assert; import org.jetbrains.jet.JetTestUtils;
import junit.framework.Test; import org.jetbrains.jet.test.TestMetadata;
import junit.framework.TestSuite;
import java.io.File; import java.io.File;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.jetbrains.jet.completion.handlers.AbstractSmartCompletionHandlerTest;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ /** This class is generated by {@link org.jetbrains.jet.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all") @SuppressWarnings("all")
@@ -151,6 +145,16 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
doTest("idea/testData/completion/handlers/smart/ConstructorInsertsImport2.kt"); doTest("idea/testData/completion/handlers/smart/ConstructorInsertsImport2.kt");
} }
@TestMetadata("ConstructorWithLambdaParameter1.kt")
public void testConstructorWithLambdaParameter1() throws Exception {
doTest("idea/testData/completion/handlers/smart/ConstructorWithLambdaParameter1.kt");
}
@TestMetadata("ConstructorWithLambdaParameter2.kt")
public void testConstructorWithLambdaParameter2() throws Exception {
doTest("idea/testData/completion/handlers/smart/ConstructorWithLambdaParameter2.kt");
}
@TestMetadata("ConstructorWithParameters.kt") @TestMetadata("ConstructorWithParameters.kt")
public void testConstructorWithParameters() throws Exception { public void testConstructorWithParameters() throws Exception {
doTest("idea/testData/completion/handlers/smart/ConstructorWithParameters.kt"); doTest("idea/testData/completion/handlers/smart/ConstructorWithParameters.kt");