Diff options
Diffstat (limited to 'custom')
-rw-r--r--custom/extensions/about-render.lua124
1 file changed, 105 insertions, 19 deletions
diff --git a/custom/extensions/about-render.lua b/custom/extensions/about-render.lua
index d7753a3..040768c 100644
--- a/custom/extensions/about-render.lua
+++ b/custom/extensions/about-render.lua
@@ -81,11 +81,36 @@ local function split_lines(text)
end
end
+-- A pipe escaped with a backslash belongs to its cell, even inside a code
+-- span, so as on GitHub the backslash goes and the pipe stays. An even run of
+-- backslashes escapes only itself, so the pipe after it still divides.
local function split_cells(row)
- row = trim(row):gsub("^|", ""):gsub("|$", "")
- local cells = {}
- for cell in (row .. "|"):gmatch("(.-)|") do
- cells[#cells + 1] = trim(cell)
+ row = trim(row)
+ local cells, parts, start = {}, {}, 1
+ if row:sub(1, 1) == "|" then
+ start = 2
+ end
+ while true do
+ local bar = row:find("|", start, true)
+ if not bar then
+ break
+ end
+ local slashes = 0
+ while row:sub(bar - slashes - 1, bar - slashes - 1) == "\\" do
+ slashes = slashes + 1
+ end
+ if slashes % 2 == 1 then
+ parts[#parts + 1] = row:sub(start, bar - 2) .. "|"
+ else
+ parts[#parts + 1] = row:sub(start, bar - 1)
+ cells[#cells + 1] = trim(table.concat(parts))
+ parts = {}
+ end
+ start = bar + 1
+ end
+ if start <= #row or #parts > 0 then
+ parts[#parts + 1] = row:sub(start)
+ cells[#cells + 1] = trim(table.concat(parts))
end
return cells
end
@@ -96,10 +121,11 @@ end
-- so it is safe on Lua 5.1, where an embedded zero byte ends a pattern.
local function safe_url(url)
url = url:gsub("[%c%s]", "")
- -- A leading "//" or "/\" is scheme-relative, which a browser
- -- resolves to another origin, so it is not the local target the
- -- parser meant to allow.
- if url:sub(1, 2) == "//" or url:sub(1, 2) == "/\\" then
+ -- A leading "//" is scheme-relative, which a browser resolves to
+ -- another origin, and it reads a backslash there as a slash, so no
+ -- pair of the two is the local target the parser meant to allow.
+ local head = url:sub(1, 2):gsub("\\", "/")
+ if head == "//" then
return nil
end
local scheme = url:match("^(%a[%w%+%.%-]*):")
@@ -316,11 +342,10 @@ end
-- Markdown is a deliberate subset rather than CommonMark, leaving out
--- reference links, raw HTML passthrough, nested lists and setext headings,
--- and inline markup does not span the end of a line. Man rendering covers the
--- common macros and drops the rest. Both parse into a node tree before
--- anything is emitted, so a failed parse falls back to plain text without
--- leaving half a page behind.
+-- reference links, raw HTML passthrough and nested lists, and inline markup
+-- does not span the end of a line. Man rendering covers the common macros and
+-- drops the rest. Both parse into a node tree before anything is emitted, so a
+-- failed parse falls back to plain text without leaving half a page behind.
local render_markdown, render_man
@@ -428,6 +453,11 @@ if has_lpeg then
local lang_char = R("az", "AZ", "09") + S("_.+#-")
local heading_line = C(P("#") * P("#") ^ -5) * space ^ 1 * C(P(1) ^ 0)
+ -- A run of equals signs or dashes under a paragraph turns it into a
+ -- heading, as on GitHub, rather than reading as more of its text.
+ local setext_line = space ^ 0 * (C(P("=") ^ 1) + C(P("-") ^ 1))
+ * space ^ 0 * eol
+ local code_indent = P(" ") + P("\t")
local function thematic(mark)
return space ^ 0 * P(mark) * (space ^ 0 * P(mark)) ^ 2 * space ^ 0 * eol
end
@@ -454,6 +484,28 @@ if has_lpeg then
local function ordered(mark) return mark:match("%d") ~= nil end
+ local function follows_list(blocks)
+ local last = blocks[#blocks]
+ return last ~= nil and last.kind == "list"
+ end
+
+ -- An indented block runs on through blank lines, and up to four spaces
+ -- or one tab come off each of its lines.
+ local function continues_code(line)
+ return code_indent:match(line) or not line:find("%S")
+ end
+
+ local function unindent(line)
+ return line:match("^\t(.*)$") or line:match("^ ? ? ? ?(.*)$")
+ end
+
+ local function setext_level(line)
+ local run = setext_line:match(line)
+ if run then
+ return run:sub(1, 1) == "=" and 1 or 2
+ end
+ end
+
local function table_starts(lines, i)
return i < #lines and lines[i]:find("|", 1, true) ~= nil
and table_delimiter:match(lines[i + 1]) ~= nil
@@ -589,17 +641,51 @@ if has_lpeg then
ordered = is_ordered,
items = items,
}
+ -- Four spaces or a tab start a code block, except straight
+ -- after a list, where an indented paragraph more likely
+ -- carries an item on.
+ elseif code_indent:match(line) and not follows_list(blocks) then
+ local code = {}
+ while i <= n and continues_code(lines[i]) do
+ code[#code + 1] = unindent(lines[i])
+ i = i + 1
+ end
+ while code[#code]:match("^%s*$") do
+ code[#code] = nil
+ end
+ blocks[#blocks + 1] = {
+ kind = "code",
+ text = table.concat(code, "\n") .. "\n",
+ }
else
- local paragraph = { line }
+ local paragraph, level = { line }, nil
i = i + 1
- while i <= n and not ends_paragraph(lines, i) do
+ while i <= n do
+ level = setext_level(lines[i])
+ if level or ends_paragraph(lines, i)
+ or break_line:match(lines[i]) then
+ break
+ end
paragraph[#paragraph + 1] = lines[i]
i = i + 1
end
- blocks[#blocks + 1] = {
- kind = "para",
- lines = paragraph_lines(paragraph),
- }
+ if level then
+ local words = {}
+ for k, text in ipairs(paragraph) do
+ words[k] = trim(text)
+ end
+ blocks[#blocks + 1] = {
+ kind = "heading",
+ level = level,
+ kids = parse_inline(table.concat(words, " ")),
+ }
+ i = i + 1
+ else
+ blocks[#blocks + 1] = {
+ kind = "para",
+ lines = paragraph_lines(paragraph),
+ }
+ end
end
end
return blocks