/* * Copyright 2013-2017 consulo.io * * 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 consulo.csharp.ide.codeInsight.actions; import java.util.Collection; import java.util.Collections; import java.util.Set; import org.jetbrains.annotations.NotNull; import consulo.csharp.lang.psi.CSharpAttribute; import consulo.csharp.lang.psi.CSharpCallArgument; import consulo.csharp.lang.psi.CSharpMethodDeclaration; import consulo.csharp.lang.psi.CSharpNewExpression; import consulo.csharp.lang.psi.CSharpReferenceExpression; import consulo.csharp.lang.psi.CSharpUserType; import consulo.csharp.lang.psi.CSharpUsingListChild; import consulo.csharp.lang.psi.impl.light.CSharpLightCallArgument; import consulo.csharp.lang.psi.impl.source.CSharpMethodCallExpressionImpl; import consulo.csharp.lang.psi.impl.source.resolve.methodResolving.MethodResolver; import consulo.csharp.lang.psi.impl.stub.index.ExtensionMethodIndex; import consulo.csharp.lang.psi.impl.stub.index.MethodIndex; import consulo.csharp.lang.psi.resolve.AttributeByNameSelector; import consulo.dotnet.libraryAnalyzer.DotNetLibraryAnalyzerComponent; import consulo.dotnet.libraryAnalyzer.NamespaceReference; import com.intellij.codeInsight.daemon.impl.ShowAutoImportPass; import com.intellij.codeInsight.hint.HintManager; import com.intellij.codeInsight.intention.HighPriorityAction; import com.intellij.codeInspection.HintAction; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.SmartPointerManager; import com.intellij.psi.SmartPsiElementPointer; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.CommonProcessors; import com.intellij.util.IncorrectOperationException; import com.intellij.util.containers.ArrayListSet; import consulo.annotations.RequiredReadAction; import consulo.dotnet.DotNetBundle; import consulo.dotnet.psi.DotNetExpression; import consulo.dotnet.psi.DotNetInheritUtil; import consulo.dotnet.psi.DotNetLikeMethodDeclaration; import consulo.dotnet.psi.DotNetNamespaceDeclaration; import consulo.dotnet.psi.DotNetQualifiedElement; import consulo.dotnet.psi.DotNetTypeDeclaration; import consulo.dotnet.resolve.DotNetShortNameSearcher; import consulo.dotnet.resolve.GlobalSearchScopeFilter; /** * @author VISTALL * @since 30.12.13. */ public class UsingNamespaceFix implements HintAction, HighPriorityAction { enum PopupResult { NOT_AVAILABLE, SHOW_HIT, SHOW_ACTION } private final SmartPsiElementPointer<CSharpReferenceExpression> myRefPointer; public UsingNamespaceFix(@NotNull CSharpReferenceExpression ref) { myRefPointer = SmartPointerManager.getInstance(ref.getProject()).createSmartPsiElementPointer(ref); } @NotNull @RequiredReadAction public PopupResult doFix(Editor editor) { CSharpReferenceExpression element = myRefPointer.getElement(); if(element == null) { return PopupResult.NOT_AVAILABLE; } CSharpReferenceExpression.ResolveToKind kind = element.kind(); if(!isValidReference(kind, element)) { return PopupResult.NOT_AVAILABLE; } Set<NamespaceReference> references = collectAllAvailableNamespaces(element, kind); if(references.isEmpty()) { return PopupResult.NOT_AVAILABLE; } AddUsingAction action = new AddUsingAction(editor, element, references); String message = ShowAutoImportPass.getMessage(references.size() != 1, DotNetBundle.message("use.popup", AddUsingAction.formatMessage(references.iterator().next()))); PsiElement referenceElement = element.getReferenceElement(); assert referenceElement != null; TextRange referenceTextRange = referenceElement.getTextRange(); HintManager.getInstance().showQuestionHint(editor, message, referenceTextRange.getStartOffset(), referenceTextRange.getEndOffset(), action); return PopupResult.SHOW_HIT; } @RequiredReadAction public static boolean isValidReference(CSharpReferenceExpression.ResolveToKind kind, CSharpReferenceExpression expression) { PsiElement resolvedElement = expression.resolve(); if(resolvedElement != null) { return false; } switch(kind) { case TYPE_LIKE: case ANY_MEMBER: if(expression.getQualifier() != null) { return false; } return true; case CONSTRUCTOR: if(expression.getQualifier() != null) { return false; } PsiElement parent = expression.getParent(); if(parent instanceof CSharpAttribute) { return true; } else if(parent instanceof CSharpUserType) { if(!(parent.getParent() instanceof CSharpNewExpression)) { return false; } else { return true; } } return false; case METHOD: return expression.getQualifier() != null; } return false; } @NotNull @RequiredReadAction private static Set<NamespaceReference> collectAllAvailableNamespaces(CSharpReferenceExpression ref, CSharpReferenceExpression.ResolveToKind kind) { if(PsiTreeUtil.getParentOfType(ref, CSharpUsingListChild.class) != null || !ref.isValid()) { return Collections.emptySet(); } String referenceName = ref.getReferenceName(); if(StringUtil.isEmpty(referenceName)) { return Collections.emptySet(); } Set<NamespaceReference> resultSet = new ArrayListSet<NamespaceReference>(); if(kind == CSharpReferenceExpression.ResolveToKind.TYPE_LIKE || kind == CSharpReferenceExpression.ResolveToKind.CONSTRUCTOR || ref.getQualifier() == null) { collectAvailableNamespaces(ref, resultSet, referenceName); } if(kind == CSharpReferenceExpression.ResolveToKind.METHOD) { collectAvailableNamespacesForMethodExtensions(ref, resultSet, referenceName); } Module moduleForPsiElement = ModuleUtilCore.findModuleForPsiElement(ref); if(moduleForPsiElement != null) { resultSet.addAll(DotNetLibraryAnalyzerComponent.getInstance(moduleForPsiElement.getProject()).get(moduleForPsiElement, referenceName)); } return resultSet; } private static void collectAvailableNamespaces(final CSharpReferenceExpression ref, Set<NamespaceReference> set, String referenceName) { if(ref.getQualifier() != null) { return; } Collection<DotNetTypeDeclaration> tempTypes; Collection<DotNetLikeMethodDeclaration> tempMethods; PsiElement parent = ref.getParent(); if(parent instanceof CSharpAttribute) { final Condition<DotNetTypeDeclaration> cond = new Condition<DotNetTypeDeclaration>() { @Override public boolean value(DotNetTypeDeclaration typeDeclaration) { return DotNetInheritUtil.isAttribute(typeDeclaration); } }; tempTypes = getTypesWithGeneric(ref, referenceName); collect(set, tempTypes, cond); tempTypes = getTypesWithGeneric(ref, referenceName + AttributeByNameSelector.AttributeSuffix); collect(set, tempTypes, cond); } else { tempTypes = getTypesWithGeneric(ref, referenceName); collect(set, tempTypes, Conditions.<DotNetTypeDeclaration>alwaysTrue()); tempMethods = MethodIndex.getInstance().get(referenceName, ref.getProject(), ref.getResolveScope()); collect(set, tempMethods, new Condition<DotNetLikeMethodDeclaration>() { @Override public boolean value(DotNetLikeMethodDeclaration method) { return (method.getParent() instanceof DotNetNamespaceDeclaration || method.getParent() instanceof PsiFile) && method instanceof CSharpMethodDeclaration && ((CSharpMethodDeclaration) method).isDelegate(); } }); } } private static Collection<DotNetTypeDeclaration> getTypesWithGeneric(CSharpReferenceExpression ref, final String refName) { CommonProcessors.CollectProcessor<DotNetTypeDeclaration> processor = new CommonProcessors.CollectProcessor<DotNetTypeDeclaration>(); GlobalSearchScopeFilter filter = new GlobalSearchScopeFilter(ref.getResolveScope()); DotNetShortNameSearcher.getInstance(ref.getProject()).collectTypes(refName, ref.getResolveScope(), filter, processor); return processor.getResults(); } @RequiredReadAction private static void collectAvailableNamespacesForMethodExtensions(CSharpReferenceExpression ref, Set<NamespaceReference> set, String referenceName) { PsiElement qualifier = ref.getQualifier(); if(qualifier == null) { return; } PsiElement parent = ref.getParent(); if(!(parent instanceof CSharpMethodCallExpressionImpl)) { return; } CSharpCallArgument[] callArguments = ((CSharpMethodCallExpressionImpl) parent).getCallArguments(); CSharpCallArgument[] newCallArguments = new CSharpCallArgument[callArguments.length + 1]; newCallArguments[0] = new CSharpLightCallArgument((DotNetExpression) qualifier); System.arraycopy(callArguments, 0, newCallArguments, 1, callArguments.length); Collection<DotNetLikeMethodDeclaration> list = ExtensionMethodIndex.getInstance().get(referenceName, ref.getProject(), ref.getResolveScope()); for(DotNetLikeMethodDeclaration possibleMethod : list) { if(MethodResolver.calc(newCallArguments, possibleMethod, ref).isValidResult()) { PsiElement parentOfMethod = possibleMethod.getParent(); if(parentOfMethod instanceof DotNetQualifiedElement) { set.add(new NamespaceReference(((DotNetQualifiedElement) parentOfMethod).getPresentableParentQName(), null)); } } } } private static <T extends DotNetQualifiedElement> void collect(Set<NamespaceReference> result, Collection<T> element, Condition<T> condition) { for(T type : element) { String presentableParentQName = type.getPresentableParentQName(); if(StringUtil.isEmpty(presentableParentQName)) { continue; } if(!condition.value(type)) { continue; } result.add(new NamespaceReference(presentableParentQName, null)); } } @Override public boolean showHint(@NotNull Editor editor) { return doFix(editor) == PopupResult.SHOW_HIT; } @NotNull @Override public String getText() { return DotNetBundle.message("add.using"); } @NotNull @Override public String getFamilyName() { return "Import"; } @Override public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile psiFile) { CSharpReferenceExpression element = myRefPointer.getElement(); if(element == null) { return false; } CSharpReferenceExpression.ResolveToKind kind = element.kind(); return !collectAllAvailableNamespaces(element, kind).isEmpty(); } @Override public void invoke(@NotNull Project project, Editor editor, PsiFile psiFile) throws IncorrectOperationException { } @Override public boolean startInWriteAction() { return true; } }