Markdown: render /+/foo.md as markdown

Default to the rendered version of a markdown file when it is
clicked on in a tree or navigated to using /+/ operator in URL.

Change-Id: I385e6fb6fe2cfcddf6ef8b0d0f4192e920e848e5
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
index ae13a31..ddb0abf 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesFilter.java
@@ -244,6 +244,7 @@
         return new RefServlet(accessFactory, renderer, timeCache);
       case REVISION:
         return new RevisionServlet(accessFactory, renderer, linkifier());
+      case SHOW:
       case PATH:
         return new PathServlet(accessFactory, renderer, urls);
       case DIFF:
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java
index 64c5177..b6223f4 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/GitilesView.java
@@ -61,6 +61,7 @@
     REFS,
     REVISION,
     PATH,
+    SHOW,
     DIFF,
     LOG,
     DESCRIBE,
@@ -108,6 +109,7 @@
         case DOC:
         case ARCHIVE:
         case BLAME:
+        case SHOW:
           path = other.path;
           // Fallthrough.
         case REVISION:
@@ -227,6 +229,7 @@
       switch (type) {
         case PATH:
         case DIFF:
+        case SHOW:
           checkState(path != null, "cannot set null path on %s view", type);
           break;
         case BLAME:
@@ -312,6 +315,7 @@
           checkRevision();
           break;
         case PATH:
+        case SHOW:
           checkPath();
           break;
         case DIFF:
@@ -417,6 +421,10 @@
     return new Builder(Type.PATH);
   }
 
+  public static Builder show() {
+    return new Builder(Type.SHOW);
+  }
+
   public static Builder diff() {
     return new Builder(Type.DIFF);
   }
@@ -598,6 +606,10 @@
         url.append(repositoryName).append("/+/").append(revision.getName()).append('/')
             .append(path);
         break;
+      case SHOW:
+        url.append(repositoryName).append("/+show/").append(revision.getName())
+            .append('/').append(path);
+        break;
       case DIFF:
         url.append(repositoryName).append("/+/");
         if (isFirstParent(revision, oldRevision)) {
@@ -625,7 +637,13 @@
             .append(path);
         break;
       case DOC:
-        url.append(repositoryName).append("/+doc/").append(revision.getName());
+        url.append(repositoryName);
+        if (path != null && path.endsWith(".md")) {
+          url.append("/+/");
+        } else {
+          url.append("/+doc/");
+        }
+        url.append(revision.getName());
         if (path != null) {
           url.append('/').append(path);
         }
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/ViewFilter.java b/gitiles-servlet/src/main/java/com/google/gitiles/ViewFilter.java
index 99259d6..d8a0420 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/ViewFilter.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/ViewFilter.java
@@ -213,9 +213,15 @@
     }
     if (result.getOldRevision() != null) {
       return parseDiffCommand(repoName, result);
-    } else {
-      return parseShowCommand(repoName, result);
     }
+    GitilesView.Builder b = parseShowCommand(repoName, result);
+    if (b != null && b.getPathPart() != null && b.getPathPart().endsWith(".md")) {
+      return GitilesView.doc()
+        .setRepositoryName(repoName)
+        .setRevision(result.getRevision())
+        .setPathPart(result.getPath());
+    }
+    return b;
   }
 
   private GitilesView.Builder parseBlameCommand(
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/DocServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/DocServlet.java
index b6a1b82..e6d37a0 100644
--- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/DocServlet.java
+++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/DocServlet.java
@@ -170,7 +170,7 @@
     data.put("pageTitle", MoreObjects.firstNonNull(
         MarkdownUtil.getTitle(doc),
         view.getPathPart()));
-    data.put("sourceUrl", GitilesView.path().copyFrom(view).toUrl());
+    data.put("sourceUrl", GitilesView.show().copyFrom(view).toUrl());
     data.put("logUrl", GitilesView.log().copyFrom(view).toUrl());
     data.put("blameUrl", GitilesView.blame().copyFrom(view).toUrl());
     data.put("navbarHtml", new MarkdownToHtml(view).toSoyHtml(nav));
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/GitilesViewTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/GitilesViewTest.java
index 76e7c01..9e6ddd8 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/GitilesViewTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/GitilesViewTest.java
@@ -264,6 +264,69 @@
   }
 
   @Test
