diff options
Diffstat (limited to 'scripts/text_utils.lua')
-rw-r--r-- | scripts/text_utils.lua | 154 |
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) |