Merge "Add and fix copyright headers"
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/Module.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/Module.java
index 65be419..d822faf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/Module.java
@@ -31,6 +31,7 @@
 import com.googlesource.gerrit.plugins.xdocs.formatter.Formatter;
 import com.googlesource.gerrit.plugins.xdocs.formatter.MarkdownFormatter;
 import com.googlesource.gerrit.plugins.xdocs.formatter.PlainTextFormatter;
+import com.googlesource.gerrit.plugins.xdocs.formatter.ZipFormatter;
 
 import java.util.List;
 
@@ -57,6 +58,9 @@
     bind(Formatter.class)
         .annotatedWith(Exports.named(PlainTextFormatter.NAME))
         .to(PlainTextFormatter.class);
+    bind(Formatter.class)
+        .annotatedWith(Exports.named(ZipFormatter.NAME))
+        .to(ZipFormatter.class);
 
     DynamicSet.bind(binder(), ProjectWebLink.class)
         .to(XDocWebLink.class);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java
index e84e1e4..1f86e69 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocGlobalConfig.java
@@ -17,9 +17,12 @@
 import com.googlesource.gerrit.plugins.xdocs.formatter.AsciidoctorFormatter;
 import com.googlesource.gerrit.plugins.xdocs.formatter.MarkdownFormatter;
 import com.googlesource.gerrit.plugins.xdocs.formatter.PlainTextFormatter;
+import com.googlesource.gerrit.plugins.xdocs.formatter.ZipFormatter;
 
 import org.eclipse.jgit.lib.Config;
 
+import java.util.Arrays;
+
 public class XDocGlobalConfig {
   public static final String SECTION_FORMATTER = "formatter";
   public static final String KEY_ALLOW_HTML = "allowHtml";
@@ -49,5 +52,7 @@
         "text/x-markdown");
     cfg.setString(SECTION_FORMATTER, PlainTextFormatter.NAME, KEY_MIME_TYPE,
         "text/plain");
+    cfg.setStringList(SECTION_FORMATTER, ZipFormatter.NAME, KEY_EXT,
+        Arrays.asList("jar", "war", "zip"));
   }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocs.gwt.xml b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocs.gwt.xml
index 7855c78..405a81f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocs.gwt.xml
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/XDocs.gwt.xml
@@ -18,6 +18,7 @@
   <!-- Inherit the core Web Toolkit stuff.                        -->
   <inherits name="com.google.gwt.user.User"/>
   <!-- Other module inherits                                      -->
+  <inherits name="com.google.gerrit.GerritGwtUICommon"/>
   <inherits name="com.google.gerrit.Plugin"/>
   <inherits name="com.google.gwt.http.HTTP"/>
   <inherits name="com.google.gwt.json.JSON"/>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeApi.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeApi.java
index 69ecf7d..44d1d49 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeApi.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeApi.java
@@ -31,6 +31,10 @@
     return call(id, "detail");
   }
 
