// Copyright 2012 Google Inc. 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,
// 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.gitiles;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;

import org.apache.commons.lang3.StringEscapeUtils;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.FileHeader.PatchType;
import org.eclipse.jgit.util.RawParseUtils;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;

/** Formats a unified format patch as UTF-8 encoded HTML. */
final class HtmlDiffFormatter extends DiffFormatter {
  private static final byte[] DIFF_BEGIN = "<pre class=\"diff-unified\">".getBytes(Charsets.UTF_8);
  private static final byte[] DIFF_END = "</pre>".getBytes(Charsets.UTF_8);

  private static final byte[] HUNK_BEGIN = "<span class=\"h\">".getBytes(Charsets.UTF_8);
  private static final byte[] HUNK_END = "</span>".getBytes(Charsets.UTF_8);

  private static final byte[] LINE_INSERT_BEGIN = "<span class=\"i\">".getBytes(Charsets.UTF_8);
  private static final byte[] LINE_DELETE_BEGIN = "<span class=\"d\">".getBytes(Charsets.UTF_8);
  private static final byte[] LINE_CHANGE_BEGIN = "<span class=\"c\">".getBytes(Charsets.UTF_8);
  private static final byte[] LINE_END = "</span>\n".getBytes(Charsets.UTF_8);

  private final Renderer renderer;
  private int fileIndex;

  HtmlDiffFormatter(Renderer renderer, OutputStream out) {
    super(out);
    this.renderer = checkNotNull(renderer, "renderer");
  }

  @Override
  public void format(List<? extends DiffEntry> entries) throws IOException {
    for (fileIndex = 0; fileIndex < entries.size(); fileIndex++) {
      format(entries.get(fileIndex));
    }
  }

  @Override
  public void format(FileHeader hdr, RawText a, RawText b)
      throws IOException {
    int start = hdr.getStartOffset();
    int end = hdr.getEndOffset();
    if (!hdr.getHunks().isEmpty()) {
      end = hdr.getHunks().get(0).getStartOffset();
    }
    renderHeader(RawParseUtils.decode(hdr.getBuffer(), start, end));

    if (hdr.getPatchType() == PatchType.UNIFIED) {
      getOutputStream().write(DIFF_BEGIN);
      format(hdr.toEditList(), a, b);
      getOutputStream().write(DIFF_END);
    }
  }

  private void renderHeader(String header)
      throws IOException {
    int lf = header.indexOf('\n');
    String first;
    String rest;
    if (0 <= lf) {
      first = header.substring(0, lf);
      rest = header.substring(lf + 1);
    } else {
      first = header;
      rest = "";
    }
    getOutputStream().write(renderer.newRenderer("gitiles.diffHeader")
        .setData(ImmutableMap.of("first", first, "rest", rest, "fileIndex", fileIndex))
        .render()
        .getBytes(Charsets.UTF_8));
  }

  @Override
  protected void writeHunkHeader(int aStartLine, int aEndLine,
      int bStartLine, int bEndLine) throws IOException {
    getOutputStream().write(HUNK_BEGIN);
    // TODO(sop): If hunk header starts including method names, escape it.
    super.writeHunkHeader(aStartLine, aEndLine, bStartLine, bEndLine);
    getOutputStream().write(HUNK_END);
  }

  @Override
  protected void writeLine(char prefix, RawText text, int cur)
      throws IOException {
    // Manually render each line, rather than invoke a Soy template. This method
    // can be called thousands of times in a single request. Avoid unnecessary
    // overheads by formatting as-is.
    OutputStream out = getOutputStream();
    switch (prefix) {
      case '+':
        out.write(LINE_INSERT_BEGIN);
        break;
      case '-':
        out.write(LINE_DELETE_BEGIN);
        break;
      case ' ':
      default:
        out.write(LINE_CHANGE_BEGIN);
        break;
    }
    out.write(StringEscapeUtils.escapeHtml4(text.getString(cur)).getBytes(Charsets.UTF_8));
    out.write(LINE_END);
  }
}

@@ -4395,24 +4395,47 @@
 reak;%0A    %7D%0A
+    out.write(prefix);%0A
     out.writ

Add missing +/-/' ' on diff views