Merge "Switch from pegdown to flexmark-java"
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 5c008c7..6517262 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2534,7 +2534,7 @@
 attribute.
 
 Documentation may be written in the Markdown flavor
-link:https://github.com/sirthias/pegdown[pegdown]
+link:https://github.com/vsch/flexmark-java[flexmark-java]
 if the file name ends with `.md`. Gerrit will automatically convert
 Markdown to HTML if accessed with extension `.html`.
 
diff --git a/WORKSPACE b/WORKSPACE
index e4d62e8..3e0fdbc 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -351,22 +351,163 @@
     sha1 = "959a0c62f9a5c2309e0ad0b0589c74d69e101241",
 )
 
+FLEXMARK_VERS = "0.32.4"
+
 maven_jar(
-    name = "pegdown",
-    artifact = "org.pegdown:pegdown:1.6.0",
-    sha1 = "231ae49d913467deb2027d0b8a0b68b231deef4f",
+    name = "flexmark",
+    artifact = "com.vladsch.flexmark:flexmark:" + FLEXMARK_VERS,
+    sha1 = "61323e756ebaf1936d143afdd70277251206005e",
 )
 
 maven_jar(
-    name = "grappa",
-    artifact = "com.github.parboiled1:grappa:1.0.4",
-    sha1 = "ad4b44b9c305dad7aa1e680d4b5c8eec9c4fd6f5",
+    name = "flexmark-ext-abbreviation",
+    artifact = "com.vladsch.flexmark:flexmark-ext-abbreviation:" + FLEXMARK_VERS,
+    sha1 = "726e5978edc68403286a88f9366f572af6a76506",
 )
 
 maven_jar(
-    name = "jitescript",
-    artifact = "me.qmx.jitescript:jitescript:0.4.0",
-    sha1 = "2e35862b0435c1b027a21f3d6eecbe50e6e08d54",
+    name = "flexmark-ext-anchorlink",
+    artifact = "com.vladsch.flexmark:flexmark-ext-anchorlink:" + FLEXMARK_VERS,
+    sha1 = "ec3bcf718dae98eec2829e351eda59d63eeac94b",
+)
+
+maven_jar(
+    name = "flexmark-ext-autolink",
+    artifact = "com.vladsch.flexmark:flexmark-ext-autolink:" + FLEXMARK_VERS,
+    sha1 = "2103b77836f266775d20e5fe2a6ba51d5c8b63c8",
+)
+
+maven_jar(
+    name = "flexmark-ext-definition",
+    artifact = "com.vladsch.flexmark:flexmark-ext-definition:" + FLEXMARK_VERS,
+    sha1 = "c896f2291ad2b9b253ee2949865598a1e9454c55",
+)
+
+maven_jar(
+    name = "flexmark-ext-emoji",
+    artifact = "com.vladsch.flexmark:flexmark-ext-emoji:" + FLEXMARK_VERS,
+    sha1 = "f167c3073b7bb5ced944c4837ae655b2e5ea7a1c",
+)
+
+maven_jar(
+    name = "flexmark-ext-escaped-character",
+    artifact = "com.vladsch.flexmark:flexmark-ext-escaped-character:" + FLEXMARK_VERS,
+    sha1 = "23004d741824f0c564de2c72e579ca67b01a4623",
+)
+
+maven_jar(
+    name = "flexmark-ext-footnotes",
+    artifact = "com.vladsch.flexmark:flexmark-ext-footnotes:" + FLEXMARK_VERS,
+    sha1 = "c9b98778b94fee2efd886e47317944f7fcb8a037",
+)
+
+maven_jar(
+    name = "flexmark-ext-gfm-issues",
+    artifact = "com.vladsch.flexmark:flexmark-ext-gfm-issues:" + FLEXMARK_VERS,
+    sha1 = "0d543c46a6025c1b8342f0e99155208276c86966",
+)
+
+maven_jar(
+    name = "flexmark-ext-gfm-strikethrough",
+    artifact = "com.vladsch.flexmark:flexmark-ext-gfm-strikethrough:" + FLEXMARK_VERS,
+    sha1 = "95414bb07c8429389374b66dd42a9da2e17c693b",
+)
+
+maven_jar(
+    name = "flexmark-ext-gfm-tables",
+    artifact = "com.vladsch.flexmark:flexmark-ext-gfm-tables:" + FLEXMARK_VERS,
+    sha1 = "1cbc2bc4de374b75d9dff812054d51aa84a61d5d",
+)
+
+maven_jar(
+    name = "flexmark-ext-gfm-tasklist",
+    artifact = "com.vladsch.flexmark:flexmark-ext-gfm-tasklist:" + FLEXMARK_VERS,
+    sha1 = "c93ed560ff2047a4c0cdbfe563a3873574404477",
+)
+
+maven_jar(
+    name = "flexmark-ext-gfm-users",
+    artifact = "com.vladsch.flexmark:flexmark-ext-gfm-users:" + FLEXMARK_VERS,
+    sha1 = "c58c74ba4a51c2376760f2c011a6bbf21c39455d",
+)
+
+maven_jar(
+    name = "flexmark-ext-ins",
+    artifact = "com.vladsch.flexmark:flexmark-ext-ins:" + FLEXMARK_VERS,
+    sha1 = "76d712a244df71f875f66244407d3c51596712ae",
+)
+
+maven_jar(
+    name = "flexmark-ext-jekyll-front-matter",
+    artifact = "com.vladsch.flexmark:flexmark-ext-jekyll-front-matter:" + FLEXMARK_VERS,
+    sha1 = "8603af459c0215c58f0a6debd903201941f7d9e9",
+)
+
+maven_jar(
+    name = "flexmark-ext-superscript",
+    artifact = "com.vladsch.flexmark:flexmark-ext-superscript:" + FLEXMARK_VERS,
+    sha1 = "9ad131c9f6bec18e357c9238e78211d88a4474b9",
+)
+
+maven_jar(
+    name = "flexmark-ext-tables",
+    artifact = "com.vladsch.flexmark:flexmark-ext-tables:" + FLEXMARK_VERS,
+    sha1 = "4410446d9e77761331e474f7b920d975342cd9a1",
+)
+
+maven_jar(
+    name = "flexmark-ext-toc",
+    artifact = "com.vladsch.flexmark:flexmark-ext-toc:" + FLEXMARK_VERS,
+    sha1 = "3ad7c7ec4f7a5cce181a710ba5f689eeec8a3e52",
+)
+
+maven_jar(
+    name = "flexmark-ext-typographic",
+    artifact = "com.vladsch.flexmark:flexmark-ext-typographic:" + FLEXMARK_VERS,
+    sha1 = "c9df5e7631471120e67bd3cde827af1770d583c7",
+)
+
+maven_jar(
+    name = "flexmark-ext-wikilink",
+    artifact = "com.vladsch.flexmark:flexmark-ext-wikilink:" + FLEXMARK_VERS,
+    sha1 = "dc3a988dcd9055c226845cd332f8838096aed32b",
+)
+
+maven_jar(
+    name = "flexmark-ext-yaml-front-matter",
+    artifact = "com.vladsch.flexmark:flexmark-ext-yaml-front-matter:" + FLEXMARK_VERS,
+    sha1 = "588c6233cb11fa1395ab58fec8034bfa9893a3d9",
+)
+
+maven_jar(
+    name = "flexmark-formatter",
+    artifact = "com.vladsch.flexmark:flexmark-formatter:" + FLEXMARK_VERS,
+    sha1 = "6c0869ee52ab22202c52dd1674da821e6a579a65",
+)
+
+maven_jar(
+    name = "flexmark-html-parser",
+    artifact = "com.vladsch.flexmark:flexmark-html-parser:" + FLEXMARK_VERS,
+    sha1 = "95a3a8a68e536f916df8a7a4988ef4aacf9a573f",
+)
+
+maven_jar(
+    name = "flexmark-profile-pegdown",
+    artifact = "com.vladsch.flexmark:flexmark-profile-pegdown:" + FLEXMARK_VERS,
+    sha1 = "a760923a1f03780b2e947a4e8e9b50785e0204e5",
+)
+
+maven_jar(
+    name = "flexmark-util",
+    artifact = "com.vladsch.flexmark:flexmark-util:" + FLEXMARK_VERS,
+    sha1 = "1f6fce06245c0e1660c56db2af0a1ec0b023a164",
+)
+
+# Transitive dependency of flexmark
+maven_jar(
+    name = "autolink",
+    artifact = "org.nibor.autolink:autolink:0.7.0",
+    sha1 = "649f9f13422cf50c926febe6035662ae25dc89b2",
 )
 
 GREENMAIL_VERS = "1.5.5"