+  public static RestApi revision(String changeId, int patchSetId) {
+    return change(changeId).view("revisions").id(patchSetId);
+  }
+
   private static RestApi call(String id, String action) {
     return change(id).view(action);
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeInfo.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeInfo.java
index caad355..9f28d99 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeInfo.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/ChangeInfo.java
@@ -20,6 +20,7 @@
 public class ChangeInfo extends JavaScriptObject {
   public final native String project() /*-{ return this.project; }-*/;
   public final native NativeMap<RevisionInfo> revisions() /*-{ return this.revisions; }-*/;
+  public final native int _number() /*-{ return this._number; }-*/;
 
   protected ChangeInfo() {
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/DiffApi.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/DiffApi.java
new file mode 100644
index 0000000..7bd9b7f
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/DiffApi.java
@@ -0,0 +1,31 @@
+// 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.googlesource.gerrit.plugins.xdocs.client;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.plugin.client.rpc.RestApi;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public class DiffApi {
+
+  public static void list(String changeId, int patchSetId, Integer basePatchSetId,
+      AsyncCallback<NativeMap<FileInfo>> cb) {
+    RestApi api = ChangeApi.revision(changeId, patchSetId).view("files");
+    if (basePatchSetId != null) {
+      api.addParameter("base", basePatchSetId);
+    }
+    api.get(NativeMap.copyKeysIntoChildren("path", cb));
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/FileInfo.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/FileInfo.java
new file mode 100644
index 0000000..8fff48b
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/FileInfo.java
@@ -0,0 +1,53 @@
+// 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.googlesource.gerrit.plugins.xdocs.client;
+
+import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArray;
+
+import java.util.Collections;
+import java.util.Comparator;
+
+public class FileInfo extends JavaScriptObject {
+  public final native String path() /*-{ return this.path; }-*/;
+  public final native boolean binary() /*-{ return this.binary || false; }-*/;
+
+  public static void sortFileInfoByPath(JsArray<FileInfo> list) {
+    Collections.sort(Natives.asList(list), new Comparator<FileInfo>() {
+      @Override
+      public int compare(FileInfo a, FileInfo b) {
+        if (Patch.COMMIT_MSG.equals(a.path())) {
+          return -1;
+        } else if (Patch.COMMIT_MSG.equals(b.path())) {
+          return 1;
+        }
+        return a.path().compareTo(b.path());
+      }
+    });
+  }
+
+  public static String getFileName(String path) {
+    String fileName = Patch.COMMIT_MSG.equals(path)
+        ? "Commit Message"
+        : path;
+    int s = fileName.lastIndexOf('/');
+    return s >= 0 ? fileName.substring(s + 1) : fileName;
+  }
+
+  protected FileInfo() {
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/VoidResult.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/VoidResult.java
new file mode 100644
index 0000000..d381cd6
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/VoidResult.java
@@ -0,0 +1,26 @@
+// 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.googlesource.gerrit.plugins.xdocs.client;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public final class VoidResult extends JavaScriptObject {
+  protected VoidResult() {
+  }
+
+  public static VoidResult create() {
+    return createObject().cast();
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocApi.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocApi.java
new file mode 100644
index 0000000..e8de4ab
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocApi.java
@@ -0,0 +1,66 @@
+// 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.googlesource.gerrit.plugins.xdocs.client;
+
+import com.google.gerrit.plugin.client.Plugin;
+import com.google.gwt.http.client.Request;
+import com.google.gwt.http.client.RequestBuilder;
+import com.google.gwt.http.client.RequestCallback;
+import com.google.gwt.http.client.RequestException;
+import com.google.gwt.http.client.Response;
+import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public class XDocApi {
+  public static String getUrl(String projectName, String revision,
+      String path) {
+    StringBuilder url = new StringBuilder();
+    url.append("plugins/");
+    url.append(Plugin.get().getName());
+    url.append("/project/");
+    url.append(URL.encodeQueryString(projectName));
+    if (revision != null && !"HEAD".equals(revision)) {
+      url.append("/rev/");
+      url.append(URL.encodeQueryString(revision));
+    }
+    url.append("/");
+    url.append(URL.encodeQueryString(path));
+    return url.toString();
+  }
+
+  public static void checkHtml(String url,
+      final AsyncCallback<VoidResult> callback) {
+    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
+    try {
+      builder.sendRequest(null, new RequestCallback() {
+        public void onResponseReceived(Request request, Response response) {
+          int status = response.getStatusCode();
+          if (200 <= status && status < 300) {
+            callback.onSuccess(VoidResult.create());
+          } else {
+            callback.onFailure(new RequestException(status + " "
+                + response.getStatusText()));
+          }
+        }
+
+        public void onError(Request request, Throwable caught) {
+          callback.onFailure(caught);
+        }
+      });
+    } catch (RequestException e) {
+      callback.onFailure(e);
+    }
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocDiffScreen.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocDiffScreen.java
index eb05e5a..12606b6 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocDiffScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocDiffScreen.java
@@ -14,37 +14,50 @@
 
 package com.googlesource.gerrit.plugins.xdocs.client;
 
+import com.google.gerrit.client.rpc.NativeMap;
 import com.google.gerrit.client.rpc.Natives;
+import com.google.gerrit.plugin.client.Plugin;
+import com.google.gwt.core.client.JsArray;
+import com.google.gwt.resources.client.ImageResource;
 import com.google.gwt.user.client.rpc.AsyncCallback;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HorizontalPanel;
 import com.google.gwt.user.client.ui.InlineHyperlink;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.VerticalPanel;
+import com.google.gwt.user.client.ui.Widget;
 
 import com.googlesource.gerrit.plugins.xdocs.client.ChangeInfo.RevisionInfo;
 
 public abstract class XDocDiffScreen extends VerticalPanel {
+  private final String changeId;
   private final String path;
   private String revisionA;
   private String revisionB;
+  private int patchSet;
+  private Integer base;
+  private FlowPanel iconPanel;
 
-  XDocDiffScreen(final String change, final String patchSet, String path) {
+  XDocDiffScreen(String changeId, final String patchSet, String path) {
     setStyleName("xdocs-panel");
 
+    this.changeId = changeId;
     this.path = path;
 
-    ChangeApi.getChangeInfo(change, new AsyncCallback<ChangeInfo>() {
+    ChangeApi.getChangeInfo(changeId, new AsyncCallback<ChangeInfo>() {
 
       @Override
       public void onSuccess(ChangeInfo change) {
-        addPathHeader(change);
         setRevisions(change, patchSet);
+        addHeader(change);
         display(change);
       }
 
       @Override
       public void onFailure(Throwable caught) {
-        showError("Unable to load change " + change + ": " + caught.getMessage());
+        showError("Unable to load change " + XDocDiffScreen.this.changeId
+            + ": " + caught.getMessage());
       }
     });
   }
@@ -63,41 +76,163 @@
     return revisionB;
   }
 
-  private void setRevisions(ChangeInfo change, String patchSet) {
-    int i = patchSet.indexOf("..");
+  private void setRevisions(ChangeInfo change, String patchSetString) {
+    int i = patchSetString.indexOf("..");
     if (i > 0) {
-      revisionA = getRevision(change, patchSet.substring(0, i));
-      if (patchSet.length() > i + 2) {
-        revisionB = getRevision(change, patchSet.substring(i + 2));
+      base = parsePatchSet(patchSetString.substring(0, i));
+      revisionA = getRevision(change, base);
+      if (patchSetString.length() > i + 2) {
+        patchSet = parsePatchSet(patchSetString.substring(i + 2));
+        revisionB = getRevision(change, patchSet);
       } else {
-        throw new IllegalArgumentException("Invalid patch set: " + patchSet);
+        throw new IllegalArgumentException("Invalid patch set: " + patchSetString);
       }
     } else {
+      patchSet = parsePatchSet(patchSetString);
       revisionB = getRevision(change, patchSet);
       revisionA = this.revisionB + "^1";
     }
   }
 
-  private static String getRevision(ChangeInfo change, String patchSet) {
+  private static String getRevision(ChangeInfo change, int patchSet) {
     for (RevisionInfo rev : Natives.asList(change.revisions().values())) {
-      try {
-        if (rev._number() == Integer.valueOf(patchSet)) {
-          return rev.ref();
-        }
-      } catch (NumberFormatException e) {
-        throw new IllegalArgumentException("Invalid patch set: " + patchSet);
+      if (rev._number() == patchSet) {
+        return rev.ref();
       }
     }
     throw new IllegalArgumentException("Patch set " + patchSet + " not found.");
   }
 
-  private void addPathHeader(ChangeInfo change) {
+  private static int parsePatchSet(String patchSet) {
+    try {
+      return Integer.valueOf(patchSet);
+    } catch (NumberFormatException e) {
+      throw new IllegalArgumentException("Invalid patch set: " + patchSet);
+    }
+  }
+
+  private void addHeader(ChangeInfo change) {
     HorizontalPanel p = new HorizontalPanel();
     p.setStyleName("xdocs-header");
+    p.add(getPathHeader(change));
+
+    iconPanel = new FlowPanel();
+    iconPanel.setStyleName("xdocs-icon-panel");
+    p.add(iconPanel);
+    addNavigationButtons(change);
+
+    add(p);
+  }
+
+  private Widget getPathHeader(ChangeInfo change) {
+    HorizontalPanel p = new HorizontalPanel();
+    p.setStyleName("xdocs-file-header");
     p.add(new InlineHyperlink(change.project(), "/admin/projects/" + change.project()));
     p.add(new Label("/"));
     p.add(new Label(path));
-    add(p);
+    return p;
+  }
+
+  private void addNavigationButtons(final ChangeInfo change) {
+    DiffApi.list(changeId, patchSet, base,
+        new AsyncCallback<NativeMap<FileInfo>>() {
+      @Override
+      public void onSuccess(NativeMap<FileInfo> result) {
+        JsArray<FileInfo> files = result.values();
+        FileInfo.sortFileInfoByPath(files);
+        int index = 0;
+        for (int i = 0; i < files.length(); i++) {
+          if (path.equals(files.get(i).path())) {
+            index = i;
+            break;
+          }
+        }
+
+        FileInfo prevInfo = index == 0 ? null : files.get(index - 1);
+        if (prevInfo != null) {
+          iconPanel.add(createNavLink(XDocsPlugin.RESOURCES.goPrev(),
+              change, patchSet, base, prevInfo));
+        }
+
+        iconPanel.add(createIcon(XDocsPlugin.RESOURCES.goUp(),
+            "Up to change", toChange(change)));
+
+        FileInfo nextInfo = index == files.length() - 1
+            ? null
+            : files.get(index + 1);
+        if (nextInfo != null) {
+          iconPanel.add(createNavLink(XDocsPlugin.RESOURCES.goNext(),
+              change, patchSet, base, nextInfo));
+        }
+      }
+
+      @Override
+      public void onFailure(Throwable caught) {
+        showError("Unable to load files of change " + changeId + ": "
+            + caught.getMessage());
+      }
+    });
+  }
+
+  private InlineHyperlink createNavLink(ImageResource res,
+      final ChangeInfo change, final int patchSet, final Integer base,
+      final FileInfo file) {
+    final InlineHyperlink link = createIcon(
+        res, FileInfo.getFileName(file.path()),
+        toFile(change, patchSet, base, file));
+    XDocApi.checkHtml(XDocApi.getUrl(change.project(),
+        getRevision(change, patchSet), file.path()),
+        new AsyncCallback<VoidResult>() {
+      @Override
+      public void onSuccess(VoidResult result) {
+        link.setTargetHistoryToken(
+            toPreview(change, patchSet, base, file));
+      }
+
+      @Override
+      public void onFailure(Throwable caught) {
+      }
+    });
+    return link;
+  }
+
+  private static InlineHyperlink createIcon(ImageResource res, String tooltip, String target) {
+    InlineHyperlink l = new InlineHyperlink(
+        AbstractImagePrototype.create(res).getHTML(), true, target);
+    if (tooltip != null) {
+      l.setTitle(tooltip);
+    }
+    return l;
+  }
+
+  private String toPreview(ChangeInfo change, int patchSet,
+      Integer base, FileInfo file) {
+    String panel = getPanel();
+    return "/x/" + Plugin.get().getName()
+        + toPatchSet(change, patchSet, base)
+        + file.path()
+        + (panel != null ? "," + panel : "");
+  }
+
+  private String toFile(ChangeInfo change, int patchSet, Integer base,
+      FileInfo file) {
+    String panel = file.binary() ? "unified" : getPanel();
+    return toPatchSet(change, patchSet, base)
+        + file.path()
+        + (panel != null ? "," + panel : "");
+  }
+
+  protected String getPanel() {
+    return null;
+  }
+
+  private static String toPatchSet(ChangeInfo change, int patchSet, Integer base) {
+    return toChange(change)
+        + (base != null ? patchSet + ".." + base : patchSet) + "/";
+  }
+
+  private static String toChange(ChangeInfo change) {
+    return "/c/" + change._number() + "/";
   }
 
   protected void showError(String message) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocScreen.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocScreen.java
index 9c3439e..f60e140 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocScreen.java
@@ -14,15 +14,9 @@
 
 package com.googlesource.gerrit.plugins.xdocs.client;
 
-import com.google.gerrit.plugin.client.Plugin;
 import com.google.gerrit.plugin.client.screen.Screen;
-import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gwt.http.client.Request;
-import com.google.gwt.http.client.RequestBuilder;
-import com.google.gwt.http.client.RequestCallback;
-import com.google.gwt.http.client.RequestException;
-import com.google.gwt.http.client.Response;
 import com.google.gwt.http.client.URL;
+import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Frame;
 import com.google.gwt.user.client.ui.HorizontalPanel;
 import com.google.gwt.user.client.ui.InlineHyperlink;
@@ -38,7 +32,7 @@
       String revision = URL.decode(screen.getToken(2));
       String path = URL.decode(screen.getToken(3));
       screen.show(new XDocScreen(projectName, revision, path));
-      screen.setWindowTitle(getFileName(path));
+      screen.setWindowTitle(FileInfo.getFileName(path));
     }
   }
 
@@ -48,7 +42,7 @@
       String projectName = URL.decode(screen.getToken(1));
       String path = URL.decode(screen.getToken(2));
       screen.show(new XDocScreen(projectName, "HEAD", path));
-      screen.setWindowTitle(getFileName(path));
+      screen.setWindowTitle(FileInfo.getFileName(path));
     }
   }
 
@@ -56,37 +50,29 @@
     setStyleName("xdocs-panel");
 
     HorizontalPanel p = new HorizontalPanel();
-    p.setStyleName("xdocs-header");
+    p.setStyleName("xdocs-file-header");
     p.add(new InlineHyperlink(projectName, "/admin/projects/" + projectName));
     p.add(new Label("/"));
     p.add(new Label(path));
     p.add(new Label("(" + revision + ")"));
     add(p);
 
-    final String url = getUrl(projectName, revision, path);
-    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, url);
-    try {
-      builder.sendRequest(null, new RequestCallback() {
-        public void onResponseReceived(Request request, Response response) {
-          int status = response.getStatusCode();
-          if (200 <= status && status < 300) {
-            String frameId = "xdoc_iframe";
-            Frame frame = new Frame(url);
-            frame.getElement().setId(frameId);
-            resize(frame, frameId);
-            add(frame);
-          } else {
-            showError(status + " " + response.getStatusText());
-          }
-        }
+    final String url = XDocApi.getUrl(projectName, revision, path);
+    XDocApi.checkHtml(url, new AsyncCallback<VoidResult>() {
+      @Override
+      public void onSuccess(VoidResult result) {
+        String frameId = "xdoc_iframe";
+        Frame frame = new Frame(url);
+        frame.getElement().setId(frameId);
+        resize(frame, frameId);
+        add(frame);
+      }
 
-        public void onError(Request request, Throwable exception) {
-          showError(exception.getMessage());
-        }
-      });
-    } catch (RequestException e) {
-      showError(e.getMessage());
-    }
+      @Override
+      public void onFailure(Throwable caught) {
+        showError(caught.getMessage());
+      }
+    });
   }
 
   private void showError(String message) {
@@ -95,29 +81,6 @@
     add(l);
   }
 
-  public static String getFileName(String path) {
-    String fileName = Patch.COMMIT_MSG.equals(path)
-        ? "Commit Message"
-        : path;
-    int s = fileName.lastIndexOf('/');
-    return s >= 0 ? fileName.substring(s + 1) : fileName;
-  }
-
-  public static String getUrl(String projectName, String revision, String fileName) {
-    StringBuilder url = new StringBuilder();
-    url.append("plugins/");
-    url.append(Plugin.get().getName());
-    url.append("/project/");
-    url.append(URL.encodeQueryString(projectName));
-    if (revision != null && !"HEAD".equals(revision)) {
-      url.append("/rev/");
-      url.append(URL.encodeQueryString(revision));
-    }
-    url.append("/");
-    url.append(URL.encodeQueryString(fileName));
-    return url.toString();
-  }
-
   public static void resize(Widget w, String id) {
     StringBuilder autoResizeScript = new StringBuilder();
     autoResizeScript.append("if (document.getElementById) {");
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocSideBySideDiffScreen.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocSideBySideDiffScreen.java
index f46344e..9209a36 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocSideBySideDiffScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocSideBySideDiffScreen.java
@@ -27,7 +27,7 @@
       String patchSet = URL.decode(screen.getToken(2));
       String path = URL.decode(screen.getToken(4));
       screen.show(new XDocSideBySideDiffScreen(change, patchSet, path));
-      screen.setWindowTitle(XDocScreen.getFileName(path));
+      screen.setWindowTitle(FileInfo.getFileName(path));
     }
   }
 
@@ -39,13 +39,13 @@
   protected void display(ChangeInfo change) {
     String frameIdA = "xdoc_sidebyside_diff_a_iframe";
     Frame frameA =
-        new Frame(XDocScreen.getUrl(change.project(), getRevisionSideA(), getPath()));
+        new Frame(XDocApi.getUrl(change.project(), getRevisionSideA(), getPath()));
     frameA.getElement().setId(frameIdA);
     XDocScreen.resize(frameA, frameIdA);
 
     String frameIdB = "xdoc_sidebyside_diff_b_iframe";
     Frame frameB =
-        new Frame(XDocScreen.getUrl(change.project(), getRevisionSideB(), getPath()));
+        new Frame(XDocApi.getUrl(change.project(), getRevisionSideB(), getPath()));
     frameB.getElement().setId(frameIdB);
     XDocScreen.resize(frameB, frameIdB);
 
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocUnifiedDiffScreen.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocUnifiedDiffScreen.java
index 57d6dcb..551d76e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocUnifiedDiffScreen.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocUnifiedDiffScreen.java
@@ -26,7 +26,7 @@
       String patchSet = URL.decode(screen.getToken(2));
       String path = URL.decode(screen.getToken(4));
       screen.show(new XDocUnifiedDiffScreen(change, patchSet, path));
-      screen.setWindowTitle(XDocScreen.getFileName(path));
+      screen.setWindowTitle(FileInfo.getFileName(path));
     }
   }
 
@@ -38,7 +38,7 @@
   protected void display(ChangeInfo change) {
     String frameId = "xdoc_unified_diff_iframe";
     Frame frame =
-        new Frame(XDocScreen.getUrl(change.project(), getRevision(), getPath()));
+        new Frame(XDocApi.getUrl(change.project(), getRevision(), getPath()));
     frame.getElement().setId(frameId);
     XDocScreen.resize(frame, frameId);
     add(frame);
@@ -47,4 +47,9 @@
   private String getRevision() {
     return getRevisionA() + "<->" + getRevisionB();
   }
+
+  @Override
+  protected String getPanel() {
+    return "unified";
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocsPlugin.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocsPlugin.java
index 08507c0..71c2714 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocsPlugin.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/client/XDocsPlugin.java
@@ -14,10 +14,13 @@
 
 package com.googlesource.gerrit.plugins.xdocs.client;
 
+import com.google.gerrit.client.Resources;
 import com.google.gerrit.plugin.client.Plugin;
 import com.google.gerrit.plugin.client.PluginEntryPoint;
+import com.google.gwt.core.client.GWT;
 
 public class XDocsPlugin extends PluginEntryPoint {
+  public static final Resources RESOURCES = GWT.create(Resources.class);
 
   @Override
   public void onPluginLoad() {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java
index 42c0456..2f9ae4d 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/AsciidoctorFormatter.java
@@ -15,12 +15,9 @@
 package com.googlesource.gerrit.plugins.xdocs.formatter;
 
 import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_ALLOW_HTML;
-import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_CSS_THEME;
 import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_INCLUDE_TOC;
-import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_INHERIT_CSS;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import com.google.common.base.MoreObjects;
 import com.google.common.io.ByteStreams;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.annotations.PluginData;
@@ -35,17 +32,14 @@
 import org.asciidoctor.Options;
 import org.asciidoctor.OptionsBuilder;
 import org.asciidoctor.SafeMode;
-import org.eclipse.jgit.util.TemporaryBuffer;
 
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.StringReader;
-import java.net.URL;
 import java.util.Properties;
 
 @Singleton
@@ -57,7 +51,6 @@
   private static final String ERUBY = "erb";
 
   private final File baseDir;
-  private final String defaultCss;
   private final Properties attributes;
   private final FormatterUtil util;
   private final Formatters formatters;
@@ -66,7 +59,6 @@
   public AsciidoctorFormatter(@PluginData File baseDir,
       FormatterUtil formatterUtil, Formatters formatters) throws IOException {
     this.baseDir = baseDir;
-    this.defaultCss = readCss();
     this.attributes = readAttributes();
     this.util = formatterUtil;
     this.formatters = formatters;
@@ -92,18 +84,7 @@
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ByteStreams.copy(input, out);
         String html = out.toString(UTF_8.name());
-        String cssTheme = projectCfg.getString(KEY_CSS_THEME);
-        String inheritedCss =
-            util.getInheritedCss(projectName, NAME, "asciidoctor", cssTheme);
-        String projectCss = util.getCss(projectName, "asciidoctor", cssTheme);
-        if (projectCfg.getBoolean(KEY_INHERIT_CSS, true)) {
-          return util.insertCss(html,
-              MoreObjects.firstNonNull(inheritedCss, defaultCss), projectCss);
-        } else {
-          return util.insertCss(html,
-              MoreObjects.firstNonNull(projectCss,
-                  MoreObjects.firstNonNull(inheritedCss, defaultCss)));
-        }
+        return util.applyCss(html, NAME, projectName);
       }
     } finally {
       if (!tmpFile.delete()) {
@@ -154,19 +135,6 @@
     return ab.get();
   }
 
-  private static String readCss() throws IOException {
-    String name = "asciidoctor.css";
-    URL url = AsciidoctorFormatter.class.getResource(name);
-    if (url == null) {
-      throw new FileNotFoundException("Resource " + name);
-    }
-    try (InputStream in = url.openStream();
-        TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap(128 * 1024)) {
-      tmp.copy(in);
-      return new String(tmp.toByteArray(), UTF_8);
-    }
-  }
-
   private static Properties readAttributes() throws IOException {
     Properties attributes = new Properties();
     try (InputStream in = AsciidoctorFormatter.class
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/FormatterUtil.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/FormatterUtil.java
index 2c49704..5d62a7e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/FormatterUtil.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/FormatterUtil.java
@@ -14,11 +14,13 @@
 
 package com.googlesource.gerrit.plugins.xdocs.formatter;
 
+import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_CSS_THEME;
 import static com.googlesource.gerrit.plugins.xdocs.XDocGlobalConfig.KEY_INHERIT_CSS;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
 
 import com.google.common.base.Joiner;
+import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
 import com.google.gerrit.extensions.annotations.PluginData;
 import com.google.gerrit.extensions.annotations.PluginName;
@@ -40,20 +42,31 @@
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
 import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.util.TemporaryBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
 
 @Singleton
 public class FormatterUtil {
+  private static final Logger log = LoggerFactory.getLogger(FormatterUtil.class);
+
   private final String pluginName;
   private final File baseDir;
   private final GitRepositoryManager repoManager;
   private final ProjectCache projectCache;
   private final Formatters formatters;
+  private final Map<String, String> defaultCss;
 
   @Inject
   FormatterUtil(@PluginName String pluginName,
@@ -66,6 +79,7 @@
     this.repoManager = repoManager;
     this.projectCache = projectCache;
     this.formatters = formatters;
+    this.defaultCss = new HashMap<>();
   }
 
   /**
@@ -191,6 +205,47 @@
     return null;
   }
 
+  public String applyCss(String html, String formatterName, String projectName)
+      throws IOException {
+    ConfigSection projectCfg =
+        formatters.getFormatterConfig(formatterName, projectName);
+    String cssName = formatterName.toLowerCase(Locale.US);
+    String cssTheme = projectCfg.getString(KEY_CSS_THEME);
+    String defaultCss = getDefaultCss(formatterName);
+    String inheritedCss =
+        getInheritedCss(projectName, formatterName, cssName, cssTheme);
+    String projectCss = getCss(projectName, cssName, cssTheme);
+    if (projectCfg.getBoolean(KEY_INHERIT_CSS, true)) {
+      return insertCss(html,
+          MoreObjects.firstNonNull(inheritedCss, defaultCss), projectCss);
+    } else {
+      return insertCss(html,
+          MoreObjects.firstNonNull(projectCss,
+              MoreObjects.firstNonNull(inheritedCss, defaultCss)));
+    }
+  }
+
+  private String getDefaultCss(String formatterName) throws IOException {
+    String css = defaultCss.get(formatterName) ;
+    if (css == null) {
+      URL url = FormatterUtil.class.getResource(
+          formatterName.toLowerCase(Locale.US) + ".css");
+      if (url != null) {
+        try (InputStream in = url.openStream();
+            TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap(128 * 1024)) {
+          tmp.copy(in);
+          css = new String(tmp.toByteArray(), UTF_8);
+        }
+      } else {
+        log.info(String.format("No default CSS for formatter '%s' found.",
+            formatterName));
+        css = "";
+      }
+      defaultCss.put(formatterName, css);
+    }
+    return css;
+  }
+
   /**
    * Inserts the given CSS into the given HTML.
    *
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/HtmlBuilder.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/HtmlBuilder.java
new file mode 100644
index 0000000..c971df5
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/HtmlBuilder.java
@@ -0,0 +1,121 @@
+// 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.googlesource.gerrit.plugins.xdocs.formatter;
+
+import static org.apache.commons.lang.StringEscapeUtils.escapeHtml;
+
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+
+public class HtmlBuilder {
+  private final DateFormat rfc2822DateFormatter;
+  private final StringBuilder html = new StringBuilder();
+
+  @Inject
+  HtmlBuilder(@GerritPersonIdent PersonIdent gerritIdent) {
+    rfc2822DateFormatter =
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.US);
+    rfc2822DateFormatter.setCalendar(Calendar.getInstance(
+        gerritIdent.getTimeZone(), Locale.US));
+  }
+
+  public HtmlBuilder startDocument() {
+    return open("html");
+  }
+
+  public HtmlBuilder endDocument() {
+    return close("html");
+  }
+
+  public HtmlBuilder openBody() {
+    return open("body");
+  }
+
+  public HtmlBuilder closeBody() {
+    return close("body");
+  }
+
+  public HtmlBuilder openHead() {
+    return open("head");
+  }
+
+  public HtmlBuilder closeHead() {
+    return close("head");
+  }
+
+  public HtmlBuilder openTable() {
+    return open("table");
+  }
+
+  public HtmlBuilder closeTable() {
+    return close("table");
+  }
+
+  public HtmlBuilder openRow() {
+    return open("tr");
+  }
+
+  public HtmlBuilder closeRow() {
+    return close("tr");
+  }
+
+  public HtmlBuilder appendCellHeader(String s) {
+    return open("th").append(s).close("th");
+  }
+
+  public HtmlBuilder appendCell(String s) {
+    return open("td").append(s).close("td");
+  }
+
+  public HtmlBuilder appendCell() {
+    return appendCell("");
+  }
+
+  public HtmlBuilder appendDateCell(long date) {
+    return open("td").appendDate(date).close("td");
+  }
+
+  public HtmlBuilder open(String tag) {
+    html.append("<").append(tag).append(">");
+    return this;
+  }
+
+  public HtmlBuilder close(String tag) {
+    html.append("</").append(tag).append(">");
+    return this;
+  }
+
+  public HtmlBuilder append(String s) {
+    html.append(escapeHtml(s));
+    return this;
+  }
+
+  public HtmlBuilder appendDate(long date) {
+    html.append(rfc2822DateFormatter.format(new Date(date)));
+    return this;
+  }
+
+  public String toString() {
+    return html.toString();
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/ZipFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/ZipFormatter.java
new file mode 100644
index 0000000..fcb9bd8
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/formatter/ZipFormatter.java
@@ -0,0 +1,77 @@
+// 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.googlesource.gerrit.plugins.xdocs.formatter;
+
+import com.google.inject.Inject;
+
+import com.googlesource.gerrit.plugins.xdocs.ConfigSection;
+
+import org.apache.commons.io.FileUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class ZipFormatter implements StreamFormatter {
+  public static final String NAME = "ZIP";
+
+  private final FormatterUtil util;
+  private final HtmlBuilder html;
+
+  @Inject
+  ZipFormatter(
+      Formatters formatters,
+      FormatterUtil formatterUtil,
+      HtmlBuilder html) {
+    this.util = formatterUtil;
+    this.html = html;
+  }
+
+  @Override
+  public String format(String projectName, String revision, ConfigSection globalCfg,
+      InputStream raw) throws IOException {
+    html.startDocument()
+        .openHead()
+        .closeHead()
+        .openBody()
+        .openTable()
+        .appendCellHeader("name")
+        .appendCellHeader("size")
+        .appendCellHeader("last modified");
+    try (ZipInputStream zip = new ZipInputStream(raw)) {
+      for (ZipEntry entry; (entry = zip.getNextEntry()) != null;) {
+        html.openRow()
+            .appendCell(entry.getName());
+        if (!entry.isDirectory()) {
+          if (entry.getSize() != -1) {
+            html.appendCell(FileUtils.byteCountToDisplaySize(entry.getSize()));
+          } else {
+            html.appendCell("n/a");
+          }
+        } else {
+          html.appendCell();
+        }
+        html.appendDateCell(entry.getTime())
+            .closeRow();
+      }
+    }
+    html.closeTable()
+        .closeBody()
+        .endDocument();
+
+    return util.applyCss(html.toString(), NAME, projectName);
+  }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css b/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css
index 8a83068..e7364b9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css
+++ b/src/main/java/com/googlesource/gerrit/plugins/xdocs/public/xdocs.css
@@ -18,10 +18,21 @@
   width: 100%;
 }
 
-.xdocs-header td {
+.xdocs-file-header td {
   padding-right: 5px;
 }
 
+.xdocs-icon-panel {
+  position: absolute;
+  right: 10px;
+  height: 16px;
+  line-height: 16px;
+}
+
+.xdocs-icon-panel img {
+  padding-right: 3px;
+}
+
 .xdocs-panel iframe {
   width: 100%;
   border-width: 1px;
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index f7b2632..7477b6e 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -82,6 +82,13 @@
     <td><a href="../../../Documentation/licenses.html#Apache2_0">Apache2.0</a></td>
     <td><a href="http://commons.apache.org">http://commons.apache.org</a></td>
   </tr>
+  <tr>
+    <td><tt>ZipFormatter</tt></td>
+    <td><tt>ZIP</tt></td>
+    <td>Formatter for zip files.</td>
+    <td></td>
+    <td></td>
+  </tr>
 </table>
 
 <a id="htmlDiff">
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 26d773e..2b12b55 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -96,7 +96,7 @@
 	Overrides the [global configuration of `inheritCss`](#formatterInheritCss)
 	for this formatter.
 
-	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`
+	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`, `ZIP`
 
 	Default: `true` (CSS is inherited)
 
@@ -111,7 +111,7 @@
 	Overrides the [global configuration of `cssTheme`](#formatterCssTheme)
 	for this formatter.
 
-	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`
+	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`, `ZIP`
 
 	By default not set.
 
@@ -137,6 +137,7 @@
 
 * `ASCIIDOCTOR`: `@PLUGIN@/asciidoctor.css`
 * `MARKDOWN`: `@PLUGIN@/markdown.css`
+* `ZIP`: `@PLUGIN@/zip.css`
 
 If link:inheritCss[inheritCss] is set to true custom CSS files are
 inherited from parent projects.
@@ -158,6 +159,10 @@
     mimeType = text/x-markdown
   [formatter "PLAIN_TEXT"]
     mimeType = text/plain
+  [formatter "ZIP"]
+    ext = jar
+    ext = war
+    ext = zip
 ```
 
 Supported formatters:
@@ -165,6 +170,7 @@
 * `ASCIIDOCTOR`
 * `MARKDOWN`
 * `PLAIN_TEXT`
+* `ZIP`
 
 <a id="formatterExt">
 formatter.<formatter>.ext
@@ -243,7 +249,7 @@
 
 	Can be overridden on [project-level](#inheritCss).
 
-	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`
+	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`, `ZIP`
 
 	Default: `true` (CSS is inherited)
 
@@ -256,7 +262,7 @@
 
 	Can be overridden on [project-level](#cssTheme).
 
-	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`
+	Supported for the following formatters: `ASCIIDOCTOR`, `MARKDOWN`, `ZIP`
 
 	By default not set.
 
@@ -295,3 +301,4 @@
 
 * `ASCIIDOCTOR`: `asciidoctor.css`
 * `MARKDOWN`: `markdown.css`
+* `ZIP`: `zip.css`