mirror of
https://github.com/nvim-neotest/nvim-nio.git
synced 2024-09-16 14:24:04 +02:00
858 lines
24 KiB
Lua
858 lines
24 KiB
Lua
-- TODO: A lot of this is private code from minidoc, which could be removed if made public
|
||
|
||
local minidoc = require("mini.doc")
|
||
local util = require("nio.util")
|
||
|
||
local H = {}
|
||
--stylua: ignore start
|
||
H.pattern_sets = {
|
||
-- Patterns for working with afterlines. At the moment deliberately crafted
|
||
-- to work only on first line without indent.
|
||
|
||
-- Determine if line is a function definition. Captures function name and
|
||
-- arguments. For reference see '2.5.9 – Function Definitions' in Lua manual.
|
||
afterline_fundef = {
|
||
'^function%s+(%S-)(%b())', -- Regular definition
|
||
'^local%s+function%s+(%S-)(%b())', -- Local definition
|
||
'^(%S+)%s*=%s*function(%b())', -- Regular assignment
|
||
'^(%S+)%s*=%s*nio.create%(function(%b())', -- Regular assignment
|
||
'^local%s+(%S+)%s*=%s*function(%b())', -- Local assignment
|
||
},
|
||
-- Determine if line is a general assignment
|
||
afterline_assign = {
|
||
'^(%S-)%s*=', -- General assignment
|
||
'^local%s+(%S-)%s*=', -- Local assignment
|
||
},
|
||
-- Patterns to work with type descriptions
|
||
-- (see https://github.com/sumneko/lua-language-server/wiki/EmmyLua-Annotations#types-and-type)
|
||
types = {
|
||
'table%b<>',
|
||
'fun%b(): %S+', 'fun%b()', 'async fun%b(): %S+', 'async fun%b()',
|
||
'nil', 'any', 'boolean', 'string', 'number', 'integer', 'function', 'table', 'thread', 'userdata', 'lightuserdata',
|
||
'%.%.%.',
|
||
"%S+",
|
||
|
||
},
|
||
}
|
||
|
||
|
||
H.apply_config = function(config)
|
||
MiniDoc.config = config
|
||
end
|
||
|
||
H.is_disabled = function()
|
||
return vim.g.minidoc_disable == true or vim.b.minidoc_disable == true
|
||
end
|
||
|
||
H.get_config = function(config)
|
||
return vim.tbl_deep_extend("force", MiniDoc.config, vim.b.minidoc_config or {}, config or {})
|
||
end
|
||
|
||
-- Work with project specific script ==========================================
|
||
H.execute_project_script = function(input, output, config)
|
||
-- Don't process script if there are more than one active `generate` calls
|
||
if H.generate_is_active then
|
||
return
|
||
end
|
||
|
||
-- Don't process script if at least one argument is not default
|
||
if not (input == nil and output == nil and config == nil) then
|
||
return
|
||
end
|
||
|
||
-- Store information
|
||
local global_config_cache = vim.deepcopy(MiniDoc.config)
|
||
local local_config_cache = vim.b.minidoc_config
|
||
|
||
-- Pass information to a possible `generate()` call inside script
|
||
H.generate_is_active = true
|
||
H.generate_recent_output = nil
|
||
|
||
-- Execute script
|
||
local success = pcall(vim.cmd, "luafile " .. H.get_config(config).script_path)
|
||
|
||
-- Restore information
|
||
MiniDoc.config = global_config_cache
|
||
vim.b.minidoc_config = local_config_cache
|
||
H.generate_is_active = nil
|
||
|
||
return success
|
||
end
|
||
|
||
-- Default documentation targets ----------------------------------------------
|
||
H.default_input = function()
|
||
-- Search in current and recursively in other directories for files with
|
||
-- 'lua' extension
|
||
local res = {}
|
||
for _, dir_glob in ipairs({ ".", "lua/**", "after/**", "colors/**" }) do
|
||
local files = vim.fn.globpath(dir_glob, "*.lua", false, true)
|
||
|
||
-- Use full paths
|
||
files = vim.tbl_map(function(x)
|
||
return vim.fn.fnamemodify(x, ":p")
|
||
end, files)
|
||
|
||
-- Put 'init.lua' first among files from same directory
|
||
table.sort(files, function(a, b)
|
||
if vim.fn.fnamemodify(a, ":h") == vim.fn.fnamemodify(b, ":h") then
|
||
if vim.fn.fnamemodify(a, ":t") == "init.lua" then
|
||
return true
|
||
end
|
||
if vim.fn.fnamemodify(b, ":t") == "init.lua" then
|
||
return false
|
||
end
|
||
end
|
||
|
||
return a < b
|
||
end)
|
||
table.insert(res, files)
|
||
end
|
||
|
||
return util.tbl_flatten(res)
|
||
end
|
||
|
||
-- Parsing --------------------------------------------------------------------
|
||
H.lines_to_block_arr = function(lines, config)
|
||
local matched_prev, matched_cur
|
||
|
||
local res = {}
|
||
local block_raw = { annotation = {}, section_id = {}, afterlines = {}, line_begin = 1 }
|
||
|
||
for i, l in ipairs(lines) do
|
||
local from, to, section_id = config.annotation_extractor(l)
|
||
matched_prev, matched_cur = matched_cur, from ~= nil
|
||
|
||
if matched_cur then
|
||
if not matched_prev then
|
||
-- Finish current block
|
||
block_raw.line_end = i - 1
|
||
table.insert(res, H.raw_block_to_block(block_raw, config))
|
||
|
||
-- Start new block
|
||
block_raw = { annotation = {}, section_id = {}, afterlines = {}, line_begin = i }
|
||
end
|
||
|
||
-- Add annotation line without matched annotation pattern
|
||
table.insert(block_raw.annotation, ("%s%s"):format(l:sub(0, from - 1), l:sub(to + 1)))
|
||
|
||
-- Add section id (it is empty string in case of no section id capture)
|
||
table.insert(block_raw.section_id, section_id or "")
|
||
else
|
||
-- Add afterline
|
||
table.insert(block_raw.afterlines, l)
|
||
end
|
||
end
|
||
block_raw.line_end = #lines
|
||
table.insert(res, H.raw_block_to_block(block_raw, config))
|
||
|
||
return res
|
||
end
|
||
|
||
-- Raw block structure is an intermediate step added for convenience. It is
|
||
-- a table with the following keys:
|
||
-- - `annotation` - lines (after removing matched annotation pattern) that were
|
||
-- parsed as annotation.
|
||
-- - `section_id` - array with length equal to `annotation` length with strings
|
||
-- captured as section id. Empty string of no section id was captured.
|
||
-- - Everything else is used as block info (like `afterlines`, etc.).
|
||
H.raw_block_to_block = function(block_raw, config)
|
||
if #block_raw.annotation == 0 and #block_raw.afterlines == 0 then
|
||
return nil
|
||
end
|
||
|
||
local block = H.new_struct("block", {
|
||
afterlines = block_raw.afterlines,
|
||
line_begin = block_raw.line_begin,
|
||
line_end = block_raw.line_end,
|
||
})
|
||
local block_begin = block.info.line_begin
|
||
|
||
-- Parse raw block annotation lines from top to bottom. New section starts
|
||
-- when section id is detected in that line.
|
||
local section_cur = H.new_struct(
|
||
"section",
|
||
{ id = config.default_section_id, line_begin = block_begin }
|
||
)
|
||
|
||
for i, annotation_line in ipairs(block_raw.annotation) do
|
||
local id = block_raw.section_id[i]
|
||
if id ~= "" then
|
||
-- Finish current section
|
||
if #section_cur > 0 then
|
||
section_cur.info.line_end = block_begin + i - 2
|
||
block:insert(section_cur)
|
||
end
|
||
|
||
-- Start new section
|
||
section_cur = H.new_struct("section", { id = id, line_begin = block_begin + i - 1 })
|
||
end
|
||
|
||
section_cur:insert(annotation_line)
|
||
end
|
||
|
||
if #section_cur > 0 then
|
||
section_cur.info.line_end = block_begin + #block_raw.annotation - 1
|
||
block:insert(section_cur)
|
||
end
|
||
|
||
return block
|
||
end
|
||
|
||
-- Hooks ----------------------------------------------------------------------
|
||
H.apply_structure_hooks = function(doc, hooks)
|
||
for _, file in ipairs(doc) do
|
||
for _, block in ipairs(file) do
|
||
hooks.block_pre(block)
|
||
|
||
for _, section in ipairs(block) do
|
||
hooks.section_pre(section)
|
||
|
||
local hook = hooks.sections[section.info.id]
|
||
if hook ~= nil then
|
||
hook(section)
|
||
end
|
||
|
||
hooks.section_post(section)
|
||
end
|
||
|
||
hooks.block_post(block)
|
||
end
|
||
|
||
hooks.file(file)
|
||
end
|
||
|
||
hooks.doc(doc)
|
||
end
|
||
|
||
H.alias_register = function(s)
|
||
if #s == 0 then
|
||
return
|
||
end
|
||
|
||
-- Remove first word (with bits of surrounding whitespace) while capturing it
|
||
local alias_name
|
||
s[1] = s[1]:gsub("%s*(%S+) ?", function(x)
|
||
alias_name = x
|
||
return ""
|
||
end, 1)
|
||
if alias_name == nil then
|
||
return
|
||
end
|
||
|
||
MiniDoc.current.aliases = MiniDoc.current.aliases or {}
|
||
MiniDoc.current.aliases[alias_name] = table.concat(s, "\n")
|
||
end
|
||
|
||
H.alias_replace = function(s)
|
||
if MiniDoc.current.aliases == nil then
|
||
return
|
||
end
|
||
|
||
for i, _ in ipairs(s) do
|
||
for alias_name, alias_desc in pairs(MiniDoc.current.aliases) do
|
||
-- Escape special characters. This is done here and not while registering
|
||
-- alias to allow user to refer to aliases by its original name.
|
||
-- Store escaped words in separate variables because `vim.pesc()` returns
|
||
-- two values which might conflict if outputs are used as arguments.
|
||
local name_escaped = vim.pesc(alias_name)
|
||
local desc_escaped = vim.pesc(alias_desc)
|
||
s[i] = s[i]:gsub(name_escaped, desc_escaped)
|
||
end
|
||
end
|
||
end
|
||
|
||
H.toc_register = function(s)
|
||
MiniDoc.current.toc = MiniDoc.current.toc or {}
|
||
table.insert(MiniDoc.current.toc, s)
|
||
end
|
||
|
||
H.toc_insert = function(s)
|
||
if MiniDoc.current.toc == nil then
|
||
return
|
||
end
|
||
|
||
-- Render table of contents
|
||
local toc_lines = {}
|
||
for _, toc_entry in ipairs(MiniDoc.current.toc) do
|
||
local _, tag_section = toc_entry.parent:has_descendant(function(x)
|
||
return type(x) == "table" and x.type == "section" and x.info.id == "@tag"
|
||
end)
|
||
tag_section = tag_section or {}
|
||
|
||
local lines = {}
|
||
for i = 1, math.max(#toc_entry, #tag_section) do
|
||
local left = toc_entry[i] or ""
|
||
-- Use tag refernce instead of tag enclosure
|
||
local right = string.match(tag_section[i], "%*.*%*"):gsub("%*", "|")
|
||
-- local right = vim.trim((tag_section[i] or ""):gsub("%*", "|"))
|
||
-- Add visual line only at first entry (while not adding trailing space)
|
||
local filler = i == 1 and "." or (right == "" and "" or " ")
|
||
-- Make padding of 2 spaces at both left and right
|
||
local n_filler = math.max(74 - H.visual_text_width(left) - H.visual_text_width(right), 3)
|
||
table.insert(lines, (" %s%s%s"):format(left, filler:rep(n_filler), right))
|
||
end
|
||
|
||
table.insert(toc_lines, lines)
|
||
|
||
-- Don't show `toc_entry` lines in output
|
||
toc_entry:clear_lines()
|
||
end
|
||
|
||
for _, l in ipairs(util.tbl_flatten(toc_lines)) do
|
||
s:insert(l)
|
||
end
|
||
end
|
||
|
||
H.add_section_heading = function(s, heading)
|
||
if #s == 0 or s.type ~= "section" then
|
||
return
|
||
end
|
||
|
||
-- Add heading
|
||
s:insert(1, ("%s~"):format(heading))
|
||
end
|
||
|
||
H.enclose_var_name = function(s)
|
||
if #s == 0 or s.type ~= "section" then
|
||
return
|
||
end
|
||
|
||
s[1] = s[1]:gsub("(%S+)", "{%1}", 1)
|
||
end
|
||
|
||
---@param init number Start of searching for first "type-like" string. It is
|
||
--- needed to not detect type early. Like in `@param a_function function`.
|
||
---@private
|
||
H.enclose_type = function(s, enclosure, init)
|
||
if #s == 0 or s.type ~= "section" then
|
||
return
|
||
end
|
||
enclosure = enclosure or "`%(%1%)`"
|
||
init = init or 1
|
||
|
||
local cur_type = H.match_first_pattern(s[1], H.pattern_sets["types"], init)
|
||
if #cur_type == 0 then
|
||
return
|
||
end
|
||
|
||
-- Add `%S*` to front and back of found pattern to support their combination
|
||
-- with `|`. Also allows using `[]` and `?` prefixes.
|
||
local type_pattern = ("(%%S*%s%%S*)"):format(vim.pesc(cur_type[1]))
|
||
|
||
-- Avoid replacing possible match before `init`
|
||
local l_start = s[1]:sub(1, init - 1)
|
||
local l_end = s[1]:sub(init):gsub(type_pattern, enclosure, 1)
|
||
s[1] = ("%s%s"):format(l_start, l_end)
|
||
end
|
||
|
||
-- Infer data from afterlines -------------------------------------------------
|
||
H.infer_header = function(b)
|
||
local has_signature = b:has_descendant(function(x)
|
||
return type(x) == "table" and x.type == "section" and x.info.id == "@signature"
|
||
end)
|
||
local has_tag = b:has_descendant(function(x)
|
||
return type(x) == "table" and x.type == "section" and x.info.id == "@tag"
|
||
end)
|
||
|
||
if has_signature and has_tag then
|
||
return
|
||
end
|
||
|
||
local l_all = table.concat(b.info.afterlines, " ")
|
||
local tag, signature
|
||
|
||
-- Try function definition
|
||
local fun_capture = H.match_first_pattern(l_all, H.pattern_sets["afterline_fundef"])
|
||
if #fun_capture > 0 then
|
||
tag = tag or ("%s()"):format(fun_capture[1])
|
||
signature = signature or ("%s%s"):format(fun_capture[1], fun_capture[2])
|
||
end
|
||
|
||
-- Try general assignment
|
||
local assign_capture = H.match_first_pattern(l_all, H.pattern_sets["afterline_assign"])
|
||
if #assign_capture > 0 then
|
||
tag = tag or assign_capture[1]
|
||
signature = signature or assign_capture[1]
|
||
end
|
||
|
||
if tag ~= nil then
|
||
-- First insert signature (so that it will appear after tag section)
|
||
if not has_signature then
|
||
b:insert(1, H.as_struct({ signature }, "section", { id = "@signature" }))
|
||
end
|
||
|
||
-- Insert tag
|
||
if not has_tag then
|
||
b:insert(1, H.as_struct({ tag }, "section", { id = "@tag" }))
|
||
end
|
||
end
|
||
end
|
||
|
||
function H.is_module(name)
|
||
if string.find(name, "%(") then
|
||
return false
|
||
end
|
||
if string.find(name, "[A-Z]") then
|
||
return false
|
||
end
|
||
return true
|
||
end
|
||
|
||
H.format_signature = function(line)
|
||
-- Try capture function signature
|
||
local name, args = line:match("(%S-)(%b())")
|
||
|
||
|
||
-- Otherwise pick first word
|
||
name = name or line:match("(%S+)")
|
||
if not args and H.is_module(name) then
|
||
return ""
|
||
end
|
||
local name_elems = vim.split(name, ".", { plain = true })
|
||
name = name_elems[#name_elems]
|
||
|
||
if not name then
|
||
return ""
|
||
end
|
||
|
||
-- Tidy arguments
|
||
if args and args ~= "()" then
|
||
local arg_parts = vim.split(args:sub(2, -2), ",")
|
||
local arg_list = {}
|
||
for _, a in ipairs(arg_parts) do
|
||
-- Enclose argument in `{}` while controlling whitespace
|
||
table.insert(arg_list, ("{%s}"):format(vim.trim(a)))
|
||
end
|
||
args = ("(%s)"):format(table.concat(arg_list, ", "))
|
||
end
|
||
|
||
return ("`%s`%s"):format(name, args or "")
|
||
end
|
||
|
||
-- Work with structures -------------------------------------------------------
|
||
-- Constructor
|
||
H.new_struct = function(struct_type, info)
|
||
local output = {
|
||
info = info or {},
|
||
type = struct_type,
|
||
}
|
||
|
||
output.insert = function(self, index, child)
|
||
-- Allow both `x:insert(child)` and `x:insert(1, child)`
|
||
if child == nil then
|
||
child, index = index, #self + 1
|
||
end
|
||
|
||
if type(child) == "table" then
|
||
child.parent = self
|
||
child.parent_index = index
|
||
end
|
||
|
||
table.insert(self, index, child)
|
||
|
||
H.sync_parent_index(self)
|
||
end
|
||
|
||
output.remove = function(self, index)
|
||
index = index or #self
|
||
table.remove(self, index)
|
||
|
||
H.sync_parent_index(self)
|
||
end
|
||
|
||
output.has_descendant = function(self, predicate)
|
||
local bool_res, descendant = false, nil
|
||
H.apply_recursively(function(x)
|
||
if not bool_res and predicate(x) then
|
||
bool_res = true
|
||
descendant = x
|
||
end
|
||
end, self)
|
||
return bool_res, descendant
|
||
end
|
||
|
||
output.has_lines = function(self)
|
||
return self:has_descendant(function(x)
|
||
return type(x) == "string"
|
||
end)
|
||
end
|
||
|
||
output.clear_lines = function(self)
|
||
for i, x in ipairs(self) do
|
||
if type(x) == "string" then
|
||
self[i] = nil
|
||
else
|
||
x:clear_lines()
|
||
end
|
||
end
|
||
end
|
||
|
||
return output
|
||
end
|
||
|
||
H.sync_parent_index = function(x)
|
||
for i, _ in ipairs(x) do
|
||
if type(x[i]) == "table" then
|
||
x[i].parent_index = i
|
||
end
|
||
end
|
||
return x
|
||
end
|
||
|
||
-- Converter (this ensures that children have proper parent-related data)
|
||
H.as_struct = function(array, struct_type, info)
|
||
-- Make default info `info` for cases when structure is created manually
|
||
local default_info = ({
|
||
section = { id = "@text", line_begin = -1, line_end = -1 },
|
||
block = { afterlines = {}, line_begin = -1, line_end = -1 },
|
||
file = { path = "" },
|
||
doc = { input = {}, output = "", config = H.get_config() },
|
||
})[struct_type]
|
||
info = vim.tbl_deep_extend("force", default_info, info or {})
|
||
|
||
local res = H.new_struct(struct_type, info)
|
||
for _, x in ipairs(array) do
|
||
res:insert(x)
|
||
end
|
||
return res
|
||
end
|
||
|
||
-- Work with text -------------------------------------------------------------
|
||
H.ensure_indent = function(text, n_indent_target)
|
||
local lines = vim.split(text, "\n")
|
||
local n_indent, n_indent_cur = math.huge, math.huge
|
||
|
||
-- Find number of characters in indent
|
||
for _, l in ipairs(lines) do
|
||
-- Update lines indent: minimum of all indents except empty lines
|
||
if n_indent > 0 then
|
||
_, n_indent_cur = l:find("^%s*")
|
||
-- Condition "current n-indent equals line length" detects empty line
|
||
if (n_indent_cur < n_indent) and (n_indent_cur < l:len()) then
|
||
n_indent = n_indent_cur
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Ensure indent
|
||
local indent = string.rep(" ", n_indent_target)
|
||
for i, l in ipairs(lines) do
|
||
if l ~= "" then
|
||
lines[i] = indent .. l:sub(n_indent + 1)
|
||
end
|
||
end
|
||
|
||
return table.concat(lines, "\n")
|
||
end
|
||
|
||
H.align_text = function(text, width, direction)
|
||
if type(text) ~= "string" then
|
||
return
|
||
end
|
||
text = vim.trim(text)
|
||
width = width or 78
|
||
direction = direction or "left"
|
||
|
||
-- Don't do anything if aligning left or line is a whitespace
|
||
if direction == "left" or text:find("^%s*$") then
|
||
return text
|
||
end
|
||
|
||
local n_left = math.max(0, 78 - H.visual_text_width(text))
|
||
if direction == "center" then
|
||
n_left = math.floor(0.5 * n_left)
|
||
end
|
||
|
||
return (" "):rep(n_left) .. text
|
||
end
|
||
|
||
H.visual_text_width = function(text)
|
||
-- Ignore concealed characters (usually "invisible" in 'help' filetype)
|
||
local _, n_concealed_chars = text:gsub("([*|`])", "%1")
|
||
return vim.fn.strdisplaywidth(text) - n_concealed_chars
|
||
end
|
||
|
||
--- Return earliest match among many patterns
|
||
---
|
||
--- Logic here is to test among several patterns. If several got a match,
|
||
--- return one with earliest match.
|
||
---
|
||
---@private
|
||
H.match_first_pattern = function(text, pattern_set, init)
|
||
local start_tbl = vim.tbl_map(function(pattern)
|
||
return text:find(pattern, init) or math.huge
|
||
end, pattern_set)
|
||
|
||
local min_start, min_id = math.huge, nil
|
||
for id, st in ipairs(start_tbl) do
|
||
if st < min_start then
|
||
min_start, min_id = st, id
|
||
end
|
||
end
|
||
|
||
if min_id == nil then
|
||
return {}
|
||
end
|
||
return { text:match(pattern_set[min_id], init) }
|
||
end
|
||
|
||
-- Utilities ------------------------------------------------------------------
|
||
H.apply_recursively = function(f, x, used)
|
||
used = used or {}
|
||
if used[x] then
|
||
return
|
||
end
|
||
f(x)
|
||
used[x] = true
|
||
|
||
if type(x) == "table" then
|
||
for _, t in ipairs(x) do
|
||
H.apply_recursively(f, t, used)
|
||
end
|
||
end
|
||
end
|
||
|
||
H.collect_strings = function(x)
|
||
local res = {}
|
||
H.apply_recursively(function(y)
|
||
if type(y) == "string" then
|
||
-- Allow `\n` in strings
|
||
table.insert(res, vim.split(y, "\n"))
|
||
end
|
||
end, x)
|
||
-- Flatten to only have strings and not table of strings (from `vim.split`)
|
||
return util.tbl_flatten(res)
|
||
end
|
||
|
||
H.file_read = function(path)
|
||
local file = assert(io.open(path))
|
||
local contents = file:read("*all")
|
||
file:close()
|
||
|
||
return vim.split(contents, "\n")
|
||
end
|
||
|
||
H.file_write = function(path, lines)
|
||
-- Ensure target directory exists
|
||
local dir = vim.fn.fnamemodify(path, ":h")
|
||
vim.fn.mkdir(dir, "p")
|
||
|
||
-- Write to file
|
||
vim.fn.writefile(lines, path, "b")
|
||
end
|
||
|
||
H.full_path = function(path)
|
||
return vim.fn.resolve(vim.fn.fnamemodify(path, ":p"))
|
||
end
|
||
|
||
H.message = function(msg)
|
||
vim.cmd("echomsg " .. vim.inspect("(mini.doc) " .. msg))
|
||
end
|
||
|
||
local function wrap(str, limit, indent, indent1)
|
||
indent = indent or ""
|
||
indent1 = indent1 or indent
|
||
limit = limit or 79
|
||
local here = 1 - #indent1
|
||
local wrapped = indent1
|
||
.. str:gsub("(%s+)()(%S+)()", function(sp, st, word, fi)
|
||
local delta = 0
|
||
word:gsub("@([@%a])", function(c)
|
||
if c == "@" then
|
||
delta = delta + 1
|
||
elseif c == "x" then
|
||
delta = delta + 5
|
||
else
|
||
delta = delta + 2
|
||
end
|
||
end)
|
||
here = here + delta
|
||
if fi - here > limit then
|
||
here = st - #indent + delta
|
||
return "\n" .. indent .. word
|
||
end
|
||
end)
|
||
|
||
return vim.split(wrapped, "\n")
|
||
end
|
||
|
||
|
||
local function create_config(module, header)
|
||
return {
|
||
hooks = vim.tbl_extend("force", minidoc.default_hooks, {
|
||
block_pre = function(b)
|
||
-- Infer metadata based on afterlines
|
||
if b:has_lines() and #b.info.afterlines > 0 then H.infer_header(b) end
|
||
end,
|
||
section_post = function(section)
|
||
for i, line in ipairs(section) do
|
||
if type(line) == "string" then
|
||
if string.find(line, "^```") then
|
||
string.gsub(line, "```(.*)", function(lang)
|
||
section[i] = lang == "" and "<" or (">%s"):format(lang)
|
||
end)
|
||
end
|
||
end
|
||
end
|
||
end,
|
||
block_post = function(b)
|
||
if not b:has_lines() then return end
|
||
|
||
local found_param, found_field = false, false
|
||
local n_tag_sections = 0
|
||
H.apply_recursively(function(x)
|
||
if not (type(x) == 'table' and x.type == 'section') then return end
|
||
|
||
-- Add headings before first occurence of a section which type usually
|
||
-- appear several times
|
||
if not found_param and x.info.id == '@param' then
|
||
H.add_section_heading(x, 'Parameters')
|
||
found_param = true
|
||
end
|
||
if not found_field and x.info.id == '@field' then
|
||
H.add_section_heading(x, 'Fields')
|
||
found_field = true
|
||
end
|
||
|
||
if x.info.id == '@tag' then
|
||
local text = x[1]
|
||
local tag = string.match(text, "%*.*%*")
|
||
local prefix = (string.sub(tag, 2, #tag - 1))
|
||
if not H.is_module(prefix) then
|
||
prefix = ""
|
||
end
|
||
local n_filler = math.max(78 - H.visual_text_width(prefix) - H.visual_text_width(tag), 3)
|
||
local line = ("%s%s%s"):format(prefix, (" "):rep(n_filler), tag)
|
||
x:remove(1)
|
||
x:insert(1, line)
|
||
x.parent:remove(x.parent_index)
|
||
n_tag_sections = n_tag_sections + 1
|
||
x.parent:insert(n_tag_sections, x)
|
||
end
|
||
end, b)
|
||
|
||
-- b:insert(1, H.as_struct({ string.rep('=', 78) }, 'section'))
|
||
b:insert(H.as_struct({ '' }, 'section'))
|
||
end,
|
||
doc = function(d)
|
||
-- Render table of contents
|
||
H.apply_recursively(function(x)
|
||
if not (type(x) == 'table' and x.type == 'section' and x.info.id == '@toc') then return end
|
||
H.toc_insert(x)
|
||
end, d)
|
||
|
||
-- Insert modeline
|
||
d:insert(
|
||
H.as_struct(
|
||
{ H.as_struct({ H.as_struct({ ' vim:tw=78:ts=8:noet:ft=help:norl:' }, 'section') }, 'block') },
|
||
'file'
|
||
)
|
||
)
|
||
end,
|
||
sections = {
|
||
['@generic'] = function(s)
|
||
s:remove(1)
|
||
end,
|
||
['@field'] = function(s)
|
||
-- H.mark_optional(s)
|
||
if string.find(s[1], "^private ") then
|
||
s:remove(1)
|
||
return
|
||
end
|
||
H.enclose_var_name(s)
|
||
H.enclose_type(s, '`%(%1%)`', s[1]:find('%s'))
|
||
local wrapped = wrap(s[1], 78, "")
|
||
s:remove(1)
|
||
for i, line in ipairs(wrapped) do
|
||
s:insert(i, line)
|
||
end
|
||
end,
|
||
['@alias'] = function(s)
|
||
local name = s[1]:match('%s*(%S*)')
|
||
local alias = s[1]:match('%s(.*)$')
|
||
s[1] = ("`%s` → `%s`"):format(name, alias)
|
||
H.add_section_heading(s, 'Alias')
|
||
s:insert(1, H.as_struct({ ("*%s*"):format(name) }, "section", { id = "@tag" }))
|
||
end,
|
||
['@param'] = function(s)
|
||
H.enclose_var_name(s)
|
||
H.enclose_type(s, '`%(%1%)`', s[1]:find('%s'))
|
||
local wrapped = wrap(s[1], 78, "")
|
||
s:remove(1)
|
||
for i, line in ipairs(wrapped) do
|
||
s:insert(i, line)
|
||
end
|
||
end,
|
||
['@return'] = function(s)
|
||
H.enclose_type(s, '`%(%1%)`', 1)
|
||
H.add_section_heading(s, 'Return')
|
||
end,
|
||
['@nodoc'] = function(s) s.parent:clear_lines() end,
|
||
['@class'] = function(s)
|
||
H.enclose_var_name(s)
|
||
-- Add heading
|
||
local line = s[1]
|
||
s:remove(1)
|
||
local class_name = string.match(line, "%{(.*)%}")
|
||
local inherits = string.match(line, ": (.*)")
|
||
if inherits then
|
||
s:insert(1, ("Inherits: `%s`"):format(inherits))
|
||
s:insert(2, "")
|
||
end
|
||
s:insert(1, H.as_struct({ ("*%s*"):format(class_name) }, "section", { id = "@tag" }))
|
||
end,
|
||
['@signature'] = function(s)
|
||
s[1] = H.format_signature(s[1])
|
||
if s[1] ~= "" then
|
||
table.insert(s, "")
|
||
end
|
||
end,
|
||
},
|
||
file = function(f)
|
||
if not f:has_lines() then
|
||
return
|
||
end
|
||
|
||
if f.info.path ~= "./lua/" .. module .. "/init.lua" then
|
||
f:insert(1, H.as_struct({ H.as_struct({ string.rep("=", 78) }, "section") }, "block"))
|
||
f:insert(H.as_struct({ H.as_struct({ "" }, "section") }, "block"))
|
||
else
|
||
f:insert(
|
||
1,
|
||
H.as_struct(
|
||
{
|
||
H.as_struct(
|
||
{ header },
|
||
"section"
|
||
),
|
||
},
|
||
"block"
|
||
)
|
||
)
|
||
f:insert(2, H.as_struct({ H.as_struct({ "" }, "section") }, "block"))
|
||
f:insert(3, H.as_struct({ H.as_struct({ string.rep("=", 78) }, "section") }, "block"))
|
||
f:insert(H.as_struct({ H.as_struct({ "" }, "section") }, "block"))
|
||
end
|
||
end,
|
||
}),
|
||
}
|
||
end
|
||
|
||
minidoc.setup({})
|
||
minidoc.generate(
|
||
{
|
||
"./lua/nio/init.lua",
|
||
"./lua/nio/control.lua",
|
||
"./lua/nio/lsp.lua",
|
||
"./lua/nio/file.lua",
|
||
"./lua/nio/process.lua",
|
||
"./lua/nio/streams.lua",
|
||
"./lua/nio/uv.lua",
|
||
"./lua/nio/ui.lua",
|
||
"./lua/nio/tests.lua",
|
||
|
||
},
|
||
"doc/nio.txt",
|
||
create_config("nio", "*nvim-nio.txt* A library for asynchronous IO in Neovim")
|
||
|
||
)
|