+  public void oneDocFileAuto() throws Exception {
+    ObjectId id = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+    GitilesView view = GitilesView.doc()
+        .copyFrom(HOST)
+        .setRepositoryName("foo/bar")
+        .setRevision(Revision.unpeeled("master", id))
+        .setPathPart("/README.md")
+        .build();
+
+    assertEquals("/b", view.getServletPath());
+    assertEquals(Type.DOC, view.getType());
+    assertEquals("host", view.getHostName());
+    assertEquals("foo/bar", view.getRepositoryName());
+    assertEquals(id, view.getRevision().getId());
+    assertEquals("master", view.getRevision().getName());
+    assertEquals("README.md", view.getPathPart());
+    assertTrue(HOST.getParameters().isEmpty());
+    assertEquals("/b/foo/bar/+/master/README.md", view.toUrl());
+  }
+
+  @Test
+  public void oneDocTree() throws Exception {
+    ObjectId id = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+    GitilesView view = GitilesView.doc()
+        .copyFrom(HOST)
+        .setRepositoryName("foo/bar")
+        .setRevision(Revision.unpeeled("master", id))
+        .setPathPart("/docs/")
+        .build();
+
+    assertEquals("/b", view.getServletPath());
+    assertEquals(Type.DOC, view.getType());
+    assertEquals("host", view.getHostName());
+    assertEquals("foo/bar", view.getRepositoryName());
+    assertEquals(id, view.getRevision().getId());
+    assertEquals("master", view.getRevision().getName());
+    assertEquals("docs", view.getPathPart());
+    assertTrue(HOST.getParameters().isEmpty());
+    assertEquals("/b/foo/bar/+doc/master/docs", view.toUrl());
+  }
+
+  @Test
+  public void showMarkdown() throws Exception {
+    ObjectId id = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
+    GitilesView view = GitilesView.show()
+        .copyFrom(HOST)
+        .setRepositoryName("foo/bar")
+        .setRevision(Revision.unpeeled("master", id))
+        .setPathPart("/README.md")
+        .build();
+
+    assertEquals("/b", view.getServletPath());
+    assertEquals(Type.SHOW, view.getType());
+    assertEquals("host", view.getHostName());
+    assertEquals("foo/bar", view.getRepositoryName());
+    assertEquals(id, view.getRevision().getId());
+    assertEquals("master", view.getRevision().getName());
+    assertEquals("README.md", view.getPathPart());
+    assertTrue(HOST.getParameters().isEmpty());
+    assertEquals("/b/foo/bar/+show/master/README.md", view.toUrl());
+  }
+
+  @Test
   public void multiplePathComponents() throws Exception {
     ObjectId id = ObjectId.fromString("abcd1234abcd1234abcd1234abcd1234abcd1234");
     GitilesView view = GitilesView.path()
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/ViewFilterTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/ViewFilterTest.java
index acec4eb..a3ae81a 100644
--- a/gitiles-servlet/src/test/java/com/google/gitiles/ViewFilterTest.java
+++ b/gitiles-servlet/src/test/java/com/google/gitiles/ViewFilterTest.java
@@ -68,6 +68,8 @@
     assertEquals(Type.REVISION, getView("/repo/+/" + hex.substring(0, 7)).getType());
     assertEquals(Type.PATH, getView("/repo/+/master/").getType());
     assertEquals(Type.PATH, getView("/repo/+/" + hex + "/").getType());
+    assertEquals(Type.PATH, getView("/repo/+/" + hex + "/index.c").getType());
+    assertEquals(Type.DOC, getView("/repo/+/" + hex + "/index.md").getType());
     assertEquals(Type.DIFF, getView("/repo/+/master^..master").getType());
     assertEquals(Type.DIFF, getView("/repo/+/master^..master/").getType());
     assertEquals(Type.DIFF, getView("/repo/+/" + parent.name() + ".." + hex + "/").getType());