diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/DescriptorsUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/DescriptorsUtils.java new file mode 100644 index 00000000000..dd4081398c2 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/lang/descriptors/DescriptorsUtils.java @@ -0,0 +1,40 @@ +package org.jetbrains.jet.lang.descriptors; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Nikolay Krasko + */ +public final class DescriptorsUtils { + + private DescriptorsUtils() { + } + + public static String getFQName(@NotNull NamespaceDescriptor namespaceDescriptor) { + + NamespaceDescriptor tempNamespace = namespaceDescriptor; + String fqn = tempNamespace.getName(); + + while (tempNamespace.getContainingDeclaration() instanceof NamespaceDescriptor) { + tempNamespace = (NamespaceDescriptor) tempNamespace.getContainingDeclaration(); + + assert tempNamespace != null; + fqn = tempNamespace.getName() + "." + fqn; + } + + return fqn; + } + + public static String getFQName(@NotNull NamedFunctionDescriptor functionDescriptor) { + if (functionDescriptor.getContainingDeclaration() instanceof NamespaceDescriptor) { + final String namespaceFQN = getFQName((NamespaceDescriptor) functionDescriptor.getContainingDeclaration()); + return !namespaceFQN.isEmpty() ? namespaceFQN + "." + functionDescriptor.getName() : functionDescriptor.getName(); + } + + throw new IllegalArgumentException("Currently supported only for top level functions"); + } + + public static boolean isTopLevelFunction(@NotNull NamedFunctionDescriptor functionDescriptor) { + return functionDescriptor.getContainingDeclaration() instanceof NamespaceDescriptor; + } +} diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java index b1789879dd6..3f1121d9a19 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/psi/JetPsiUtil.java @@ -107,17 +107,6 @@ public class JetPsiUtil { } } -// public static String getFQName(JetNamespace jetNamespace) { -// JetNamespace parent = PsiTreeUtil.getParentOfType(jetNamespace, JetNamespace.class); -// if (parent != null) { -// String parentFQName = getFQName(parent); -// if (parentFQName.length() > 0) { -// return parentFQName + "." + getFQName(jetNamespace.getNamespaceHeader()); -// } -// } -// return getFQName(jetNamespace.getNamespaceHeader()); // TODO: Must include module root namespace -// } - private static String getFQName(JetNamespaceHeader header) { StringBuilder builder = new StringBuilder(); for (JetSimpleNameExpression nameExpression : header.getParentNamespaceNames()) { diff --git a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AnalyzingUtils.java b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AnalyzingUtils.java index 3edb5e6a0ae..bab57d0a763 100644 --- a/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AnalyzingUtils.java +++ b/compiler/frontend/src/org/jetbrains/jet/lang/resolve/AnalyzingUtils.java @@ -88,11 +88,14 @@ public class AnalyzingUtils { @NotNull JetControlFlowDataTraceFactory flowDataTraceFactory, @NotNull BindingTraceContext bindingTraceContext, @NotNull JetSemanticServices semanticServices) { + JetScope libraryScope = semanticServices.getStandardLibrary().getLibraryScope(); ModuleDescriptor owner = new ModuleDescriptor(""); - final WritableScope scope = new WritableScopeImpl(JetScope.EMPTY, owner, new TraceBasedRedeclarationHandler(bindingTraceContext)).setDebugName("Root scope in analyzeNamespace"); -// configuration.addImports(project, semanticServices, bindingTraceContext, scope); + final WritableScope scope = new WritableScopeImpl( + JetScope.EMPTY, owner, + new TraceBasedRedeclarationHandler(bindingTraceContext)).setDebugName("Root scope in analyzeNamespace"); + scope.importScope(libraryScope); scope.changeLockLevel(WritableScope.LockLevel.BOTH); @@ -136,6 +139,7 @@ public class AnalyzingUtils { throw new IllegalStateException("Must be guaranteed not to happen by the parser"); } }, files, filesToAnalyzeCompletely, flowDataTraceFactory, configuration); + return bindingTraceContext.getBindingContext(); } diff --git a/compiler/frontend/src/org/jetbrains/jet/util/QualifiedNamesUtil.java b/compiler/frontend/src/org/jetbrains/jet/util/QualifiedNamesUtil.java new file mode 100644 index 00000000000..553f735cc05 --- /dev/null +++ b/compiler/frontend/src/org/jetbrains/jet/util/QualifiedNamesUtil.java @@ -0,0 +1,71 @@ +package org.jetbrains.jet.util; + +import com.intellij.openapi.util.text.StringUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Common methods for working with qualified names strings. + * + * @author Nikolay Krasko + */ +public final class QualifiedNamesUtil { + + public static boolean isSubpackageOf(@NotNull final String subpackageName, @NotNull String packageName) { + return subpackageName.equals(packageName) || + (subpackageName.startsWith(packageName) && subpackageName.charAt(packageName.length()) == '.'); + } + + public static boolean isShortNameForFQN(@NotNull final String name, @NotNull final String fqn) { + return fqn.equals(name) || + (fqn.endsWith(name) && fqn.charAt(fqn.length() - name.length() - 1) == '.'); + } + + public static boolean isOneSegmentFQN(@NotNull final String fqn) { + if (fqn.isEmpty()) { + return false; + } + + return fqn.indexOf('.') < 0; + } + + @NotNull + public static String fqnToShortName(@NotNull String fqName) { + int lastDotIndex = fqName.lastIndexOf('.'); + + if (lastDotIndex != -1) { + return fqName.substring(lastDotIndex + 1, fqName.length()); + } + + return fqName; + } + + @NotNull + public static String withoutLastSegment(@NotNull String fqn) { + final int lastDotIndex = fqn.lastIndexOf('.'); + if (lastDotIndex > 0) { + return fqn.substring(0, lastDotIndex); + } + + return ""; + } + + public static String combine(@Nullable String first, @NotNull String second) { + if (StringUtil.isEmpty(first)) return second; + return first + "." + second; + } + +// private static String subPackageName(String packageName, String packageFQN) { +// if (!isInPackage(packageName, packageFQN)) { +// return null; +// } +// +// int nextDotIndex = packageFQN.indexOf('.', (packageName + ".").length()); +// +// if (nextDotIndex != -1) { +// return packageFQN.substring((packageName + ".").length(), nextDotIndex); +// } +// +// return packageFQN.substring((packageName + ".").length()); +// } +} diff --git a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java index 3cf99a79baa..7cc206bb7f9 100644 --- a/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java +++ b/compiler/jet.as.java.psi/src/org/jetbrains/jet/asJava/JavaElementFinder.java @@ -4,7 +4,6 @@ package org.jetbrains.jet.asJava; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.*; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElementFinder; @@ -17,6 +16,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.jet.codegen.JetTypeMapper; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.java.JvmAbi; +import org.jetbrains.jet.util.QualifiedNamesUtil; import java.util.HashSet; import java.util.List; @@ -74,7 +74,7 @@ public class JavaElementFinder extends PsiElementFinder { for (JetFile file : filesInScope) { final String packageName = JetPsiUtil.getFQName(file); if (packageName != null && qualifiedName.startsWith(packageName)) { - if (qualifiedName.equals(fqn(packageName, JvmAbi.PACKAGE_CLASS))) { + if (qualifiedName.equals(QualifiedNamesUtil.combine(packageName, JvmAbi.PACKAGE_CLASS))) { answer.add(new JetLightClass(psiManager, file, qualifiedName)); } else { @@ -91,7 +91,7 @@ public class JavaElementFinder extends PsiElementFinder { if (declaration instanceof JetClassOrObject) { String localName = getLocalName(declaration); if (localName != null) { - String fqn = fqn(containerFqn, localName); + String fqn = QualifiedNamesUtil.combine(containerFqn, localName); if (qualifiedName.equals(fqn)) { answer.add(new JetLightClass(psiManager, file, qualifiedName)); } @@ -136,11 +136,6 @@ public class JavaElementFinder extends PsiElementFinder { return answer; } - private static String fqn(String packageName, String className) { - if (StringUtil.isEmpty(packageName)) return className; - return packageName + "." + className; - } - @Override public PsiPackage findPackage(@NotNull String qualifiedName) { final List psiFiles = collectProjectJetFiles(project, GlobalSearchScope.allScope(project)); @@ -163,7 +158,10 @@ public class JavaElementFinder extends PsiElementFinder { for (JetFile psiFile : psiFiles) { String jetRootNamespace = JetPsiUtil.getFQName(psiFile); - if (isInPackage(psiPackage.getQualifiedName(), jetRootNamespace)) { + + if (QualifiedNamesUtil.isSubpackageOf(jetRootNamespace, psiPackage.getQualifiedName())) { + + // TODO: wrong package here answer.add(new JetLightPackage(psiFile.getManager(), jetRootNamespace)); } } @@ -179,10 +177,10 @@ public class JavaElementFinder extends PsiElementFinder { String packageFQN = psiPackage.getQualifiedName(); for (JetFile file : filesInScope) { if (packageFQN.equals(JetPsiUtil.getFQName(file))) { - answer.add(new JetLightClass(psiManager, file, fqn(packageFQN, JvmAbi.PACKAGE_CLASS))); + answer.add(new JetLightClass(psiManager, file, QualifiedNamesUtil.combine(packageFQN, JvmAbi.PACKAGE_CLASS))); for (JetDeclaration declaration : file.getDeclarations()) { if (declaration instanceof JetClassOrObject) { - answer.add(new JetLightClass(psiManager, file, fqn(packageFQN, getLocalName(declaration)))); + answer.add(new JetLightClass(psiManager, file, QualifiedNamesUtil.combine(packageFQN, getLocalName(declaration)))); } } } @@ -191,24 +189,6 @@ public class JavaElementFinder extends PsiElementFinder { return answer.toArray(new PsiClass[answer.size()]); } - private static boolean isInPackage(String packageName, String fqn) { - return fqn.startsWith(packageName + "."); - } - - private static String subPackageName(String packageName, String packageFQN) { - if (!isInPackage(packageName, packageFQN)) { - return null; - } - - int nextDotIndex = packageFQN.indexOf('.', (packageName + ".").length()); - - if (nextDotIndex != -1) { - return packageFQN.substring((packageName + ".").length(), nextDotIndex); - } - - return packageFQN.substring((packageName + ".").length()); - } - private synchronized void invalidateJetFilesCache() { jetFiles.clear(); } diff --git a/idea/src/org/jetbrains/jet/plugin/caches/JetShortNamesCache.java b/idea/src/org/jetbrains/jet/plugin/caches/JetShortNamesCache.java index 3c0ef5132b5..77888d36bfa 100644 --- a/idea/src/org/jetbrains/jet/plugin/caches/JetShortNamesCache.java +++ b/idea/src/org/jetbrains/jet/plugin/caches/JetShortNamesCache.java @@ -14,17 +14,21 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.asJava.JavaElementFinder; import org.jetbrains.jet.lang.descriptors.NamedFunctionDescriptor; +import org.jetbrains.jet.lang.descriptors.NamespaceDescriptor; import org.jetbrains.jet.lang.psi.JetNamedFunction; import org.jetbrains.jet.lang.resolve.BindingContext; +import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import org.jetbrains.jet.plugin.compiler.WholeProjectAnalyzerFacade; import org.jetbrains.jet.plugin.stubindex.JetExtensionFunctionNameIndex; import org.jetbrains.jet.plugin.stubindex.JetFullClassNameIndex; import org.jetbrains.jet.plugin.stubindex.JetShortClassNameIndex; import org.jetbrains.jet.plugin.stubindex.JetShortFunctionNameIndex; +import org.jetbrains.jet.util.QualifiedNamesUtil; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; /** * Will provide both java elements from jet context and some special declarations special to jet. @@ -68,7 +72,7 @@ public class JetShortNamesCache extends PsiShortNamesCache { List result = new ArrayList(); for (String fqName : JetFullClassNameIndex.getInstance().getAllKeys(project)) { - if (fqnToShortName(fqName).equals(name)) { + if (QualifiedNamesUtil.fqnToShortName(fqName).equals(name)) { final PsiClass psiClass = javaElementFinder.findClass(fqName, scope); if (psiClass != null) { result.add(psiClass); @@ -84,56 +88,18 @@ public class JetShortNamesCache extends PsiShortNamesCache { // TODO: Implement it. Is it called somewhere? } - public static String fqnToShortName(String fqName) { - int lastDotIndex = fqName.lastIndexOf('.'); - - if (lastDotIndex != -1) { - return fqName.substring(lastDotIndex + 1, fqName.length()); - } - - return fqName; - } - public Collection getALlJetClassFQNames() { - final BindingContext context = WholeProjectAnalyzerFacade.analyzeProjectWithCache(project, GlobalSearchScope.allScope(project)); + final BindingContext context = getResolutionContext(GlobalSearchScope.allScope(project)); return context.getKeys(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR); } -// public Collection getClassDescriptors() { -// final BindingContext context = WholeProjectAnalyzerFacade.analyzeProjectWithCache(project, GlobalSearchScope.allScope(project)); -// -// final Collection fqNames = context.getKeys(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR); -// return Collections2.transform(fqNames, new Function() { -// @Override -// public ClassDescriptor apply(String fqName) { -// return context.get(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR, fqName); -// } -// }); -// } - -// public List getJetClassesByName(@NotNull String name, @NotNull GlobalSearchScope scope) { -// final BindingContext context = WholeProjectAnalyzerFacade.analyzeProjectWithCache(project, scope); -// final ArrayList classesResult = new ArrayList(); -// -// for (String fqn : getFQNamesByName(name, scope)) { -// final ClassDescriptor descriptor = context.get(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR, fqn); -// final PsiElement declaration = context.get(BindingContext.DESCRIPTOR_TO_DECLARATION, descriptor); -// -// if (declaration instanceof JetClass) { -// classesResult.add((JetClass) declaration); -// } -// } -// -// return classesResult; -// } - @NotNull public Collection getFQNamesByName(@NotNull final String name, @NotNull GlobalSearchScope scope) { - final BindingContext context = WholeProjectAnalyzerFacade.analyzeProjectWithCache(project, scope); + final BindingContext context = getResolutionContext(scope); return Collections2.filter(context.getKeys(BindingContext.FQNAME_TO_CLASS_DESCRIPTOR), new Predicate() { @Override public boolean apply(@Nullable String fqName) { - return fqName != null && (fqName.endsWith('.' + name) || fqName.equals(name)); + return fqName != null && QualifiedNamesUtil.isShortNameForFQN(name, fqName); } }); } @@ -160,7 +126,7 @@ public class JetShortNamesCache extends PsiShortNamesCache { final Collection jetNamedFunctions = JetShortFunctionNameIndex.getInstance().get(name, project, scope); - final BindingContext context = WholeProjectAnalyzerFacade.analyzeProjectWithCache(project, scope); + final BindingContext context = getResolutionContext(scope); final HashSet result = new HashSet(); @@ -174,20 +140,52 @@ public class JetShortNamesCache extends PsiShortNamesCache { return result; } + @NotNull + public BindingContext getResolutionContext(final @NotNull GlobalSearchScope scope) { + return WholeProjectAnalyzerFacade.analyzeProjectWithCache(project, scope); + } + public Collection getTopLevelFunctionsByName(final @NotNull String name, @NotNull GlobalSearchScope scope) { return JetShortFunctionNameIndex.getInstance().get(name, project, scope); } + /** + * Get jet extensions top-level function names. Method is allowed to give invalid names - all result should be + * checked with getAllJetExtensionFunctionsByName(). + * + * @return + */ @NotNull public Collection getAllJetExtensionFunctionsNames(@NotNull GlobalSearchScope scope) { - - - JetExtensionFunctionNameIndex.getInstance().getKey(); - return JetFromJavaDescriptorHelper.getTopExtensionFunctionNames(null, project, scope); + final Set extensionFunctionNames = new HashSet(); + + extensionFunctionNames.addAll(JetExtensionFunctionNameIndex.getInstance().getAllKeys(project)); + extensionFunctionNames.addAll(JetFromJavaDescriptorHelper.getTopExtensionFunctionNames(null, project, scope)); + + return extensionFunctionNames; } - public Collection getAllJetExtensionFunctionsByName(@NotNull String name, @NotNull GlobalSearchScope scope) { - return JetExtensionFunctionNameIndex.getInstance().get(name, project, scope); + public Collection getAllJetExtensionFunctionsByName(@NotNull String name, @NotNull GlobalSearchScope scope) { + // TODO: Add jet extension functions in jar-dependencies (those functions are missing in BindingContext and stubs) + + final Collection jetNamedFunctions = JetShortFunctionNameIndex.getInstance().get(name, project, scope); + + final BindingContext context = getResolutionContext(scope); + + final HashSet result = new HashSet(); + + for (JetNamedFunction jetNamedFunction : jetNamedFunctions) { + final NamedFunctionDescriptor functionDescriptor = context.get(BindingContext.FUNCTION, jetNamedFunction); + if (functionDescriptor != null) { + if (functionDescriptor.getContainingDeclaration() instanceof NamespaceDescriptor) { + if (functionDescriptor.getExpectedThisObject() != ReceiverDescriptor.NO_RECEIVER) { + result.add(functionDescriptor); + } + } + } + } + + return result; } @NotNull diff --git a/idea/src/org/jetbrains/jet/plugin/completion/DescriptorLookupConverter.java b/idea/src/org/jetbrains/jet/plugin/completion/DescriptorLookupConverter.java index 63f25c24132..4503b9e7378 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/DescriptorLookupConverter.java +++ b/idea/src/org/jetbrains/jet/plugin/completion/DescriptorLookupConverter.java @@ -29,17 +29,9 @@ public final class DescriptorLookupConverter { private DescriptorLookupConverter() {} @NotNull - public static LookupElement createLookupElement(@Nullable BindingContext context, @NotNull DeclarationDescriptor descriptor, @Nullable PsiElement declaration) { + public static LookupElement createLookupElement(@NotNull DeclarationDescriptor descriptor, @Nullable PsiElement declaration) { - Object lookupObject = descriptor; - if (context != null) { - final PsiElement psiElement = context.get(BindingContext.DESCRIPTOR_TO_DECLARATION, descriptor); - if (psiElement != null) { - lookupObject = new LookupPositionObject(psiElement); - } - } - - LookupElementBuilder element = LookupElementBuilder.create(lookupObject, descriptor.getName()); + LookupElementBuilder element = LookupElementBuilder.create(new JetLookupObject(descriptor, declaration), descriptor.getName()); String typeText = ""; String tailText = ""; boolean tailTextGrayed = false; @@ -89,6 +81,6 @@ public final class DescriptorLookupConverter { @NotNull public static LookupElement createLookupElement(@NotNull BindingContext bindingContext, @NotNull DeclarationDescriptor descriptor) { - return createLookupElement(bindingContext, descriptor, bindingContext.get(BindingContext.DESCRIPTOR_TO_DECLARATION, descriptor.getOriginal())); + return createLookupElement(descriptor, bindingContext.get(BindingContext.DESCRIPTOR_TO_DECLARATION, descriptor)); } } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.java b/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.java index 163ac99ee6a..c4f19406d6c 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.java +++ b/idea/src/org/jetbrains/jet/plugin/completion/JetCompletionContributor.java @@ -18,6 +18,7 @@ import org.jetbrains.jet.lang.psi.JetFile; import org.jetbrains.jet.lang.psi.JetQualifiedExpression; import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; import org.jetbrains.jet.lang.psi.JetUserType; +import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lexer.JetTokens; import org.jetbrains.jet.plugin.caches.JetCacheManager; import org.jetbrains.jet.plugin.caches.JetShortNamesCache; @@ -52,12 +53,11 @@ public class JetCompletionContributor extends CompletionContributor { final JetSimpleNameReference jetReference = getJetReference(parameters); if (jetReference != null) { for (Object variant : jetReference.getVariants()) { - addReferenceVariant(result, variant, positions); + addReferenceVariant(result, variant, positions, parameters.getPosition().getProject()); } } if (shouldRunTopLevelCompletion(parameters)) { - // Jet classes will be added as java completions for unification addClasses(parameters, result, positions); addJetTopLevelFunctions(result, position, positions); } @@ -67,13 +67,17 @@ public class JetCompletionContributor extends CompletionContributor { }); } - private static void addReferenceVariant(@NotNull CompletionResultSet result, @NotNull Object variant, - @NotNull final HashSet positions) { + private static void addReferenceVariant( + @NotNull CompletionResultSet result, + @NotNull Object variant, + @NotNull final HashSet positions, + @NotNull final Project project) { + if (variant instanceof LookupElement) { - addCompletionToResult(result, (LookupElement) variant, positions); + addCompletionToResult(result, (LookupElement) variant, positions, project); } else { - addCompletionToResult(result, LookupElementBuilder.create(variant.toString()), positions); + addCompletionToResult(result, LookupElementBuilder.create(variant.toString()), positions, project); } } @@ -88,16 +92,25 @@ public class JetCompletionContributor extends CompletionContributor { final GlobalSearchScope scope = GlobalSearchScope.allScope(project); final Collection functionNames = namesCache.getAllTopLevelFunctionNames(); + BindingContext resolutionContext = namesCache.getResolutionContext(scope); + for (String name : functionNames) { if (name.contains(actualPrefix)) { for (FunctionDescriptor function : namesCache.getTopLevelFunctionDescriptorsByName(name, scope)) { - addCompletionToResult(result, DescriptorLookupConverter.createLookupElement(null, function, null), positions); + addCompletionToResult(result, DescriptorLookupConverter.createLookupElement(resolutionContext, function), positions, project); } } } } - private static void addClasses(@NotNull CompletionParameters parameters, @NotNull final CompletionResultSet result, final HashSet positions) { + /** + * Jet classes will be added as java completions for unification + */ + private static void addClasses( + @NotNull final CompletionParameters parameters, + @NotNull final CompletionResultSet result, + @NotNull final HashSet positions) { + CompletionResultSet tempResult = result.withPrefixMatcher(CompletionUtil.findReferenceOrAlphanumericPrefix(parameters)); JavaClassNameCompletionContributor.addAllClasses( @@ -113,7 +126,7 @@ public class JetCompletionContributor extends CompletionContributor { javaPsiReferenceElement.setInsertHandler(JetJavaClassInsertHandler.JAVA_CLASS_INSERT_HANDLER); } - addCompletionToResult(result, element, positions); + addCompletionToResult(result, element, positions, parameters.getPosition().getProject()); } }); } @@ -162,18 +175,31 @@ public class JetCompletionContributor extends CompletionContributor { return null; } - private static void addCompletionToResult(@NotNull final CompletionResultSet result, LookupElement element, HashSet positions) { - if (element.getObject() instanceof LookupPositionObject) { - final LookupPositionObject positionObject = (LookupPositionObject) element.getObject(); + private static void addCompletionToResult( + @NotNull final CompletionResultSet result, + @NotNull LookupElement element, + @NotNull HashSet positions, + @NotNull Project project) { - if (positions.contains(positionObject)) { - return; - } - else { - positions.add(positionObject); + final LookupPositionObject lookupPosition = getLookupPosition(element, project); + if (lookupPosition != null && !positions.contains(lookupPosition)) { + positions.add(lookupPosition); + result.addElement(element); + } + } + + private static LookupPositionObject getLookupPosition(LookupElement element, Project project) { + final Object lookupObject = element.getObject(); + if (lookupObject instanceof PsiElement) { + return new LookupPositionObject((PsiElement) lookupObject); + } + else if (lookupObject instanceof JetLookupObject) { + final PsiElement psiElement = ((JetLookupObject) lookupObject).getPsiElement(); + if (psiElement != null) { + return new LookupPositionObject(psiElement); } } - result.addElement(element); + return null; } } diff --git a/idea/src/org/jetbrains/jet/plugin/completion/JetLookupObject.java b/idea/src/org/jetbrains/jet/plugin/completion/JetLookupObject.java new file mode 100644 index 00000000000..bc27f49f0ee --- /dev/null +++ b/idea/src/org/jetbrains/jet/plugin/completion/JetLookupObject.java @@ -0,0 +1,35 @@ +package org.jetbrains.jet.plugin.completion; + +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; + +/** + * Stores information about resolved descriptor and position of that descriptor. + * Position will be used for removing duplicates + * + * @author Nikolay Krasko + */ +public class JetLookupObject { + @NotNull + private final DeclarationDescriptor descriptor; + + @Nullable + private final PsiElement psiElement; + + public JetLookupObject(@NotNull DeclarationDescriptor descriptor, @Nullable PsiElement psiElement) { + this.descriptor = descriptor; + this.psiElement = psiElement; + } + + @NotNull + public DeclarationDescriptor getDescriptor() { + return descriptor; + } + + @Nullable + public PsiElement getPsiElement() { + return psiElement; + } +} diff --git a/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetFunctionInsertHandler.java b/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetFunctionInsertHandler.java index 8a35b107592..37a6c19cf39 100644 --- a/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetFunctionInsertHandler.java +++ b/idea/src/org/jetbrains/jet/plugin/completion/handlers/JetFunctionInsertHandler.java @@ -4,6 +4,13 @@ import com.intellij.codeInsight.completion.InsertHandler; import com.intellij.codeInsight.completion.InsertionContext; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiDocumentManager; +import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; +import org.jetbrains.jet.lang.descriptors.DescriptorsUtils; +import org.jetbrains.jet.lang.descriptors.NamedFunctionDescriptor; +import org.jetbrains.jet.lang.psi.JetFile; +import org.jetbrains.jet.plugin.completion.JetLookupObject; +import org.jetbrains.jet.plugin.quickfix.ImportClassHelper; /** * Inserts '()' after function proposal insert. @@ -38,5 +45,25 @@ public class JetFunctionInsertHandler implements InsertHandler { } else { editor.getCaretModel().moveToOffset(editor.getCaretModel().getOffset() + 2); } + + PsiDocumentManager.getInstance(context.getProject()).commitDocument(context.getDocument()); + + // Should be done after all string insertions and document commitment. + addImport(context, item); + } + + private static void addImport(InsertionContext context, LookupElement item) { + if (context.getFile() instanceof JetFile && item.getObject() instanceof JetLookupObject) { + final DeclarationDescriptor descriptor = ((JetLookupObject) item.getObject()).getDescriptor(); + if (descriptor instanceof NamedFunctionDescriptor) { + + JetFile file = (JetFile) context.getFile(); + NamedFunctionDescriptor functionDescriptor = (NamedFunctionDescriptor) descriptor; + + if (DescriptorsUtils.isTopLevelFunction(functionDescriptor)) { + ImportClassHelper.addImportDirective(DescriptorsUtils.getFQName(functionDescriptor), file); + } + } + } } } diff --git a/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java index 23ad73ea40f..98b30a5375f 100644 --- a/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java +++ b/idea/src/org/jetbrains/jet/plugin/quickfix/ImportClassHelper.java @@ -2,15 +2,13 @@ package org.jetbrains.jet.plugin.quickfix; import com.intellij.psi.PsiElement; import org.jetbrains.annotations.NotNull; -import org.jetbrains.jet.lang.psi.JetDeclaration; -import org.jetbrains.jet.lang.psi.JetFile; -import org.jetbrains.jet.lang.psi.JetImportDirective; -import org.jetbrains.jet.lang.psi.JetPsiFactory; +import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.java.AnalyzerFacade; import org.jetbrains.jet.lang.types.ErrorUtils; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.plugin.JetPluginUtil; +import org.jetbrains.jet.util.QualifiedNamesUtil; import java.util.List; @@ -43,6 +41,12 @@ public class ImportClassHelper { * @param file File where directive should be added. */ public static void addImportDirective(@NotNull String importString, @NotNull JetFile file) { + + // Check that import is useless + if (JetPsiUtil.getFQName(file).equals(QualifiedNamesUtil.withoutLastSegment(importString))) { + return; + } + List importDirectives = file.getImportDirectives(); JetImportDirective newDirective = JetPsiFactory.createImportDirective(file.getProject(), importString); diff --git a/idea/testData/completion/handlers/NoParamsFunction.kt b/idea/testData/completion/handlers/NoParamsFunction.kt index 120b5e55d02..6d697aa0f7f 100644 --- a/idea/testData/completion/handlers/NoParamsFunction.kt +++ b/idea/testData/completion/handlers/NoParamsFunction.kt @@ -1,3 +1,5 @@ +package testing.handlers + fun test() { } diff --git a/idea/testData/completion/handlers/NoParamsFunction.kt.after b/idea/testData/completion/handlers/NoParamsFunction.kt.after index 920873c0b51..92da48be6da 100644 --- a/idea/testData/completion/handlers/NoParamsFunction.kt.after +++ b/idea/testData/completion/handlers/NoParamsFunction.kt.after @@ -1,3 +1,5 @@ +package testing.handlers + fun test() { } diff --git a/idea/testData/completion/handlers/multifile/TopLevelFunctionImport-1.kt b/idea/testData/completion/handlers/multifile/TopLevelFunctionImport-1.kt new file mode 100644 index 00000000000..df0b5b797d9 --- /dev/null +++ b/idea/testData/completion/handlers/multifile/TopLevelFunctionImport-1.kt @@ -0,0 +1,7 @@ +package some + +import java.util.SortedSet + +fun other() { + somefu +} diff --git a/idea/testData/completion/handlers/multifile/TopLevelFunctionImport-2.kt b/idea/testData/completion/handlers/multifile/TopLevelFunctionImport-2.kt new file mode 100644 index 00000000000..49ed7ab081d --- /dev/null +++ b/idea/testData/completion/handlers/multifile/TopLevelFunctionImport-2.kt @@ -0,0 +1,8 @@ +package jettesting.data + +class ClassFromJet { +} + +fun somefun(ii : T) : String { + return ii.toString() +} \ No newline at end of file diff --git a/idea/testData/completion/handlers/multifile/TopLevelFunctionImport.kt.after b/idea/testData/completion/handlers/multifile/TopLevelFunctionImport.kt.after new file mode 100644 index 00000000000..698776b1568 --- /dev/null +++ b/idea/testData/completion/handlers/multifile/TopLevelFunctionImport.kt.after @@ -0,0 +1,8 @@ +package some + +import java.util.SortedSet +import jettesting.data.somefun + +fun other() { + somefun() +} diff --git a/idea/tests/org/jetbrains/jet/completion/handlers/CompletionMultifileHandlerTest.java b/idea/tests/org/jetbrains/jet/completion/handlers/CompletionMultifileHandlerTest.java new file mode 100644 index 00000000000..e192c85ba5f --- /dev/null +++ b/idea/tests/org/jetbrains/jet/completion/handlers/CompletionMultifileHandlerTest.java @@ -0,0 +1,32 @@ +package org.jetbrains.jet.completion.handlers; + +import com.intellij.codeInsight.completion.CompletionTestCase; +import org.jetbrains.jet.plugin.PluginTestCaseBase; + +import java.io.File; + +/** + * @author Nikolay Krasko + */ +public class CompletionMultifileHandlerTest extends CompletionTestCase { + + public void testTopLevelFunctionImport() { + doTest(); + } + + public void doTest() { + String fileName = getTestName(false); + try { + configureByFiles(null, fileName + "-1.kt", fileName + "-2.kt"); + complete(2); + checkResultByFile(fileName + ".kt.after"); + } catch (Exception e) { + throw new AssertionError(e); + } + } + + @Override + protected String getTestDataPath() { + return new File(PluginTestCaseBase.getTestDataPathBase(), "/completion/handlers/multifile/").getPath() + File.separator; + } +}