/* * 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.documentation; import java.util.List; import org.emonic.base.codehierarchy.CodeHierarchyHelper; import org.emonic.base.documentation.IDocumentation; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.annotations.RequiredReadAction; import consulo.csharp.ide.parameterInfo.CSharpParametersInfo; import consulo.csharp.lang.psi.CSharpEventDeclaration; import consulo.csharp.lang.psi.CSharpIndexMethodDeclaration; import consulo.csharp.lang.psi.CSharpMethodDeclaration; import consulo.csharp.lang.psi.CSharpTypeDefStatement; import consulo.csharp.lang.psi.CSharpTypeRefPresentationUtil; import consulo.csharp.lang.psi.impl.source.resolve.type.CSharpStaticTypeRef; import consulo.dotnet.documentation.DotNetDocumentationCache; import consulo.dotnet.psi.*; import consulo.dotnet.resolve.DotNetArrayTypeRef; import consulo.dotnet.resolve.DotNetGenericExtractor; import consulo.dotnet.resolve.DotNetPointerTypeRef; import consulo.dotnet.resolve.DotNetPsiSearcher; import consulo.dotnet.resolve.DotNetTypeRef; import consulo.dotnet.resolve.DotNetTypeResolveResult; import com.intellij.lang.documentation.DocumentationProvider; import com.intellij.openapi.module.Module; import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.roots.OrderEntry; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.PsiNamedElement; import com.intellij.util.Function; import com.intellij.xml.util.XmlStringUtil; /** * @author VISTALL * @since 15.12.14 */ public class CSharpDocumentationProvider implements DocumentationProvider { private static final String TYPE_PREFIX = "csharp_type::"; @Nullable @Override @RequiredReadAction public String getQuickNavigateInfo(PsiElement element, PsiElement element2) { if(element instanceof DotNetTypeDeclaration) { return generateQuickTypeDeclarationInfo((DotNetTypeDeclaration) element); } else if(element instanceof DotNetVariable) { return generateQuickVariableInfo((DotNetVariable) element); } else if(element instanceof DotNetLikeMethodDeclaration) { return generateQuickLikeMethodDeclarationInfo((DotNetLikeMethodDeclaration) element); } else if(element instanceof CSharpTypeDefStatement) { return generateQuickTypeAliasInfo((CSharpTypeDefStatement) element); } return null; } @RequiredReadAction private static String generateQuickTypeAliasInfo(CSharpTypeDefStatement typeDefStatement) { StringBuilder builder = new StringBuilder(); builder.append("type alias "); builder.append(typeDefStatement.getName()); DotNetTypeRef typeRef = typeDefStatement.toTypeRef(); if(typeRef != DotNetTypeRef.ERROR_TYPE) { builder.append(" = "); builder.append(generateLinksForType(typeRef, typeDefStatement, true)); } return builder.toString(); } @RequiredReadAction private static String generateQuickLikeMethodDeclarationInfo(DotNetLikeMethodDeclaration element) { StringBuilder builder = new StringBuilder(); appendModifiers(element, builder); if(element instanceof CSharpMethodDeclaration && ((CSharpMethodDeclaration) element).isDelegate()) { builder.append("delegate "); } if(element instanceof DotNetConstructorDeclaration) { if(((DotNetConstructorDeclaration) element).isDeConstructor()) { builder.append("~"); } builder.append(element.getName()); } else { builder.append(generateLinksForType(element.getReturnTypeRef(), element, false)); builder.append(" "); if(element instanceof CSharpIndexMethodDeclaration) { builder.append("this"); } else { builder.append(XmlStringUtil.escapeString(element.getName())); } } char[] openAndCloseTokens = CSharpParametersInfo.getOpenAndCloseTokens(element); builder.append(openAndCloseTokens[0]); builder.append(StringUtil.join(element.getParameters(), new Function<DotNetParameter, String>() { @Override @RequiredReadAction public String fun(DotNetParameter dotNetParameter) { DotNetTypeRef typeRef = dotNetParameter.toTypeRef(true); if(typeRef == CSharpStaticTypeRef.__ARGLIST_TYPE) { return typeRef.toString(); } return generateLinksForType(typeRef, dotNetParameter, false) + " " + dotNetParameter.getName(); } }, ", ")); builder.append(openAndCloseTokens[1]); return builder.toString(); } @RequiredReadAction private static String generateQuickVariableInfo(DotNetVariable element) { StringBuilder builder = new StringBuilder(); appendModifiers(element, builder); if(element instanceof CSharpEventDeclaration) { builder.append("event "); } builder.append(generateLinksForType(element.toTypeRef(true), element, false)); builder.append(" "); builder.append(element.getName()); DotNetExpression initializer = element.getInitializer(); if(initializer != null) { builder.append(" = "); builder.append(initializer.getText()); } builder.append(";"); return builder.toString(); } @RequiredReadAction private static String generateQuickTypeDeclarationInfo(DotNetTypeDeclaration element) { StringBuilder builder = new StringBuilder(); PsiFile containingFile = element.getContainingFile(); final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(element.getProject()).getFileIndex(); VirtualFile vFile = containingFile == null ? null : containingFile.getVirtualFile(); if(vFile != null && (fileIndex.isInLibrarySource(vFile) || fileIndex.isInLibraryClasses(vFile))) { final List<OrderEntry> orderEntries = fileIndex.getOrderEntriesForFile(vFile); if(orderEntries.size() > 0) { final OrderEntry orderEntry = orderEntries.get(0); builder.append("[").append(StringUtil.escapeXml(orderEntry.getPresentableName())).append("] "); } } else { final Module module = containingFile == null ? null : ModuleUtil.findModuleForPsiElement(containingFile); if(module != null) { builder.append('[').append(module.getName()).append("] "); } } String presentableParentQName = element.getPresentableParentQName(); if(!StringUtil.isEmpty(presentableParentQName)) { builder.append(presentableParentQName); } if(builder.length() > 0) { builder.append("<br>"); } appendModifiers(element, builder); appendTypeDeclarationType(element, builder); builder.append(" ").append(element.getName()); return builder.toString(); } @Nullable @Override public List<String> getUrlFor(PsiElement element, PsiElement element2) { return null; } @Nullable @Override @RequiredReadAction public String generateDoc(PsiElement element, @Nullable PsiElement element2) { if(element instanceof CSharpTypeDefStatement) { PsiElement resolvedElement = ((CSharpTypeDefStatement) element).toTypeRef().resolve().getElement(); if(resolvedElement != null) { element = resolvedElement; } } IDocumentation documentation = DotNetDocumentationCache.getInstance().findDocumentation(element); if(documentation == null) { return null; } return CodeHierarchyHelper.getFormText(documentation); } private static void appendTypeDeclarationType(DotNetTypeDeclaration psiElement, StringBuilder builder) { if(psiElement.isInterface()) { builder.append("interface"); } else if(psiElement.isEnum()) { builder.append("enum"); } else if(psiElement.isStruct()) { builder.append("struct"); } else { builder.append("class"); } } @RequiredReadAction private static void appendModifiers(DotNetModifierListOwner owner, StringBuilder builder) { DotNetModifierList modifierList = owner.getModifierList(); if(modifierList == null) { return; } for(DotNetModifier modifier : modifierList.getModifiers()) { builder.append(modifier.getPresentableText()).append(" "); } } @Nullable @Override public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object o, PsiElement element) { return null; } @Nullable @Override @RequiredReadAction public PsiElement getDocumentationElementForLink(PsiManager psiManager, String s, PsiElement element) { if(s.startsWith(TYPE_PREFIX)) { String qName = s.substring(TYPE_PREFIX.length(), s.length()); return DotNetPsiSearcher.getInstance(element.getProject()).findType(qName, element.getResolveScope()); } return null; } @RequiredReadAction private static String generateLinksForType(DotNetTypeRef typeRef, PsiElement element, boolean qualified) { StringBuilder builder = new StringBuilder(); if(typeRef == DotNetTypeRef.AUTO_TYPE) { builder.append("var"); } else if(typeRef instanceof DotNetArrayTypeRef) { builder.append(generateLinksForType(((DotNetArrayTypeRef) typeRef).getInnerTypeRef(), element, qualified)); builder.append("[]"); } else if(typeRef instanceof DotNetPointerTypeRef) { builder.append(generateLinksForType(((DotNetPointerTypeRef) typeRef).getInnerTypeRef(), element, qualified)); builder.append("*"); } else { DotNetTypeResolveResult dotNetTypeResolveResult = typeRef.resolve(); PsiElement resolved = dotNetTypeResolveResult.getElement(); if(resolved instanceof DotNetQualifiedElement) { if(resolved instanceof DotNetGenericParameterListOwner) { wrapToLink(resolved, builder, qualified); DotNetGenericParameter[] genericParameters = ((DotNetGenericParameterListOwner) resolved).getGenericParameters(); if(genericParameters.length > 0) { DotNetGenericExtractor genericExtractor = dotNetTypeResolveResult.getGenericExtractor(); builder.append(XmlStringUtil.escapeString("<")); for(int i = 0; i < genericParameters.length; i++) { if(i != 0) { builder.append(", "); } DotNetGenericParameter parameter = genericParameters[i]; DotNetTypeRef extractedTypeRef = genericExtractor.extract(parameter); if(extractedTypeRef != null) { builder.append(generateLinksForType(extractedTypeRef, element, qualified)); } else { builder.append(parameter.getName()); } } builder.append(XmlStringUtil.escapeString(">")); } } else { builder.append(((DotNetQualifiedElement) resolved).getName()); } } else { builder.append(CSharpTypeRefPresentationUtil.buildShortText(typeRef, element)); } } return builder.toString(); } @RequiredReadAction private static void wrapToLink(@NotNull PsiElement resolved, StringBuilder builder, boolean qualified) { String parentQName = qualified ? resolved instanceof DotNetQualifiedElement ? ((DotNetQualifiedElement) resolved).getPresentableParentQName() : null : null; if(!StringUtil.isEmpty(parentQName)) { builder.append(parentQName).append('.'); } builder.append("<a href=\"psi_element://").append(TYPE_PREFIX); if(resolved instanceof DotNetTypeDeclaration) { builder.append(((DotNetTypeDeclaration) resolved).getVmQName()); } else if(resolved instanceof DotNetQualifiedElement) { builder.append(((DotNetQualifiedElement) resolved).getPresentableQName()); } else { builder.append(((PsiNamedElement) resolved).getName()); } builder.append("\">").append(((PsiNamedElement) resolved).getName()).append("</a>"); } }