/* * Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.goide.completion; import com.goide.project.GoVendoringUtil; import com.goide.psi.*; import com.goide.psi.impl.*; import com.intellij.codeInsight.completion.CompletionParameters; import com.intellij.codeInsight.completion.CompletionProvider; import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.psi.*; import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.ProcessingContext; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Set; import static com.goide.completion.GoCompletionUtil.createPrefixMatcher; public class GoReferenceCompletionProvider extends CompletionProvider<CompletionParameters> { @Override protected void addCompletions(@NotNull CompletionParameters parameters, ProcessingContext context, @NotNull CompletionResultSet set) { GoReferenceExpressionBase expression = PsiTreeUtil.getParentOfType(parameters.getPosition(), GoReferenceExpressionBase.class); PsiFile originalFile = parameters.getOriginalFile(); if (expression != null) { fillVariantsByReference(expression.getReference(), originalFile, set.withPrefixMatcher(createPrefixMatcher(set.getPrefixMatcher()))); } PsiElement parent = parameters.getPosition().getParent(); if (parent != null) { fillVariantsByReference(parent.getReference(), originalFile, set.withPrefixMatcher(createPrefixMatcher(set.getPrefixMatcher()))); } } private static void fillVariantsByReference(@Nullable PsiReference reference, @NotNull PsiFile file, @NotNull CompletionResultSet result) { if (reference == null) return; if (reference instanceof PsiMultiReference) { PsiReference[] references = ((PsiMultiReference)reference).getReferences(); ContainerUtil.sort(references, PsiMultiReference.COMPARATOR); fillVariantsByReference(ArrayUtil.getFirstElement(references), file, result); } else if (reference instanceof GoReference) { GoReferenceExpression refExpression = ObjectUtils.tryCast(reference.getElement(), GoReferenceExpression.class); GoStructLiteralCompletion.Variants variants = GoStructLiteralCompletion.allowedVariants(refExpression); fillStructFieldNameVariants(file, result, variants, refExpression); if (variants != GoStructLiteralCompletion.Variants.FIELD_NAME_ONLY) { ((GoReference)reference).processResolveVariants(new MyGoScopeProcessor(result, file, false)); } } else if (reference instanceof GoTypeReference) { PsiElement element = reference.getElement(); PsiElement spec = PsiTreeUtil.getParentOfType(element, GoFieldDeclaration.class, GoTypeSpec.class); boolean insideParameter = PsiTreeUtil.getParentOfType(element, GoParameterDeclaration.class) != null; ((GoTypeReference)reference).processResolveVariants(new MyGoScopeProcessor(result, file, true) { @Override protected boolean accept(@NotNull PsiElement e) { return e != spec && !(insideParameter && (e instanceof GoNamedSignatureOwner || e instanceof GoVarDefinition || e instanceof GoConstDefinition)); } }); } else if (reference instanceof GoCachedReference) { ((GoCachedReference)reference).processResolveVariants(new MyGoScopeProcessor(result, file, false)); } } private static void fillStructFieldNameVariants(@NotNull PsiFile file, @NotNull CompletionResultSet result, @NotNull GoStructLiteralCompletion.Variants variants, @Nullable GoReferenceExpression refExpression) { if (refExpression == null || variants != GoStructLiteralCompletion.Variants.FIELD_NAME_ONLY && variants != GoStructLiteralCompletion.Variants.BOTH) { return; } GoLiteralValue literal = PsiTreeUtil.getParentOfType(refExpression, GoLiteralValue.class); new GoFieldNameReference(refExpression).processResolveVariants(new MyGoScopeProcessor(result, file, false) { final Set<String> alreadyAssignedFields = GoStructLiteralCompletion.alreadyAssignedFields(literal); @Override public boolean execute(@NotNull PsiElement o, @NotNull ResolveState state) { String structFieldName = o instanceof GoFieldDefinition ? ((GoFieldDefinition)o).getName() : o instanceof GoAnonymousFieldDefinition ? ((GoAnonymousFieldDefinition)o).getName() : null; if (structFieldName != null && alreadyAssignedFields.contains(structFieldName)) { return true; } return super.execute(o, state); } }); } private static void addElement(@NotNull PsiElement o, @NotNull ResolveState state, boolean forTypes, boolean vendoringEnabled, @NotNull Set<String> processedNames, @NotNull CompletionResultSet set) { LookupElement lookup = createLookupElement(o, state, forTypes, vendoringEnabled); if (lookup != null) { String lookupString = lookup.getLookupString(); if (!processedNames.contains(lookupString)) { set.addElement(lookup); processedNames.add(lookupString); } } } @Nullable private static LookupElement createLookupElement(@NotNull PsiElement o, @NotNull ResolveState state, boolean forTypes, boolean vendoringEnabled) { if (o instanceof GoNamedElement && !((GoNamedElement)o).isBlank() || o instanceof GoImportSpec && !((GoImportSpec)o).isDot()) { if (o instanceof GoImportSpec) { return GoCompletionUtil.createPackageLookupElement((GoImportSpec)o, state.get(GoReferenceBase.ACTUAL_NAME), vendoringEnabled); } else if (o instanceof GoNamedSignatureOwner && ((GoNamedSignatureOwner)o).getName() != null) { String name = ((GoNamedSignatureOwner)o).getName(); if (name != null) { return GoCompletionUtil.createFunctionOrMethodLookupElement((GoNamedSignatureOwner)o, name, null, GoCompletionUtil.FUNCTION_PRIORITY); } } else if (o instanceof GoTypeSpec) { return forTypes ? GoCompletionUtil.createTypeLookupElement((GoTypeSpec)o) : GoCompletionUtil.createTypeConversionLookupElement((GoTypeSpec)o); } else if (o instanceof PsiDirectory) { return GoCompletionUtil.createPackageLookupElement(((PsiDirectory)o).getName(), (PsiDirectory)o, o, vendoringEnabled, true); } else if (o instanceof GoLabelDefinition) { String name = ((GoLabelDefinition)o).getName(); if (name != null) return GoCompletionUtil.createLabelLookupElement((GoLabelDefinition)o, name); } else if (o instanceof GoFieldDefinition) { return GoCompletionUtil.createFieldLookupElement((GoFieldDefinition)o); } else { return GoCompletionUtil.createVariableLikeLookupElement((GoNamedElement)o); } } return null; } private static class MyGoScopeProcessor extends GoScopeProcessor { @NotNull private final CompletionResultSet myResult; private final boolean myForTypes; private final boolean myVendoringEnabled; private final Set<String> myProcessedNames = ContainerUtil.newHashSet(); public MyGoScopeProcessor(@NotNull CompletionResultSet result, @NotNull PsiFile originalFile, boolean forTypes) { myResult = result; myForTypes = forTypes; myVendoringEnabled = GoVendoringUtil.isVendoringEnabled(ModuleUtilCore.findModuleForPsiElement(originalFile)); } @Override public boolean execute(@NotNull PsiElement o, @NotNull ResolveState state) { if (accept(o)) { addElement(o, state, myForTypes, myVendoringEnabled, myProcessedNames, myResult); } return true; } protected boolean accept(@NotNull PsiElement e) { return true; } @Override public boolean isCompletion() { return true; } } }