Render blame in a table with server-side syntax highlighting This is likely the fastest pure HTML layout possible. The main downside is the lack of multi-line selection, but since this is available in the default blob view it's unlikely to be missed. The Chrome team's experience with ViewVC (see Issue 5) supports this. Change-Id: I9a67373f32c85a12516e92121a7ebdcbe07088ed
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java index 8259a9d..34c9016 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/blame/BlameServlet.java
@@ -15,11 +15,12 @@ package com.google.gitiles.blame; import static com.google.common.base.Preconditions.checkNotNull; + import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gitiles.BaseServlet; import com.google.gitiles.BlobSoyData; @@ -28,6 +29,8 @@ import com.google.gitiles.GitilesView; import com.google.gitiles.Renderer; import com.google.gitiles.ViewFilter; +import com.google.template.soy.data.SoyListData; +import com.google.template.soy.data.SoyMapData; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.http.server.ServletUtils; @@ -76,13 +79,13 @@ String title = "Blame - " + view.getPathPart(); Map<String, ?> blobData = new BlobSoyData(rw, view).toSoyData(view.getPathPart(), blobId); - if (blobData.get("data") != null) { + if (blobData.get("lines") != null) { List<Region> regions = cache.get(repo, commit, view.getPathPart()); if (regions.isEmpty()) { res.setStatus(SC_NOT_FOUND); return; } - GitDateFormatter df = new GitDateFormatter(Format.DEFAULT); + GitDateFormatter df = new GitDateFormatter(Format.ISO); renderHtml(req, res, "gitiles.blameDetail", ImmutableMap.of( "title", title, "breadcrumbs", view.getBreadcrumbs(), @@ -115,15 +118,28 @@ } } - private static List<Map<String, ?>> toSoyData(GitilesView view, ObjectReader reader, + private static final ImmutableList<String> CLASSES = ImmutableList.of("bg1", "bg2"); + private static final ImmutableList<SoyMapData> NULLS; + static { + ImmutableList.Builder<SoyMapData> nulls = ImmutableList.builder(); + for (String clazz : CLASSES) { + nulls.add(new SoyMapData("class", clazz)); + } + NULLS = nulls.build(); + } + + private static SoyListData toSoyData(GitilesView view, ObjectReader reader, List<Region> regions, GitDateFormatter df) throws IOException { Map<ObjectId, String> abbrevShas = Maps.newHashMap(); - List<Map<String, ?>> result = Lists.newArrayListWithCapacity(regions.size()); - for (Region r : regions) { + SoyListData result = new SoyListData(); + + for (int i = 0; i < regions.size(); i++) { + Region r = regions.get(i); + int c = i % CLASSES.size(); if (r.getSourceCommit() == null) { // JGit bug may fail to blame some regions. We should fix this // upstream, but handle it for now. - result.add(ImmutableMap.of("count", r.getCount())); + result.add(NULLS.get(c)); } else { String abbrevSha = abbrevShas.get(r.getSourceCommit()); if (abbrevSha == null) { @@ -144,9 +160,16 @@ .setPathPart(r.getSourcePath()) .toUrl()); e.put("author", CommitSoyData.toSoyData(r.getSourceAuthor(), df)); - e.put("count", r.getCount()); + e.put("class", CLASSES.get(c)); result.add(e); } + // Pad the list with null regions so we can iterate in parallel in the + // template. We can't do this by maintaining an index variable into the + // regions list because Soy {let} is an unmodifiable alias scoped to a + // single block. + for (int j = 0; j < r.getCount() - 1; j++) { + result.add(NULLS.get(c)); + } } return result; }
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css b/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css index f3a3a35..d0cc951 100644 --- a/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css +++ b/gitiles-servlet/src/main/resources/com/google/gitiles/static/gitiles.css
@@ -340,64 +340,44 @@ padding: 0; } #blame td { + line-height: 1.4; padding: 0; -} -#regions { - padding-top: 2px; - padding-right: 2px; - padding-bottom: 5px; - padding-left: 2px; + font-size: 8pt; white-space: nowrap; - list-style-type: none; - - /* Matching pre.git-blob below. */ +} +#blame .sha1, #blame .author, #blame .time { + /* TODO(dborowitz): Make 9pt values above more specific. */ + font-size: 8pt; +} +#blame .sha1 { font-family: monospace; - font-size: 8pt; - border-bottom: #ddd solid 1px; /* BORDER */ -} -#regions ul { - list-style-type: none; - margin-top: 0; - margin-bottom: 0; - padding-left: 0; -} -#blame .time, #blame .sha1 { - /* Smaller than SHORTLOG_SMALL_FONT_SIZE to match pre. */ - font-size: 8pt; } #blame .author { - padding-left: 0px; + padding-left: 3px; + padding-right: 3px; } -#blame pre.git-blob { - border-top: 0; +#blame .time { + padding-left: 5px; + padding-right: 3px; +} +#blame .linenum { + text-align: right; + padding-left: 5px; + padding-right: 5px; +} +#blame .regionLink { + padding-right: 3px; } -#regions li { - position: relative; +#blame tr.bg1 { + background: #fff; } -#regions li.line:hover { - background-color: #eee; -} -#regions li .regionMenu { - position: absolute; - top: 13px; - left: 0; - visibility: hidden; - padding-top: 4px; - padding-bottom: 4px; - padding-left: 8px; - padding-right: 8px; - - background-color: #eee; - font-size: 8pt; - font-family: arial,sans-serif; - z-index: 101; -} -#regions li:hover .regionMenu { - visibility: visible; +#blame tr.bg2 { + background: #f7f7f7; } -/* Override some styles from the default prettify.css. */ +/* Styles for pretty-print regions, including overriding some defaults from + * prettify.css. */ ol.prettyprint { border-top: #ddd solid 1px; /* BORDER */ @@ -406,10 +386,9 @@ border-right: none; padding-left: 5em; padding-bottom: 5px; +} +.prettyprint, #blame .linenum { font-family: monospace; font-size: 8pt; - white-space: pre; -} -pre.prettyprint ol { - color: grey; + white-space: pre !important; }
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy index 0118d92..942da68 100644 --- a/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy +++ b/gitiles-servlet/src/main/resources/com/google/gitiles/templates/BlameDetail.soy
@@ -22,17 +22,18 @@ * @param? headerVariant variant name for custom header. * @param breadcrumbs breadcrumbs for this page. * @param data blob data, matching the params for .blobBox. - * @param? regions for non-binary files, list of blame regions with the + * @param? regions for non-binary files, list of regions, one per line, with the * following keys: * abbrevSha: abbreviated SHA-1 of revision for this line; if missing, * assume blame info is missing. * author: author information with at least "name" and "relativeTime" * keys. - * relativeTime: relative time of the revision - * count: line count - * blameUrl: URL for a blame of this file at this commit - * commitUrl: URL for detail about the commit - * diffUrl: URL for a diff of this file at this commit + * time: time of the revision. + * blameUrl: URL for a blame of this file at this commit. + * commitUrl: URL for detail about the commit. + * diffUrl: URL for a diff of this file at this commit. + * class: class name for tr. + * All keys but "class" are optional. */ {template .blameDetail} {if $regions} @@ -48,39 +49,37 @@ {/call} <table id="blame"> - <tr> - <td> - <ul id="regions"> - {foreach $region in $regions} - <li class="line"> - {if $region.abbrevSha} - <a href="{$region.commitUrl}"> - <span class="sha1">{$region.abbrevSha}</span> - {sp}<span class="author">{$region.author.name}</span> - {sp}<span class="time">- {$region.author.relativeTime}</span> - </a> - <div class="regionMenu"> - [<a href="{$region.commitUrl}">commit</a>] - {sp}[<a href="{$region.diffUrl}">diff</a>] - {sp}[<a href="{$region.blameUrl}">blame</a>] - </div> - {else} - - {/if} - </li> - {for $i in range($region.count - 1)} - <li> </li> - {/for} + {let $regionIdx: 0 /} + {foreach $line in $data.lines} + {let $i: index($line) /} + {let $region: $regions[index($line)] /} + <tr class="{$region.class}"> + {if isNonnull($region.abbrevSha)} + <td class="region"> + <a href="{$region.commitUrl}"> + <span class="sha1">{$region.abbrevSha}</span> + <span class="author">{$region.author.name}</span> + </a> + </td> + <td class="region"> + <span class="time">{$region.author.time}</span> + </td> + <td class="regionLink"> + [<a href="{$region.diffUrl}">{msg desc="text for diff URL"}diff{/msg}</a>] + [<a href="{$region.blameUrl}">{msg desc="text for blame URL"}blame{/msg}</a>] + </td> + {else} + <td colspan="3"></td> + {/if} + <td class="linenum">{index($line) + 1}.</td> + <td class="prettyprint"> + {foreach $span in $line} + <span class="{$span.classes}">{$span.text}</span> {/foreach} - </ul> - </td> - <td> - {call .blobBox data="$data" /} - </td> - </tr> + </td> + </tr> + {/foreach} </table> - - </div> {else} {call .header data="all" /} {call .blobDetail data="$data" /}