diff --git a/java/com/google/gerrit/server/BUILD b/java/com/google/gerrit/server/BUILD
index 4002a4f..96fcd39 100644
--- a/java/com/google/gerrit/server/BUILD
+++ b/java/com/google/gerrit/server/BUILD
@@ -48,9 +48,34 @@
         "//java/org/apache/commons/net",
         "//java/org/eclipse/jgit:server",
         "//lib:args4j",
+        "//lib:autolink",
         "//lib:automaton",
         "//lib:blame-cache",
-        "//lib:grappa",
+        "//lib:flexmark",
+        "//lib:flexmark-ext-abbreviation",
+        "//lib:flexmark-ext-anchorlink",
+        "//lib:flexmark-ext-autolink",
+        "//lib:flexmark-ext-definition",
+        "//lib:flexmark-ext-emoji",
+        "//lib:flexmark-ext-escaped-character",
+        "//lib:flexmark-ext-footnotes",
+        "//lib:flexmark-ext-gfm-issues",
+        "//lib:flexmark-ext-gfm-strikethrough",
+        "//lib:flexmark-ext-gfm-tables",
+        "//lib:flexmark-ext-gfm-tasklist",
+        "//lib:flexmark-ext-gfm-users",
+        "//lib:flexmark-ext-ins",
+        "//lib:flexmark-ext-jekyll-front-matter",
+        "//lib:flexmark-ext-superscript",
+        "//lib:flexmark-ext-tables",
+        "//lib:flexmark-ext-toc",
+        "//lib:flexmark-ext-typographic",
+        "//lib:flexmark-ext-wikilink",
+        "//lib:flexmark-ext-yaml-front-matter",
+        "//lib:flexmark-formatter",
+        "//lib:flexmark-html-parser",
+        "//lib:flexmark-profile-pegdown",
+        "//lib:flexmark-util",
         "//lib:gson",
         "//lib:guava",
         "//lib:guava-retrying",
