Markdown: support column spans on |||---||| Change-Id: Ia960a3fa9c01cccc23c4e12baf0fef99c6dea84a
diff --git a/Documentation/markdown.md b/Documentation/markdown.md index 3a532c2..7ee70d9 100644 --- a/Documentation/markdown.md +++ b/Documentation/markdown.md
@@ -458,8 +458,9 @@ ### Column layout -Gitiles markdown includes support for up to 4 columns of text within -the width of the page. +Gitiles markdown includes support for up to 12 columns of text across +the width of the page. By default space is divided equally between +the columns. |||---||| #### Columns @@ -500,6 +501,31 @@ |||---||| ``` +Column spans can be specified on the first line as a comma separated +list. In the example below the first column is 4 wide or 4/12ths of +the page width, the second is 2 wide (or 2/12ths) and the final column +is 6 wide (6/12ths or 50%) of the page. + +``` +|||---||| 4,2,6 +``` + +An empty column can be inserted by prefixing its width with `:`, +for example shifting content onto the right by padding 6 columns +on the left: + +``` +|||---||| :6,3 +# Right +|||---||| +``` + +renders as: + +|||---||| :6,3 +# Right +|||---||| + ### HTML IFrame Although HTML is stripped the parser has special support for a limited
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/ColsNode.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/ColsNode.java index 00945ef..7c31b0f 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/ColsNode.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/ColsNode.java
@@ -14,9 +14,11 @@ package com.google.gitiles.doc; +import org.pegdown.ast.HeaderNode; import org.pegdown.ast.Node; import org.pegdown.ast.SuperNode; +import java.util.ArrayList; import java.util.List; /** @@ -25,12 +27,74 @@ * Each header within the layout creates a new column in the HTML. */ public class ColsNode extends SuperNode { - ColsNode(List<Node> children) { - super(children); + static final int GRID_WIDTH = 12; + + ColsNode(List<Column> spec, List<Node> children) { + super(wrap(spec, children)); } @Override public void accept(org.pegdown.ast.Visitor visitor) { ((Visitor) visitor).visit(this); } + + private static List<Node> wrap(List<Column> spec, List<Node> children) { + List<Column> columns = copyOf(spec); + splitChildren(columns, children); + + int remaining = GRID_WIDTH; + for (int i = 0; i < columns.size(); i++) { + Column col = columns.get(i); + if (col.span <= 0 || col.span > GRID_WIDTH) { + col.span = remaining / (columns.size() - i); + } + remaining = Math.max(0, remaining - col.span); + } + return asNodeList(columns); + } + + private static void splitChildren(List<Column> columns, List<Node> children) { + int idx = 0; + Column col = null; + for (Node n : children) { + if (col == null + || n instanceof HeaderNode + || n instanceof DivNode) { + for (;;) { + if (idx < columns.size()) { + col = columns.get(idx); + } else { + col = new Column(); + columns.add(col); + } + idx++; + if (!col.empty) { + break; + } + } + } + col.getChildren().add(n); + } + } + + private static <T> ArrayList<T> copyOf(List<T> in) { + return in != null && !in.isEmpty() + ? new ArrayList<>(in) + : new ArrayList<T>(); + } + + @SuppressWarnings("unchecked") + private static List<Node> asNodeList(List<? extends Node> columns) { + return (List<Node>) columns; + } + + static class Column extends SuperNode { + int span; + boolean empty; + + @Override + public void accept(org.pegdown.ast.Visitor visitor) { + ((Visitor) visitor).visit(this); + } + } }
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java index 5494cd3..9683472 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/GitilesMarkdown.java
@@ -17,7 +17,9 @@ import com.google.gitiles.GitilesView; import org.parboiled.Rule; +import org.parboiled.common.Factory; import org.parboiled.support.StringBuilderVar; +import org.parboiled.support.Var; import org.pegdown.Parser; import org.pegdown.ParsingTimeoutException; import org.pegdown.PegDownProcessor; @@ -29,6 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.List; /** Parses Gitiles extensions to markdown. */ @@ -144,21 +147,50 @@ sequence(string("aside"), push(match()))); } + @SuppressWarnings("unchecked") public Rule cols() { StringBuilderVar body = new StringBuilderVar(); return NodeSequence( - colsTag(), Newline(), + colsTag(), columnWidths(), Newline(), oneOrMore( testNot(colsTag(), Newline()), Line(body)), colsTag(), Newline(), - push(new ColsNode(parse(body)))); + push(new ColsNode((List<ColsNode.Column>) pop(), parse(body)))); } public Rule colsTag() { return string("|||---|||"); } + public Rule columnWidths() { + ListVar widths = new ListVar(); + return sequence( + zeroOrMore( + sequence( + Sp(), optional(ch(',')), Sp(), + columnWidth(widths))), + push(widths.get())); + } + + public Rule columnWidth(ListVar widths) { + StringBuilderVar s = new StringBuilderVar(); + return sequence( + optional(sequence(ch(':'), s.append(':'))), + oneOrMore(digit()), s.append(match()), + widths.get().add(parse(s.get().toString()))); + } + + static ColsNode.Column parse(String spec) { + ColsNode.Column c = new ColsNode.Column(); + if (spec.startsWith(":")) { + c.empty = true; + spec = spec.substring(1); + } + c.span = Integer.parseInt(spec, 10); + return c; + } + public List<Node> parse(StringBuilderVar body) { // The pegdown code doesn't provide enough visibility to directly // use its existing parsing rules. Recurse manually for inner text @@ -168,4 +200,16 @@ } return parser.parseMarkdown(body.getChars()).getChildren(); } + + public static class ListVar extends Var<List<Object>> { + @SuppressWarnings({"rawtypes", "unchecked"}) + public ListVar() { + super(new Factory() { + @Override + public Object create() { + return new ArrayList<>(); + } + }); + } + } }
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java index 62bc846..dd73ee4 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/MarkdownToHtml.java
@@ -119,21 +119,17 @@ @Override public void visit(ColsNode node) { html.open("div").attribute("class", "cols"); - boolean open = false; - for (Node n : node.getChildren()) { - if (n instanceof HeaderNode || n instanceof DivNode) { - if (open) { - html.close("div"); - } - html.open("div").attribute("class", "col-3"); - open = true; - } - n.accept(this); - } - if (open) { + visitChildren(node); + html.close("div"); + } + + @Override + public void visit(ColsNode.Column node) { + if (1 <= node.span && node.span <= ColsNode.GRID_WIDTH) { + html.open("div").attribute("class", "col-" + node.span); + visitChildren(node); html.close("div"); } - html.close("div"); } @Override
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java b/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java index e91e073..d8f269c 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/doc/Visitor.java
@@ -16,6 +16,7 @@ public interface Visitor extends org.pegdown.ast.Visitor { void visit(ColsNode node); + void visit(ColsNode.Column node); void visit(DivNode node); void visit(IframeNode node); void visit(TocNode node);
diff --git a/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css b/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css index 24b9b79..c397424 100644 --- a/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css +++ b/gitiles-servlet/src/main/resources/com/google/gitiles/static/doc.css
@@ -282,11 +282,23 @@ margin: 0 -1.533%; width: 103.067%; } -.col-3 { +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, +.col-7, .col-8, .col-9, .col-10, .col-11, .col-12 { float: left; margin: 0 1.488% 20px; } -.col-3 { width: 22.023%; } +.col-1 { width: 5.357%; } +.col-2 { width: 13.690%; } +.col-3 { width: 22.024%; } +.col-4 { width: 30.357%; } +.col-5 { width: 38.690%; } +.col-6 { width: 47.024%; } +.col-7 { width: 55.357%; } +.col-8 { width: 63.690%; } +.col-9 { width: 72.024%; } +.col-10 { width: 80.357%; } +.col-11 { width: 88.690%; } +.col-12 { width: 97.024%; } .cols hr { width: 80%; }