Smart completion: lambda items

This commit is contained in:
Valentin Kipyatkov
2014-04-15 18:12:32 +04:00
parent 1ba4c656a5
commit f682dc36e0
31 changed files with 289 additions and 70 deletions
@@ -54,10 +54,15 @@ public final class InTextDirectivesUtils {
List<String> result = new ArrayList<String>();
for (String line : findLinesWithPrefixesRemoved(fileText, prefixes)) {
String[] variants = line.split(",");
for (String variant : variants) {
result.add(StringUtil.unquoteString(variant.trim()));
String unquoted = StringUtil.unquoteString(line);
if (!unquoted.equals(line)) {
result.add(unquoted);
}
else{
String[] variants = line.split(",");
for (String variant : variants) {
result.add(variant.trim());
}
}
}
@@ -33,6 +33,9 @@ import org.jetbrains.jet.lang.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverValue
import com.intellij.lang.ASTNode
import org.jetbrains.jet.lang.resolve.scopes.JetScope
import org.jetbrains.jet.plugin.refactoring.JetNameSuggester
import org.jetbrains.jet.plugin.refactoring.JetNameValidator
import com.intellij.openapi.project.Project
class SmartCompletion(val expression: JetSimpleNameExpression,
val resolveSession: ResolveSessionForBodies,
@@ -40,10 +43,12 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
private val bindingContext: BindingContext
private val moduleDescriptor: ModuleDescriptor
private val project: Project
{
this.bindingContext = resolveSession.resolveToElement(expression)
this.moduleDescriptor = resolveSession.getModuleDescriptor()
this.project = expression.getProject()
}
private enum class Tail {
@@ -100,6 +105,8 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
result.addStaticMembers(expressionWithType, expectedTypes)
result.addThisItems(expressionWithType, expectedTypes)
result.addLambdaItems(functionExpectedTypes)
}
return result
@@ -245,7 +252,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
if (!(classifier is ClassDescriptor)) return
//TODO: check for constructor's visibility
val lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier)
var lookupElement = DescriptorLookupConverter.createLookupElement(resolveSession, bindingContext, classifier)
var lookupString = lookupElement.getLookupString()
@@ -253,7 +260,6 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
var itemText = lookupString + DescriptorRenderer.TEXT.renderTypeArguments(typeArgs)
val insertHandler: InsertHandler<LookupElement>
var suppressAutoInsertion: Boolean = false
val typeText = DescriptorUtils.getFqName(classifier).toString() + DescriptorRenderer.SOURCE_CODE.renderTypeArguments(typeArgs)
if (classifier.getModality() == Modality.ABSTRACT) {
val constructorParenthesis = if (classifier.getKind() != ClassKind.TRAIT) "()" else ""
@@ -267,16 +273,11 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
editor.getDocument().replaceString(startOffset, context.getTailOffset(), text)
editor.getCaretModel().moveToOffset(startOffset + text.length - 1)
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
val file = context.getFile() as JetFile
val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, startOffset + text.length, javaClass<JetElement>())
if (element != null) {
ShortenReferences.process(element)
}
shortenReferences(context, startOffset, startOffset + text.length)
ImplementMethodsHandler().invoke(context.getProject(), editor, context.getFile(), true)
}
suppressAutoInsertion = true
lookupElement = suppressAutoInsertion(lookupElement)
}
else {
itemText += "()"
@@ -298,12 +299,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
//TODO: autopopup parameter info and other functionality from JetFunctionInsertHandler
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
val file = context.getFile() as JetFile
val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, endOffset, javaClass<JetElement>())
if (element != null) {
ShortenReferences.process(element)
}
shortenReferences(context, startOffset, endOffset)
}
}
@@ -320,14 +316,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
}
}
val lookupElementWithTail = addTailToLookupElement(lookupElementDecorated, tail)
if (suppressAutoInsertion) {
add(AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElementWithTail))
}
else {
add(lookupElementWithTail)
}
add(addTailToLookupElement(lookupElementDecorated, tail))
}
private fun MutableCollection<LookupElement>.addThisItems(context: JetExpression, expectedTypes: Collection<ExpectedTypeInfo>) {
@@ -368,6 +357,65 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
?.getReferencedName()
}
private fun MutableCollection<LookupElement>.addLambdaItems(functionExpectedTypes: Collection<ExpectedTypeInfo>) {
val distinctTypes = functionExpectedTypes.map { it.`type` }.toSet()
fun createLookupElement(lookupString: String, textBeforeCaret: String, textAfterCaret: String, shortenRefs: Boolean): LookupElement {
val lookupElement = LookupElementBuilder.create(lookupString).withInsertHandler {(context, lookupElement) ->
val offset = context.getEditor().getCaretModel().getOffset()
val startOffset = offset - lookupString.length
context.getDocument().deleteString(startOffset, offset) // delete inserted lookup string
context.getDocument().insertString(startOffset, textBeforeCaret + textAfterCaret)
context.getEditor().getCaretModel().moveToOffset(startOffset + textBeforeCaret.length)
if (shortenRefs) {
shortenReferences(context, startOffset, startOffset + textBeforeCaret.length + textAfterCaret.length)
}
}
return suppressAutoInsertion(lookupElement)
}
val singleType = if (distinctTypes.size == 1) distinctTypes.single() else null
val singleSignatureLength = singleType?.let { KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(it).size }
val offerNoParametersLambda = singleSignatureLength == 0 || singleSignatureLength == 1
if (offerNoParametersLambda) {
val lookupElement = createLookupElement("{...}", "{ ", " }", shortenRefs = false)
add(addTailToLookupElement(lookupElement, functionExpectedTypes))
}
if (singleSignatureLength != 0) {
fun functionParameterTypes(functionType: JetType)
= KotlinBuiltIns.getInstance().getParameterTypeProjectionsFromFunctionType(functionType).map { it.getType() }
for (functionType in distinctTypes) {
val parameterTypes = functionParameterTypes(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 }
val nameValidator = JetNameValidator.getEmptyValidator(project)
fun parameterName(parameterType: JetType) = JetNameSuggester.suggestNames(parameterType, nameValidator, "p")[0]
fun parameterText(parameterType: JetType): String {
return if (useExplicitTypes)
parameterName(parameterType) + ": " + DescriptorRenderer.SOURCE_CODE.renderType(parameterType)
else
parameterName(parameterType)
}
val parametersText = parameterTypes.map(::parameterText).makeString(", ")
val useParenthesis = parameterTypes.size != 1
fun wrap(s: String) = if (useParenthesis) "($s)" else s
val lookupString = "{ ${wrap(parametersPresentation)} -> ... }"
val lookupElement = createLookupElement(lookupString, "{ ${wrap(parametersText)} -> ", " }", shortenRefs = true)
add(addTailToLookupElement(lookupElement, functionExpectedTypes.filter { it.`type` == functionType }))
}
}
}
private fun dataFlowToDescriptorTypes(expression: JetExpression, receiver: JetExpression?): (DeclarationDescriptor) -> Iterable<JetType> {
val dataFlowInfo = bindingContext[BindingContext.EXPRESSION_DATA_FLOW_INFO, expression]
val (variableToTypes: Map<VariableDescriptor, Collection<JetType>>, notNullVariables: Set<VariableDescriptor>)
@@ -531,12 +579,7 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
val endOffset = startOffset + text.length
editor.getCaretModel().moveToOffset(if (caretPosition == CaretPosition.IN_BRACKETS) endOffset - 1 else endOffset)
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
val file = context.getFile() as JetFile
val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, startOffset + qualifierText.length, javaClass<JetElement>())
if (element != null) {
ShortenReferences.process(element)
}
shortenReferences(context, startOffset, startOffset + qualifierText.length)
}
val lookupElementDecorated = object: LookupElementDecorator<LookupElement>(lookupElement) {
@@ -569,6 +612,15 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
}
}
private fun shortenReferences(context: InsertionContext, startOffset: Int, endOffset: Int) {
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
val file = context.getFile() as JetFile
val element = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, endOffset, javaClass<JetElement>())
if (element != null) {
ShortenReferences.process(element)
}
}
private fun mergeTails(tails: Collection<Tail?>): Tail? {
if (tails.size == 1) return tails.single()
return if (HashSet(tails).size == 1) tails.first() else null
@@ -595,6 +647,8 @@ class SmartCompletion(val expression: JetSimpleNameExpression,
private fun addTailToLookupElement(lookupElement: LookupElement, matchedExpectedTypes: Collection<ExpectedTypeInfo>): LookupElement
= addTailToLookupElement(lookupElement, mergeTails(matchedExpectedTypes.map { it.tail }))
private fun suppressAutoInsertion(lookupElement: LookupElement) = AutoCompletionPolicy.NEVER_AUTOCOMPLETE.applyPolicy(lookupElement)
private fun functionType(function: FunctionDescriptor): JetType? {
return KotlinBuiltIns.getInstance().getKFunctionType(function.getAnnotations(),
null,
@@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.lang.descriptors.ClassDescriptor;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
@@ -41,8 +42,8 @@ public class JetNameSuggester {
private JetNameSuggester() {
}
private static void addName(ArrayList<String> result, String name, JetNameValidator validator) {
if (name == "class") name = "clazz";
private static void addName(ArrayList<String> result, @Nullable String name, JetNameValidator validator) {
if ("class".equals(name)) name = "clazz";
if (!isIdentifier(name)) return;
String newName = validator.validateName(name);
if (newName == null) return;
@@ -62,7 +63,7 @@ public class JetNameSuggester {
* @param defaultName
* @return possible names
*/
public static String[] suggestNames(JetExpression expression, JetNameValidator validator, String defaultName) {
public static @NotNull String[] suggestNames(@NotNull JetExpression expression, @NotNull JetNameValidator validator, @Nullable String defaultName) {
ArrayList<String> result = new ArrayList<String>();
BindingContext bindingContext = AnalyzerFacadeWithCache.getContextForElement(expression);
@@ -76,20 +77,20 @@ public class JetNameSuggester {
return ArrayUtil.toStringArray(result);
}
public static String[] suggestNames(JetType type, JetNameValidator validator, String defaultName) {
public static @NotNull String[] suggestNames(@NotNull JetType type, @NotNull JetNameValidator validator, @Nullable String defaultName) {
ArrayList<String> result = new ArrayList<String>();
addNamesForType(result, type, validator);
if (result.isEmpty()) addName(result, defaultName, validator);
return ArrayUtil.toStringArray(result);
}
public static String[] suggestNamesForType(JetType jetType, JetNameValidator validator) {
public static @NotNull String[] suggestNamesForType(@NotNull JetType jetType, @NotNull JetNameValidator validator) {
ArrayList<String> result = new ArrayList<String>();
addNamesForType(result, jetType, validator);
return ArrayUtil.toStringArray(result);
}
public static String[] suggestNamesForExpression(JetExpression expression, JetNameValidator validator) {
public static @NotNull String[] suggestNamesForExpression(@NotNull JetExpression expression, @NotNull JetNameValidator validator) {
ArrayList<String> result = new ArrayList<String>();
addNamesForExpression(result, expression, validator);
return ArrayUtil.toStringArray(result);
@@ -240,7 +241,7 @@ public class JetNameSuggester {
});
}
public static boolean isIdentifier(String name) {
public static boolean isIdentifier(@Nullable String name) {
ApplicationManager.getApplication().assertReadAccessAllowed();
if (name == null || name.isEmpty()) return false;
@@ -17,6 +17,7 @@
package org.jetbrains.jet.plugin.refactoring;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
@@ -28,6 +29,7 @@ public abstract class JetNameValidator {
this.project = project;
}
@NotNull
public static JetNameValidator getEmptyValidator(final Project project) {
return new JetNameValidator(project) {
@Override
@@ -37,6 +39,7 @@ public abstract class JetNameValidator {
};
}
@NotNull
public static JetNameValidator getCollectingValidator(final Project project) {
return new JetNameValidator(project) {
private final Set<String> suggestedSet = new HashSet<String>();
@@ -0,0 +1,7 @@
fun foo(p: () -> Unit){}
fun bar() {
foo(<caret>)
}
// ELEMENT: "{...}"
@@ -0,0 +1,7 @@
fun foo(p: () -> Unit){}
fun bar() {
foo({ <caret> })
}
// ELEMENT: "{...}"
@@ -0,0 +1,7 @@
fun foo(p: () -> Unit, i: Int){}
fun bar() {
foo(<caret>)
}
// ELEMENT: "{...}"
@@ -0,0 +1,7 @@
fun foo(p: () -> Unit, i: Int){}
fun bar() {
foo({ <caret> }, )
}
// ELEMENT: "{...}"
@@ -0,0 +1,7 @@
fun foo(p: (Int) -> Unit){}
fun bar() {
foo(<caret>)
}
// ELEMENT: "{ Int -> ... }"
@@ -0,0 +1,7 @@
fun foo(p: (Int) -> Unit){}
fun bar() {
foo({ i -> <caret> })
}
// ELEMENT: "{ Int -> ... }"
@@ -0,0 +1,8 @@
fun foo(p: (String, StringBuilder) -> Unit){}
fun foo(p: (String) -> Unit){}
fun bar() {
foo(<caret>)
}
// ELEMENT: "{ (String, StringBuilder) -> ... }"
@@ -0,0 +1,8 @@
fun foo(p: (String, StringBuilder) -> Unit){}
fun foo(p: (String) -> Unit){}
fun bar() {
foo({ (s, stringBuilder) -> <caret> })
}
// ELEMENT: "{ (String, StringBuilder) -> ... }"
@@ -0,0 +1,8 @@
fun foo(p: (String, StringBuilder) -> Unit){}
fun foo(p: (Int, Char) -> Unit){}
fun bar() {
foo(<caret>)
}
// ELEMENT: "{ (String, StringBuilder) -> ... }"
@@ -0,0 +1,8 @@
fun foo(p: (String, StringBuilder) -> Unit){}
fun foo(p: (Int, Char) -> Unit){}
fun bar() {
foo({ (s: String, stringBuilder: StringBuilder) -> <caret> })
}
// ELEMENT: "{ (String, StringBuilder) -> ... }"
@@ -0,0 +1,7 @@
fun foo(p: (String, String, String) -> Unit){}
fun bar() {
foo(<caret>)
}
// ELEMENT: "{ (String, String, String) -> ... }"
@@ -0,0 +1,8 @@
fun foo(p: () -> Unit){}
fun bar() {
foo(<caret>)
}
// EXIST: "{...}"
// ABSENT: "{ () -> ... }"
@@ -0,0 +1,8 @@
fun foo(p: (String) -> Unit){}
fun bar() {
foo(<caret>)
}
// EXIST: "{...}"
// EXIST: "{ String -> ... }"
@@ -0,0 +1,8 @@
fun foo(p: (String, Int) -> Unit){}
fun bar() {
foo(<caret>)
}
// ABSENT: "{...}"
// EXIST: "{ (String, Int) -> ... }"
+10
View File
@@ -0,0 +1,10 @@
fun foo(p: (String) -> Unit){}
fun foo(p: (Int) -> Unit){}
fun bar() {
foo(<caret>)
}
// ABSENT: "{...}"
// EXIST: "{ String -> ... }"
// EXIST: "{ Int -> ... }"
@@ -19,7 +19,6 @@ package org.jetbrains.jet.completion;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@@ -179,6 +178,9 @@ public class ExpectedCompletionUtils {
JsonElement json = parser.parse(proposalStr);
proposals.add(new CompletionProposal((JsonObject) json));
}
else if (proposalStr.startsWith("\"") && proposalStr.endsWith("\"")) {
proposals.add(new CompletionProposal(proposalStr.substring(1, proposalStr.length() - 1)));
}
else{
for(String item : proposalStr.split(",")){
proposals.add(new CompletionProposal(item.trim()));
@@ -131,39 +131,39 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest("idea/testData/completion/smart/EnumMembers.kt");
}
@TestMetadata("FunctionExpected1.kt")
public void testFunctionExpected1() throws Exception {
doTest("idea/testData/completion/smart/FunctionExpected1.kt");
@TestMetadata("FunctionReference1.kt")
public void testFunctionReference1() throws Exception {
doTest("idea/testData/completion/smart/FunctionReference1.kt");
}
@TestMetadata("FunctionExpected3.kt")
public void testFunctionExpected3() throws Exception {
doTest("idea/testData/completion/smart/FunctionExpected3.kt");
@TestMetadata("FunctionReference3.kt")
public void testFunctionReference3() throws Exception {
doTest("idea/testData/completion/smart/FunctionReference3.kt");
}
@TestMetadata("FunctionExpected4.kt")
public void testFunctionExpected4() throws Exception {
doTest("idea/testData/completion/smart/FunctionExpected4.kt");
@TestMetadata("FunctionReference4.kt")
public void testFunctionReference4() throws Exception {
doTest("idea/testData/completion/smart/FunctionReference4.kt");
}
@TestMetadata("FunctionExpected5.kt")
public void testFunctionExpected5() throws Exception {
doTest("idea/testData/completion/smart/FunctionExpected5.kt");
@TestMetadata("FunctionReference5.kt")
public void testFunctionReference5() throws Exception {
doTest("idea/testData/completion/smart/FunctionReference5.kt");
}
@TestMetadata("FunctionExpected6.kt")
public void testFunctionExpected6() throws Exception {
doTest("idea/testData/completion/smart/FunctionExpected6.kt");
@TestMetadata("FunctionReference6.kt")
public void testFunctionReference6() throws Exception {
doTest("idea/testData/completion/smart/FunctionReference6.kt");
}
@TestMetadata("FunctionExpected7.kt")
public void testFunctionExpected7() throws Exception {
doTest("idea/testData/completion/smart/FunctionExpected7.kt");
@TestMetadata("FunctionReference7.kt")
public void testFunctionReference7() throws Exception {
doTest("idea/testData/completion/smart/FunctionReference7.kt");
}
@TestMetadata("FunctionExpected9.kt")
public void testFunctionExpected9() throws Exception {
doTest("idea/testData/completion/smart/FunctionExpected9.kt");
@TestMetadata("FunctionReference9.kt")
public void testFunctionReference9() throws Exception {
doTest("idea/testData/completion/smart/FunctionReference9.kt");
}
@TestMetadata("InsideIdentifier.kt")
@@ -201,6 +201,26 @@ public class JvmSmartCompletionTestGenerated extends AbstractJvmSmartCompletionT
doTest("idea/testData/completion/smart/JavaStaticMethods.kt");
}
@TestMetadata("Lambda1.kt")
public void testLambda1() throws Exception {
doTest("idea/testData/completion/smart/Lambda1.kt");
}
@TestMetadata("Lambda2.kt")
public void testLambda2() throws Exception {
doTest("idea/testData/completion/smart/Lambda2.kt");
}
@TestMetadata("Lambda3.kt")
public void testLambda3() throws Exception {
doTest("idea/testData/completion/smart/Lambda3.kt");
}
@TestMetadata("Lambda4.kt")
public void testLambda4() throws Exception {
doTest("idea/testData/completion/smart/Lambda4.kt");
}
@TestMetadata("MethodCallArgument.kt")
public void testMethodCallArgument() throws Exception {
doTest("idea/testData/completion/smart/MethodCallArgument.kt");
@@ -16,17 +16,11 @@
package org.jetbrains.jet.completion.handlers;
import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestSuite;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.TestMetadata;
import java.io.File;
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 */
@SuppressWarnings("all")
@@ -196,6 +190,31 @@ public class SmartCompletionHandlerTestGenerated extends AbstractSmartCompletion
doTest("idea/testData/completion/handlers/smart/JavaStaticMethodInsertsImport.kt");
}
@TestMetadata("Lambda1.kt")
public void testLambda1() throws Exception {
doTest("idea/testData/completion/handlers/smart/Lambda1.kt");
}
@TestMetadata("Lambda2.kt")
public void testLambda2() throws Exception {
doTest("idea/testData/completion/handlers/smart/Lambda2.kt");
}
@TestMetadata("Lambda3.kt")
public void testLambda3() throws Exception {
doTest("idea/testData/completion/handlers/smart/Lambda3.kt");
}
@TestMetadata("Lambda4.kt")
public void testLambda4() throws Exception {
doTest("idea/testData/completion/handlers/smart/Lambda4.kt");
}
@TestMetadata("Lambda5.kt")
public void testLambda5() throws Exception {
doTest("idea/testData/completion/handlers/smart/Lambda5.kt");
}
@TestMetadata("MergeTail1.kt")
public void testMergeTail1() throws Exception {
doTest("idea/testData/completion/handlers/smart/MergeTail1.kt");