package com.jetbrains.lang.dart.ide.documentation; import com.intellij.lang.documentation.DocumentationProvider; import com.intellij.navigation.NavigationItem; 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.PsiReference; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.lang.dart.analyzer.DartAnalysisServerService; import com.jetbrains.lang.dart.psi.*; import com.jetbrains.lang.dart.util.DartResolveUtil; import com.jetbrains.lang.dart.util.DartUrlResolver; import org.dartlang.analysis.server.protocol.HoverInformation; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collections; import java.util.List; public class DartDocumentationProvider implements DocumentationProvider { private static final String BASE_DART_DOC_URL = "https://api.dartlang.org/stable/"; @Override public String generateDoc(@NotNull final PsiElement element, @Nullable final PsiElement originalElement) { // in case of code completion 'element' comes from completion list and has nothing to do with 'originalElement', // but for Quick Doc in editor we should prefer building docs for 'originalElement' because such doc has info about propagated type final PsiElement elementForDocs = resolvesTo(originalElement, element) ? originalElement : element; final HoverInformation hover = getSingleHover(elementForDocs); if (hover != null) { return generateDocServer(hover); } return DartDocUtil.generateDoc(element); } private static boolean resolvesTo(@Nullable final PsiElement originalElement, @NotNull final PsiElement target) { final PsiReference reference; if (originalElement instanceof PsiReference) { reference = (PsiReference)originalElement; } else { final PsiElement parent = originalElement == null ? null : originalElement.getParent(); final PsiElement parentParent = parent instanceof DartId ? parent.getParent() : null; if (parentParent == null) return false; if (parentParent == target) return true; if (!parentParent.getText().equals(target.getText())) return false; reference = parentParent.getReference(); } return reference != null && reference.resolve() == target; } @Override public PsiElement getDocumentationElementForLink(PsiManager psiManager, String link, PsiElement context) { return null; } @Override public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) { return null; } @Override public String getQuickNavigateInfo(final PsiElement element, final PsiElement originalElement) { final PsiElement elementForInfo = resolvesTo(originalElement, element) ? originalElement : element; final HoverInformation hover = getSingleHover(elementForInfo); if (hover != null) { return buildHoverTextServer(hover); } return DartDocUtil.getSignature(element); } @Override @Nullable public List<String> getUrlFor(PsiElement element, PsiElement originalElement) { if (!(element instanceof DartComponent) && !(element.getParent() instanceof DartComponent)) { return null; } final DartComponent component = (DartComponent)(element instanceof DartComponent ? element : element.getParent()); if (!component.isPublic()) return null; final String docUrl = constructDocUrl(component); return docUrl == null ? null : Collections.singletonList(docUrl); } @NotNull public static String buildHoverTextServer(@NotNull final HoverInformation hover) { final String elementDescription = hover.getElementDescription(); final String staticType = elementDescription == null || elementDescription.equals(hover.getStaticType()) ? null : hover.getStaticType(); final String propagatedType = elementDescription == null || elementDescription.equals(hover.getPropagatedType()) ? null : hover.getPropagatedType(); return DartDocUtil.generateDoc(elementDescription, false, null, null, null, staticType, propagatedType, true); } @NotNull public static String generateDocServer(@NotNull final HoverInformation hover) { final String elementDescription = hover.getElementDescription(); final String containingLibraryName = hover.getContainingLibraryName(); final String containingClassDescription = hover.getContainingClassDescription(); final String staticType = hover.getStaticType(); final String propagatedType = hover.getPropagatedType(); final String docText = hover.getDartdoc(); return DartDocUtil.generateDoc(elementDescription, false, docText, containingLibraryName, containingClassDescription, staticType, propagatedType, false); } @Nullable public static HoverInformation getSingleHover(@NotNull final PsiFile psiFile, final int offset) { final List<HoverInformation> hoverList = DartAnalysisServerService.getInstance(psiFile.getProject()).analysis_getHover(psiFile.getVirtualFile(), offset); if (hoverList.isEmpty()) { return null; } return hoverList.get(0); } @Nullable private static String constructDocUrl(@NotNull final DartComponent component) { // class: https://api.dartlang.org/stable/dart-web_audio/AnalyserNode-class.html // constructor: https://api.dartlang.org/stable/dart-core/DateTime/DateTime.fromMicrosecondsSinceEpoch.html // https://api.dartlang.org/stable/dart-core/List/List.html // method: https://api.dartlang.org/stable/dart-core/Object/toString.html // property: https://api.dartlang.org/stable/dart-core/List/length.html // function: https://api.dartlang.org/stable/dart-math/cos.html final String libRelatedUrlPart = getLibRelatedUrlPart(component); final String name = component.getName(); if (libRelatedUrlPart == null || name == null) return null; final String baseUrl = BASE_DART_DOC_URL + libRelatedUrlPart + "/"; if (component instanceof DartClass) { return baseUrl + name + "-class.html"; } final DartClass dartClass = PsiTreeUtil.getParentOfType(component, DartClass.class, true); if (component instanceof DartNamedConstructorDeclaration) { assert dartClass != null; return baseUrl + dartClass.getName() + "/" + StringUtil.join(((DartNamedConstructorDeclaration)component).getComponentNameList(), NavigationItem::getName, ".") + ".html"; } if (component instanceof DartFactoryConstructorDeclaration) { assert dartClass != null; return baseUrl + dartClass.getName() + "/" + StringUtil.join(((DartFactoryConstructorDeclaration)component).getComponentNameList(), NavigationItem::getName, ".") + ".html"; } if (dartClass != null) { // method, property return baseUrl + dartClass.getName() + "/" + name + ".html"; } else { // library-level function return baseUrl + name + ".html"; } } @Nullable private static String getLibRelatedUrlPart(@NotNull final PsiElement element) { for (VirtualFile libFile : DartResolveUtil.findLibrary(element.getContainingFile())) { final DartUrlResolver urlResolver = DartUrlResolver.getInstance(element.getProject(), libFile); final String dartUrl = urlResolver.getDartUrlForFile(libFile); // "dart:html" -> "dart-html" if (dartUrl.startsWith(DartUrlResolver.DART_PREFIX)) { return "dart-" + dartUrl.substring(DartUrlResolver.DART_PREFIX.length()); } } return null; } @Nullable private static HoverInformation getSingleHover(final PsiElement element) { if (element != null) { final PsiFile psiFile = element.getContainingFile(); final int offset = element.getTextOffset(); return getSingleHover(psiFile, offset); } return null; } }