@@ -59,7 +84,6 @@
         "//lib:jsch",
         "//lib:juniversalchardet",
         "//lib:mime-util",
-        "//lib:pegdown",
         "//lib:protobuf",
         "//lib:servlet-api-3_1",
         "//lib:soy",
diff --git a/java/com/google/gerrit/server/audit/BUILD b/java/com/google/gerrit/server/audit/BUILD
index 5efdc5a..d85668e 100644
--- a/java/com/google/gerrit/server/audit/BUILD
+++ b/java/com/google/gerrit/server/audit/BUILD
@@ -24,9 +24,34 @@
         "//java/org/apache/commons/net",
         "//java/org/eclipse/jgit:server",
         "//lib:args4j",
+        "//lib:autolink",
         "//lib:automaton",
         "//lib:blame-cache",
-        "//lib:grappa",
+        "//lib:flexmark",
+        "//lib:flexmark-ext-abbreviation",
+        "//lib:flexmark-ext-anchorlink",
+        "//lib:flexmark-ext-autolink",
+        "//lib:flexmark-ext-definition",
+        "//lib:flexmark-ext-emoji",
+        "//lib:flexmark-ext-escaped-character",
+        "//lib:flexmark-ext-footnotes",
+        "//lib:flexmark-ext-gfm-issues",
+        "//lib:flexmark-ext-gfm-strikethrough",
+        "//lib:flexmark-ext-gfm-tables",
+        "//lib:flexmark-ext-gfm-tasklist",
+        "//lib:flexmark-ext-gfm-users",
+        "//lib:flexmark-ext-ins",
+        "//lib:flexmark-ext-jekyll-front-matter",
+        "//lib:flexmark-ext-superscript",
+        "//lib:flexmark-ext-tables",
+        "//lib:flexmark-ext-toc",
+        "//lib:flexmark-ext-typographic",
+        "//lib:flexmark-ext-wikilink",
+        "//lib:flexmark-ext-yaml-front-matter",
+        "//lib:flexmark-formatter",
+        "//lib:flexmark-html-parser",
+        "//lib:flexmark-profile-pegdown",
+        "//lib:flexmark-util",
         "//lib:gson",
         "//lib:guava",
         "//lib:guava-retrying",
@@ -35,7 +60,6 @@
         "//lib:jsch",
         "//lib:juniversalchardet",
         "//lib:mime-util",
-        "//lib:pegdown",
         "//lib:protobuf",
         "//lib:servlet-api-3_1",
         "//lib:soy",
