* Copyright 2016 The Bazel Authors. All rights reserved.
 * 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,
 * See the License for the specific language governing permissions and
 * limitations under the License.
package com.google.idea.blaze.base.lang.buildfile.documentation;

import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpec;
import com.google.idea.blaze.base.lang.buildfile.language.semantics.BuildLanguageSpecProvider;
import com.google.idea.blaze.base.lang.buildfile.language.semantics.RuleDefinition;
import com.google.idea.blaze.base.lang.buildfile.psi.BuildFile;
import com.google.idea.blaze.base.lang.buildfile.psi.DocStringOwner;
import com.google.idea.blaze.base.lang.buildfile.psi.FuncallExpression;
import com.google.idea.blaze.base.lang.buildfile.psi.FunctionStatement;
import com.google.idea.blaze.base.lang.buildfile.psi.ParameterList;
import com.google.idea.blaze.base.lang.buildfile.psi.StringLiteral;
import com.google.idea.blaze.base.model.primitives.Label;
import com.google.idea.blaze.base.settings.Blaze;
import com.intellij.codeInsight.documentation.DocumentationManagerProtocol;
import com.intellij.lang.documentation.AbstractDocumentationProvider;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import javax.annotation.Nullable;

/** Provides quick docs for some BUILD elements. */
public class BuildDocumentationProvider extends AbstractDocumentationProvider {

  private static final String LINK_TYPE_FILE = "#file#";

  public String generateDoc(PsiElement element, @Nullable PsiElement originalElement) {
    if (element instanceof DocStringOwner) {
      return buildDocs((DocStringOwner) element);
    if (element instanceof FuncallExpression) {
      return docsForBuiltInRule(
          element.getProject(), ((FuncallExpression) element).getFunctionName());
    return null;

  /** Returns the corresponding built-in rule in the BUILD file language, if one exists. */
  private static RuleDefinition getBuiltInRule(Project project, @Nullable String ruleName) {
    BuildLanguageSpec spec = BuildLanguageSpecProvider.getInstance().getLanguageSpec(project);
    return spec != null ? spec.getRule(ruleName) : null;

  private static String docsForBuiltInRule(Project project, @Nullable String ruleName) {
    RuleDefinition rule = getBuiltInRule(project, ruleName);
    if (rule == null) {
      return null;
    String link = Blaze.getBuildSystemProvider(project).getRuleDocumentationUrl(rule);
    if (link == null) {
      return null;
    return String.format(
        "External documentation for %s:<br><a href=\"%s\">%s</a>", rule.getName(), link, link);

  private static void describeFile(PsiFile file, StringBuilder builder, boolean linkToFile) {
    if (!(file instanceof BuildFile)) {
    BuildFile buildFile = (BuildFile) file;
    Label label = buildFile.getBuildLabel();
    String name = label != null ? label.toString() : buildFile.getPresentableText();
    if (linkToFile) {
          .append("<a href=\"")
    } else {
      builder.append(String.format("<b>%s</b>", name));

  private static String buildDocs(DocStringOwner element) {
    StringBuilder docs = new StringBuilder();
    describeFile(element.getContainingFile(), docs, !(element instanceof BuildFile));

    if (element instanceof FunctionStatement) {
      describeFunction((FunctionStatement) element, docs);
    StringLiteral docString = element.getDocString();
    if (docString != null) {
      docs.append(DocStringFormatter.formatDocString(docString, element));
    return wrapDocInHtml(docs.toString());

  private static void describeFunction(FunctionStatement function, StringBuilder builder) {
    // just show the function declaration verbatim, including the parameter list.
    ParameterList paramList = function.getParameterList();
    if (paramList == null) {
        .append("def ")

  private static String wrapDocInHtml(String doc) {
    return "<html><body><code>" + doc + "</code></body></html>";

  public PsiElement getDocumentationElementForLink(
      PsiManager psiManager, String link, PsiElement context) {
    if (link.equals(LINK_TYPE_FILE)) {
      return context.getContainingFile();
    return null;