diff options
authorBryce Kwon <bryce@brycekwon.com>
committerBryce Kwon <bryce@brycekwon.com>
commit
parent
tree
download
Show characters a browser breaks at as escapes
The HTML parser turns a carriage return with no line feed after it into a line feed, and WebKit starts a new line at U+2028 and U+2029, while git and the line numbers count line feeds alone, so each one set every later line of the tree and blame pages a line below its number. CSS Text makes vertical tab, form feed and next line forced breaks as well, though no engine honours that yet. The escapes follow gitweb, which shows control characters the same way.
Diffstat (limited to 'source/html.c')
-rw-r--r--source/html.c79
1 file changed, 79 insertions, 0 deletions
diff --git a/source/html.c b/source/html.c
index d1e28f8..d13ac5b 100644
--- a/source/html.c
+++ b/source/html.c
@@ -258,6 +258,85 @@ ssize_t html_ntxt(const char *txt, size_t len)
return left;
}
+/*
+ * git ends a line only at a line feed. A browser also starts one at a carriage
+ * return with no line feed after it, since the HTML parser turns that into a
+ * line feed, and may at the other characters Unicode makes mandatory breaks.
+ * Escaping those keeps one line on the page for every line git counts, so the
+ * numbers beside the text stay in step with it.
+ */
+const char *html_line_break_escape(const char *p, const char *end, size_t *width)
+{
+ const char *esc;
+
+ switch ((unsigned char)*p) {
+ case '\r':
+ // The parser reads CR LF as the single line feed git ends the
+ // line with.
+ if (p + 1 < end && p[1] == '\n')
+ return NULL;
+ *width = 1;
+ return "\\r";
+ case '\v':
+ *width = 1;
+ return "\\v";
+ case '\f':
+ *width = 1;
+ return "\\f";
+ case 0xc2:
+ if (end - p < 2 || memcmp(p, "\xc2\x85", 2))
+ return NULL;
+ *width = 2;
+ return "\\u0085";
+ case 0xe2:
+ if (end - p >= 3 && !memcmp(p, "\xe2\x80\xa8", 3))
+ esc = "\\u2028";
+ else if (end - p >= 3 && !memcmp(p, "\xe2\x80\xa9", 3))
+ esc = "\\u2029";
+ else
+ return NULL;
+ *width = 3;
+ return esc;
+ default:
+ return NULL;
+ }
+}
+
+void html_blob_txt(const char *txt, size_t len)
+{
+ const char *end = txt + len;
+ const char *p;
+
+ for (p = txt; p < end; p++) {
+ const char *entity = NULL, *esc = NULL;
+ size_t width = 1;
+
+ if (*p == '<')
+ entity = "&lt;";
+ else if (*p == '>')
+ entity = "&gt;";
+ else if (*p == '&')
+ entity = "&amp;";
+ else
+ esc = html_line_break_escape(p, end, &width);
+ if (!entity && !esc)
+ continue;
+ html_raw(txt, p - txt);
+ if (entity) {
+ html(entity);
+ } else {
+ // The span lets a stylesheet tell the escape from the
+ // same characters typed into the file.
+ html("<span class='escape'>");
+ html(esc);
+ html("</span>");
+ }
+ p += width - 1;
+ txt = p + 1;
+ }
+ html_raw(txt, end - txt);
+}
+
void html_attrf(const char *format, ...)
{
va_list args;