diff --git a/java/com/google/gerrit/server/documentation/MarkdownFormatter.java b/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
index a7f9a05..2eb46f1 100644
--- a/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
+++ b/java/com/google/gerrit/server/documentation/MarkdownFormatter.java
@@ -14,30 +14,33 @@
 
 package com.google.gerrit.server.documentation;
 
+import static com.vladsch.flexmark.profiles.pegdown.Extensions.ALL;
+import static com.vladsch.flexmark.profiles.pegdown.Extensions.HARDWRAPS;
+import static com.vladsch.flexmark.profiles.pegdown.Extensions.SUPPRESS_ALL_HTML;
 import static java.nio.charset.StandardCharsets.UTF_8;
-import static org.pegdown.Extensions.ALL;
-import static org.pegdown.Extensions.HARDWRAPS;
-import static org.pegdown.Extensions.SUPPRESS_ALL_HTML;
 
 import com.google.common.base.Strings;
 import com.google.common.flogger.FluentLogger;
+import com.vladsch.flexmark.Extension;
+import com.vladsch.flexmark.ast.Block;
+import com.vladsch.flexmark.ast.Heading;
+import com.vladsch.flexmark.ast.Node;
+import com.vladsch.flexmark.ast.util.TextCollectingVisitor;
+import com.vladsch.flexmark.html.HtmlRenderer;
+import com.vladsch.flexmark.parser.Parser;
+import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter;
+import com.vladsch.flexmark.util.options.MutableDataHolder;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.UnsupportedEncodingException;
 import java.net.URL;
 import java.nio.charset.Charset;
+import java.util.ArrayList;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.apache.commons.lang.StringEscapeUtils;
 import org.eclipse.jgit.util.RawParseUtils;
 import org.eclipse.jgit.util.TemporaryBuffer;
-import org.pegdown.LinkRenderer;
-import org.pegdown.PegDownProcessor;
-import org.pegdown.ToHtmlSerializer;
-import org.pegdown.ast.HeaderNode;
-import org.pegdown.ast.Node;
-import org.pegdown.ast.RootNode;
-import org.pegdown.ast.TextNode;
 
 public class MarkdownFormatter {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@@ -48,9 +51,9 @@
     AtomicBoolean file = new AtomicBoolean();
     String src;
     try {
-      src = readPegdownCss(file);
+      src = readFlexMarkJavaCss(file);
     } catch (IOException err) {
-      logger.atWarning().withCause(err).log("Cannot load pegdown.css");
+      logger.atWarning().withCause(err).log("Cannot load flexmark-java.css");
       src = "";
     }
     defaultCss = file.get() ? null : src;
@@ -61,9 +64,9 @@
       return defaultCss;
     }
     try {
-      return readPegdownCss(new AtomicBoolean());
+      return readFlexMarkJavaCss(new AtomicBoolean());
     } catch (IOException err) {
-      logger.atWarning().withCause(err).log("Cannot load pegdown.css");
+      logger.atWarning().withCause(err).log("Cannot load flexmark-java.css");
       return "";
     }
   }
@@ -81,8 +84,28 @@
     return this;
   }
 
+  private MutableDataHolder markDownOptions() {
+    int options = ALL & ~(HARDWRAPS);
+    if (suppressHtml) {
+      options |= SUPPRESS_ALL_HTML;
+    }
+
+    MutableDataHolder optionsExt =
+        PegdownOptionsAdapter.flexmarkOptions(
+                options, MarkdownFormatterHeader.HeadingExtension.create())
+            .toMutable();
+
+    ArrayList<Extension> extensions = new ArrayList<>();
+    for (Extension extension : optionsExt.get(com.vladsch.flexmark.parser.Parser.EXTENSIONS)) {
+      extensions.add(extension);
+    }
+
+    return optionsExt;
+  }
+
   public byte[] markdownToDocHtml(String md, String charEnc) throws UnsupportedEncodingException {
-    RootNode root = parseMarkdown(md);
+    Node root = parseMarkdown(md);
+    HtmlRenderer renderer = HtmlRenderer.builder(markDownOptions()).build();
     String title = findTitle(root);
 
     StringBuilder html = new StringBuilder();
@@ -100,7 +123,7 @@
     html.append("\n</style>");
     html.append("</head>");
     html.append("<body>\n");
-    html.append(new ToHtmlSerializer(new LinkRenderer()).toHtml(root));
+    html.append(renderer.render(root));
     html.append("\n</body></html>");
     return html.toString().getBytes(charEnc);
   }
