aboutsummaryrefslogtreecommitdiff
path: root/scripts/text_utils.lua
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/text_utils.lua')
-rw-r--r--scripts/text_utils.lua154
1 files changed, 136 insertions, 18 deletions
diff --git a/scripts/text_utils.lua b/scripts/text_utils.lua
index ec0ccf668b..937408c546 100644
--- a/scripts/text_utils.lua
+++ b/scripts/text_utils.lua
@@ -7,12 +7,99 @@ local fmt = string.format
local INDENTATION = 4
+local NBSP = string.char(160)
+
local M = {}
local function contains(t, xs)
return vim.tbl_contains(xs, t)
end
+--- @param txt string
+--- @param srow integer
+--- @param scol integer
+--- @param erow? integer
+--- @param ecol? integer
+--- @return string
+local function slice_text(txt, srow, scol, erow, ecol)
+ local lines = vim.split(txt, '\n')
+
+ if srow == erow then
+ return lines[srow + 1]:sub(scol + 1, ecol)
+ end
+
+ if erow then
+ -- Trim the end
+ for _ = erow + 2, #lines do
+ table.remove(lines, #lines)
+ end
+ end
+
+ -- Trim the start
+ for _ = 1, srow do
+ table.remove(lines, 1)
+ end
+
+ lines[1] = lines[1]:sub(scol + 1)
+ lines[#lines] = lines[#lines]:sub(1, ecol)
+
+ return table.concat(lines, '\n')
+end
+
+--- @param text string
+--- @return nvim.text_utils.MDNode
+local function parse_md_inline(text)
+ local parser = vim.treesitter.languagetree.new(text, 'markdown_inline')
+ local root = parser:parse(true)[1]:root()
+
+ --- @param node TSNode
+ --- @return nvim.text_utils.MDNode?
+ local function extract(node)
+ local ntype = node:type()
+
+ if ntype:match('^%p$') then
+ return
+ end
+
+ --- @type table<any,any>
+ local ret = { type = ntype }
+ ret.text = vim.treesitter.get_node_text(node, text)
+
+ local row, col = 0, 0
+
+ for child, child_field in node:iter_children() do
+ local e = extract(child)
+ if e and ntype == 'inline' then
+ local srow, scol = child:start()
+ if (srow == row and scol > col) or srow > row then
+ local t = slice_text(ret.text, row, col, srow, scol)
+ if t and t ~= '' then
+ table.insert(ret, { type = 'text', j = true, text = t })
+ end
+ end
+ row, col = child:end_()
+ end
+
+ if child_field then
+ ret[child_field] = e
+ else
+ table.insert(ret, e)
+ end
+ end
+
+ if ntype == 'inline' and (row > 0 or col > 0) then
+ local t = slice_text(ret.text, row, col)
+ if t and t ~= '' then
+ table.insert(ret, { type = 'text', text = t })
+ end
+ end
+
+ return ret
+ end
+
+ return extract(root) or {}
+end
+
--- @param text string
--- @return nvim.text_utils.MDNode
local function parse_md(text)
@@ -47,6 +134,10 @@ local function parse_md(text)
ret.text = vim.treesitter.get_node_text(node, text)
end
+ if ntype == 'inline' then
+ ret = parse_md_inline(ret.text)
+ end
+
for child, child_field in node:iter_children() do
local e = extract(child)
if child_field then
@@ -101,20 +192,47 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
local add_tag = false
-- local add_tag = true
+ local ntype = node.type
+
if add_tag then
- parts[#parts + 1] = '<' .. node.type .. '>'
+ parts[#parts + 1] = '<' .. ntype .. '>'
end
- if node.type == 'paragraph' then
- local text = assert(node.text)
- text = text:gsub('(%s)%*(%w+)%*(%s)', '%1%2%3')
- text = text:gsub('(%s)_(%w+)_(%s)', '%1%2%3')
- text = text:gsub('\\|', '|')
- text = text:gsub('\\%*', '*')
- text = text:gsub('\\_', '_')
- parts[#parts + 1] = M.wrap(text, start_indent, indent, text_width)
+ if ntype == 'text' then
+ parts[#parts + 1] = node.text
+ elseif ntype == 'html_tag' then
+ error('html_tag: ' .. node.text)
+ elseif ntype == 'inline_link' then
+ vim.list_extend(parts, { '*', node[1].text, '*' })
+ elseif ntype == 'shortcut_link' then
+ if node[1].text:find('^<.*>$') then
+ parts[#parts + 1] = node[1].text
+ else
+ vim.list_extend(parts, { '|', node[1].text, '|' })
+ end
+ elseif ntype == 'backslash_escape' then
+ parts[#parts + 1] = node.text
+ elseif ntype == 'emphasis' then
+ parts[#parts + 1] = node.text:sub(2, -2)
+ elseif ntype == 'code_span' then
+ vim.list_extend(parts, { '`', node.text:sub(2, -2):gsub(' ', NBSP), '`' })
+ elseif ntype == 'inline' then
+ if #node == 0 then
+ local text = assert(node.text)
+ parts[#parts + 1] = M.wrap(text, start_indent, indent, text_width)
+ else
+ for _, child in ipairs(node) do
+ vim.list_extend(parts, render_md(child, start_indent, indent, text_width, level + 1))
+ end
+ end
+ elseif ntype == 'paragraph' then
+ local pparts = {}
+ for _, child in ipairs(node) do
+ vim.list_extend(pparts, render_md(child, start_indent, indent, text_width, level + 1))
+ end
+ parts[#parts + 1] = M.wrap(table.concat(pparts), start_indent, indent, text_width)
parts[#parts + 1] = '\n'
- elseif node.type == 'code_fence_content' then
+ elseif ntype == 'code_fence_content' then
local lines = vim.split(node.text:gsub('\n%s*$', ''), '\n')
local cindent = indent + INDENTATION
@@ -137,7 +255,7 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
end
parts[#parts + 1] = '\n'
end
- elseif node.type == 'fenced_code_block' then
+ elseif ntype == 'fenced_code_block' then
parts[#parts + 1] = '>'
for _, child in ipairs(node) do
if child.type == 'info_string' then
@@ -152,15 +270,15 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
end
end
parts[#parts + 1] = '<\n'
- elseif node.type == 'html_block' then
+ elseif ntype == 'html_block' then
local text = node.text:gsub('^<pre>help', '')
text = text:gsub('</pre>%s*$', '')
parts[#parts + 1] = text
- elseif node.type == 'list_marker_dot' then
+ elseif ntype == 'list_marker_dot' then
parts[#parts + 1] = node.text
- elseif contains(node.type, { 'list_marker_minus', 'list_marker_star' }) then
+ elseif contains(ntype, { 'list_marker_minus', 'list_marker_star' }) then
parts[#parts + 1] = '• '
- elseif node.type == 'list_item' then
+ elseif ntype == 'list_item' then
parts[#parts + 1] = string.rep(' ', indent)
local offset = node[1].type == 'list_marker_dot' and 3 or 2
for i, child in ipairs(node) do
@@ -180,7 +298,7 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
parts,
render_md(child, start_indent0, indent, text_width, level + 1, is_list)
)
- if node.type ~= 'list' and i ~= #node then
+ if ntype ~= 'list' and i ~= #node then
if (node[i + 1] or {}).type ~= 'list' then
parts[#parts + 1] = '\n'
end
@@ -189,7 +307,7 @@ local function render_md(node, start_indent, indent, text_width, level, is_list)
end
if add_tag then
- parts[#parts + 1] = '</' .. node.type .. '>'
+ parts[#parts + 1] = '</' .. ntype .. '>'
end
return parts
@@ -227,7 +345,7 @@ function M.md_to_vimdoc(text, start_indent, indent, text_width, is_list)
local parsed = parse_md(text .. '\n')
local ret = render_md(parsed, start_indent, indent, text_width, 0, is_list)
- local lines = vim.split(table.concat(ret), '\n')
+ local lines = vim.split(table.concat(ret):gsub(NBSP, ' '), '\n')
lines = vim.tbl_map(align_tags(text_width), lines)