@@ -111,38 +134,36 @@
   }
 
   private String findTitle(Node root) {
-    if (root instanceof HeaderNode) {
-      HeaderNode h = (HeaderNode) root;
-      if (h.getLevel() == 1 && h.getChildren() != null && !h.getChildren().isEmpty()) {
-        StringBuilder b = new StringBuilder();
-        for (Node n : root.getChildren()) {
-          if (n instanceof TextNode) {
-            b.append(((TextNode) n).getText());
-          }
-        }
-        return b.toString();
+    if (root instanceof Heading) {
+      Heading h = (Heading) root;
+      if (h.getLevel() == 1 && h.hasChildren()) {
+        TextCollectingVisitor collectingVisitor = new TextCollectingVisitor();
+        return collectingVisitor.collectAndGetText(h);
       }
     }
 
-    for (Node n : root.getChildren()) {
-      String title = findTitle(n);
-      if (title != null) {
-        return title;
+    if (root instanceof Block && root.hasChildren()) {
+      Node child = root.getFirstChild();
+      while (child != null) {
+        String title = findTitle(child);
+        if (title != null) {
+          return title;
+        }
+        child = child.getNext();
       }
     }
+
     return null;
   }
 
-  private RootNode parseMarkdown(String md) {
-    int options = ALL & ~(HARDWRAPS);
-    if (suppressHtml) {
-      options |= SUPPRESS_ALL_HTML;
-    }
-    return new PegDownProcessor(options).parseMarkdown(md.toCharArray());
+  private Node parseMarkdown(String md) {
+    Parser parser = Parser.builder(markDownOptions()).build();
+    Node document = parser.parse(md);
+    return document;
   }
 
-  private static String readPegdownCss(AtomicBoolean file) throws IOException {
-    String name = "pegdown.css";
+  private static String readFlexMarkJavaCss(AtomicBoolean file) throws IOException {
+    String name = "flexmark-java.css";
     URL url = MarkdownFormatter.class.getResource(name);
     if (url == null) {
       throw new FileNotFoundException("Resource " + name);
diff --git a/java/com/google/gerrit/server/documentation/MarkdownFormatterHeader.java b/java/com/google/gerrit/server/documentation/MarkdownFormatterHeader.java
new file mode 100644
index 0000000..482b058
--- /dev/null
+++ b/java/com/google/gerrit/server/documentation/MarkdownFormatterHeader.java
@@ -0,0 +1,162 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// 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.google.gerrit.server.documentation;
+
+import com.google.common.flogger.FluentLogger;
+import com.vladsch.flexmark.ast.Heading;
+import com.vladsch.flexmark.ast.Node;
+import com.vladsch.flexmark.ext.anchorlink.AnchorLink;
+import com.vladsch.flexmark.ext.anchorlink.internal.AnchorLinkNodeRenderer;
+import com.vladsch.flexmark.html.CustomNodeRenderer;
+import com.vladsch.flexmark.html.HtmlRenderer;
+import com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
+import com.vladsch.flexmark.html.HtmlWriter;
+import com.vladsch.flexmark.html.renderer.DelegatingNodeRendererFactory;
+import com.vladsch.flexmark.html.renderer.NodeRenderer;
+import com.vladsch.flexmark.html.renderer.NodeRendererContext;
+import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
+import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
+import com.vladsch.flexmark.profiles.pegdown.Extensions;
+import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter;
+import com.vladsch.flexmark.util.options.DataHolder;
+import com.vladsch.flexmark.util.options.MutableDataHolder;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class MarkdownFormatterHeader {
+  private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+  static class HeadingExtension implements HtmlRendererExtension {
+    @Override
+    public void rendererOptions(final MutableDataHolder options) {
+      // add any configuration settings to options you want to apply to everything, here
+    }
+
+    @Override
+    public void extend(final HtmlRenderer.Builder rendererBuilder, final String rendererType) {
+      rendererBuilder.nodeRendererFactory(new HeadingNodeRenderer.Factory());
+    }
+
+    static HeadingExtension create() {
+      return new HeadingExtension();
+    }
+  }
+
+  static class HeadingNodeRenderer implements NodeRenderer {
+    public HeadingNodeRenderer(DataHolder options) {}
+
+    @Override
+    public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
+      return new HashSet<NodeRenderingHandler<? extends Node>>(
+          Arrays.asList(
+              new NodeRenderingHandler<AnchorLink>(
+                  AnchorLink.class,
+                  new CustomNodeRenderer<AnchorLink>() {
+                    @Override
+                    public void render(
+                        AnchorLink node, NodeRendererContext context, HtmlWriter html) {
+                      HeadingNodeRenderer.this.render(node, context, html);
+                    }
+                  }),
+              new NodeRenderingHandler<Heading>(
+                  Heading.class,
+                  new CustomNodeRenderer<Heading>() {
+                    @Override
+                    public void render(Heading node, NodeRendererContext context, HtmlWriter html) {
+                      HeadingNodeRenderer.this.render(node, context, html);
+                    }
+                  })));
+    }
+
+    void render(final AnchorLink node, final NodeRendererContext context, final HtmlWriter html) {
+      Node parent = node.getParent();
+
+      if (parent instanceof Heading && ((Heading) parent).getLevel() == 1) {
+        // render without anchor link
+        context.renderChildren(node);
+      } else {
+        context.delegateRender();
+      }
+    }
+
+    static boolean haveExtension(int extensions, int flags) {
+      return (extensions & flags) != 0;
+    }
+
+    static boolean haveAllExtensions(int extensions, int flags) {
+      return (extensions & flags) == flags;
+    }
+
+    void render(final Heading node, final NodeRendererContext context, final HtmlWriter html) {
+      if (node.getLevel() == 1) {
+        // render without anchor link
+        final int extensions = context.getOptions().get(PegdownOptionsAdapter.PEGDOWN_EXTENSIONS);
+        if (context.getHtmlOptions().renderHeaderId
+            || haveExtension(extensions, Extensions.ANCHORLINKS)
+            || haveAllExtensions(
+                extensions, Extensions.EXTANCHORLINKS | Extensions.EXTANCHORLINKS_WRAP)) {
+          String id = context.getNodeId(node);
+          if (id != null) {
+            html.attr("id", id);
+          }
+        }
+
+        if (context.getHtmlOptions().sourcePositionParagraphLines) {
+          html.srcPos(node.getChars())
+              .withAttr()
+              .tagLine(
+                  "h" + node.getLevel(),
+                  new Runnable() {
+                    @Override
+                    public void run() {
+                      html.srcPos(node.getText()).withAttr().tag("span");
+                      context.renderChildren(node);
+                      html.tag("/span");
+                    }
+                  });
+        } else {
+          html.srcPos(node.getText())
+              .withAttr()
+              .tagLine(
+                  "h" + node.getLevel(),
+                  new Runnable() {
+                    @Override
+                    public void run() {
+                      context.renderChildren(node);
+                    }
+                  });
+        }
+      } else {
+        context.delegateRender();
+      }
+    }
+
+    public static class Factory implements DelegatingNodeRendererFactory {
+      @Override
+      public NodeRenderer create(final DataHolder options) {
+        return new HeadingNodeRenderer(options);
+      }
+
+      @Override
+      public Set<Class<? extends NodeRendererFactory>> getDelegates() {
+        Set<Class<? extends NodeRendererFactory>> delegates =
+            new HashSet<Class<? extends NodeRendererFactory>>();
+        delegates.add(AnchorLinkNodeRenderer.Factory.class);
+        return delegates;
+      }
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/mail/BUILD b/javatests/com/google/gerrit/mail/BUILD
index 488bbcc..2fd8f24 100644
--- a/javatests/com/google/gerrit/mail/BUILD
+++ b/javatests/com/google/gerrit/mail/BUILD
@@ -22,7 +22,6 @@
         "//java/com/google/gerrit/server/project/testing:project-test-util",
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//java/org/eclipse/jgit:server",
-        "//lib:grappa",
         "//lib:gson",
         "//lib:guava-retrying",
         "//lib:gwtorm",
diff --git a/javatests/com/google/gerrit/server/BUILD b/javatests/com/google/gerrit/server/BUILD
index b413481..29e9a0b 100644
--- a/javatests/com/google/gerrit/server/BUILD
+++ b/javatests/com/google/gerrit/server/BUILD
@@ -55,7 +55,6 @@
         "//java/com/google/gerrit/testing:gerrit-test-util",
         "//java/com/google/gerrit/truth",
         "//java/org/eclipse/jgit:server",
-        "//lib:grappa",
         "//lib:gson",
         "//lib:guava",
         "//lib:guava-retrying",
diff --git a/javatests/com/google/gerrit/server/util/ParboiledTest.java b/javatests/com/google/gerrit/server/util/ParboiledTest.java
deleted file mode 100644
index 3bcfb56..0000000
--- a/javatests/com/google/gerrit/server/util/ParboiledTest.java
+++ /dev/null
@@ -1,74 +0,0 @@
-// Copyright (C) 2014 The Android Open Source Project
-//
-// 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.google.gerrit.server.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.parboiled.BaseParser;
-import org.parboiled.Parboiled;
-import org.parboiled.Rule;
-import org.parboiled.annotations.BuildParseTree;
-import org.parboiled.parserunners.ReportingParseRunner;
-import org.parboiled.support.ParseTreeUtils;
-import org.parboiled.support.ParsingResult;
-
-public class ParboiledTest {
-
-  private static final String EXPECTED =
-      "[Expression] '42'\n"
-          + "  [Term] '42'\n"
-          + "    [Factor] '42'\n"
-          + "      [Number] '42'\n"
-          + "        [0..9] '4'\n"
-          + "        [0..9] '2'\n"
-          + "    [zeroOrMore]\n"
-          + "  [zeroOrMore]\n";
-
-  private CalculatorParser parser;
-
-  @Before
-  public void setUp() {
-    parser = Parboiled.createParser(CalculatorParser.class);
-  }
-
-  @Test
-  public void test() {
-    ParsingResult<String> result = new ReportingParseRunner<String>(parser.Expression()).run("42");
-    assertThat(result.isSuccess()).isTrue();
-    // next test is optional; we could stop here.
-    assertThat(ParseTreeUtils.printNodeTree(result)).isEqualTo(EXPECTED);
-  }
-
-  @BuildParseTree
-  static class CalculatorParser extends BaseParser<Object> {
-    Rule Expression() {
-      return sequence(Term(), zeroOrMore(anyOf("+-"), Term()));
-    }
-
-    Rule Term() {
-      return sequence(Factor(), zeroOrMore(anyOf("*/"), Factor()));
-    }
-
-    Rule Factor() {
-      return firstOf(Number(), sequence('(', Expression(), ')'));
-    }
-
-    Rule Number() {
-      return oneOrMore(charRange('0', '9'));
-    }
-  }
-}
diff --git a/javatests/com/google/gerrit/server/util/git/BUILD b/javatests/com/google/gerrit/server/util/git/BUILD
index 928705c..61a776fa 100644
--- a/javatests/com/google/gerrit/server/util/git/BUILD
+++ b/javatests/com/google/gerrit/server/util/git/BUILD
@@ -12,7 +12,6 @@
         "//java/com/google/gerrit/server/util/git",
         "//java/com/google/gerrit/truth",
         "//java/org/eclipse/jgit:server",
-        "//lib:grappa",
         "//lib:gson",
         "//lib:guava",
         "//lib:guava-retrying",
diff --git a/lib/BUILD b/lib/BUILD
index 38b0b80..e5034c9 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -123,32 +123,257 @@
 )
 
 java_library(
-    name = "pegdown",
-    data = ["//lib:LICENSE-Apache2.0"],
+    name = "flexmark",
+    data = ["//lib:LICENSE-flexmark"],
     visibility = ["//visibility:public"],
-    exports = ["@pegdown//jar"],
-    runtime_deps = [":grappa"],
-)
-
-java_library(
-    name = "grappa",
-    data = ["//lib:LICENSE-Apache2.0"],
-    visibility = ["//visibility:public"],
-    exports = ["@grappa//jar"],
+    exports = ["@flexmark//jar"],
     runtime_deps = [
-        ":jitescript",
-        "//lib/ow2:ow2-asm",
-        "//lib/ow2:ow2-asm-analysis",
-        "//lib/ow2:ow2-asm-tree",
-        "//lib/ow2:ow2-asm-util",
+        ":flexmark-ext-abbreviation",
     ],
 )
 
 java_library(
-    name = "jitescript",
-    data = ["//lib:LICENSE-Apache2.0"],
+    name = "flexmark-ext-abbreviation",
+    data = ["//lib:LICENSE-flexmark"],
     visibility = ["//visibility:public"],
-    exports = ["@jitescript//jar"],
+    exports = ["@flexmark-ext-abbreviation//jar"],
+    runtime_deps = [
+        ":flexmark-ext-anchorlink",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-anchorlink",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-anchorlink//jar"],
+    runtime_deps = [
+        ":flexmark-ext-autolink",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-autolink",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-autolink//jar"],
+    runtime_deps = [
+        ":flexmark-ext-definition",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-definition",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-definition//jar"],
+    runtime_deps = [
+        ":flexmark-ext-emoji",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-emoji",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-emoji//jar"],
+    runtime_deps = [
+        ":flexmark-ext-escaped-character",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-escaped-character",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-escaped-character//jar"],
+    runtime_deps = [
+        ":flexmark-ext-footnotes",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-footnotes",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-footnotes//jar"],
+    runtime_deps = [
+        ":flexmark-ext-gfm-issues",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-gfm-issues",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-gfm-issues//jar"],
+    runtime_deps = [
+        ":flexmark-ext-gfm-strikethrough",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-gfm-strikethrough",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-gfm-strikethrough//jar"],
+    runtime_deps = [
+        ":flexmark-ext-gfm-tables",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-gfm-tables",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-gfm-tables//jar"],
+    runtime_deps = [
+        ":flexmark-ext-gfm-tasklist",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-gfm-tasklist",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-gfm-tasklist//jar"],
+    runtime_deps = [
+        ":flexmark-ext-gfm-users",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-gfm-users",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-gfm-users//jar"],
+    runtime_deps = [
+        ":flexmark-ext-ins",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-ins",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-ins//jar"],
+    runtime_deps = [
+        ":flexmark-ext-jekyll-front-matter",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-jekyll-front-matter",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-jekyll-front-matter//jar"],
+    runtime_deps = [
+        ":flexmark-ext-superscript",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-superscript",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-superscript//jar"],
+    runtime_deps = [
+        ":flexmark-ext-tables",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-tables",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-tables//jar"],
+    runtime_deps = [
+        ":flexmark-ext-toc",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-toc",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-toc//jar"],
+    runtime_deps = [
+        ":flexmark-ext-typographic",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-typographic",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-typographic//jar"],
+    runtime_deps = [
+        ":flexmark-ext-wikilink",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-wikilink",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-wikilink//jar"],
+    runtime_deps = [
+        ":flexmark-ext-yaml-front-matter",
+    ],
+)
+
+java_library(
+    name = "flexmark-ext-yaml-front-matter",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-ext-yaml-front-matter//jar"],
+    runtime_deps = [
+        ":flexmark-formatter",
+    ],
+)
+
+java_library(
+    name = "flexmark-formatter",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-formatter//jar"],
+    runtime_deps = [
+        ":flexmark-html-parser",
+    ],
+)
+
+java_library(
+    name = "flexmark-html-parser",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-html-parser//jar"],
+    runtime_deps = [
+        ":flexmark-profile-pegdown",
+    ],
+)
+
+java_library(
+    name = "flexmark-profile-pegdown",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-profile-pegdown//jar"],
+    runtime_deps = [
+        ":flexmark-util",
+    ],
+)
+
+java_library(
+    name = "flexmark-util",
+    data = ["//lib:LICENSE-flexmark"],
+    visibility = ["//visibility:public"],
+    exports = ["@flexmark-util//jar"],
+)
+
+java_library(
+    name = "autolink",
+    data = ["//lib:LICENSE-autolink"],
+    visibility = ["//visibility:public"],
+    exports = ["@autolink//jar"],
 )
 
 java_library(
diff --git a/lib/LICENSE-autolink b/lib/LICENSE-autolink
new file mode 100644
index 0000000..565820a
--- /dev/null
+++ b/lib/LICENSE-autolink
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Robin Stocker
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/lib/LICENSE-flexmark b/lib/LICENSE-flexmark
new file mode 100644
index 0000000..c5e6ce0
--- /dev/null
+++ b/lib/LICENSE-flexmark
@@ -0,0 +1,26 @@
+Copyright (c) 2015-2016, Atlassian Pty Ltd
+All rights reserved.
+
+Copyright (c) 2016, Vladimir Schneider,
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/resources/com/google/gerrit/server/documentation/pegdown.css b/resources/com/google/gerrit/server/documentation/flexmark-java.css
similarity index 100%
rename from resources/com/google/gerrit/server/documentation/pegdown.css
rename to resources/com/google/gerrit/server/documentation/flexmark-java.css