docs: better docgen using treesitter

This commit is contained in:
Folke Lemaitre 2024-11-09 14:42:09 +01:00
parent 7f9f691a12
commit 7e6ea65f5f
No known key found for this signature in database
GPG key ID: 41F8B1FBACAE2040
31 changed files with 233 additions and 80 deletions

View file

@ -1,4 +1,4 @@
*snacks-bigfile.txt* For Neovim Last change: 2024 November 08
*snacks-bigfile.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-bigfile-table-of-contents*

View file

@ -1,9 +1,10 @@
*snacks-bufdelete.txt* For Neovim Last change: 2024 November 08
*snacks-bufdelete.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-bufdelete-table-of-contents*
1. Module |snacks-bufdelete-module|
1. Config |snacks-bufdelete-config|
2. Module |snacks-bufdelete-module|
- Snacks.bufdelete() |snacks-bufdelete-module-snacks.bufdelete()|
- Snacks.bufdelete.all() |snacks-bufdelete-module-snacks.bufdelete.all()|
- Snacks.bufdelete.delete()|snacks-bufdelete-module-snacks.bufdelete.delete()|
@ -15,7 +16,20 @@ discard.
==============================================================================
1. Module *snacks-bufdelete-module*
1. Config *snacks-bufdelete-config*
>lua
---@class snacks.bufdelete.Opts
---@field buf? number Buffer to delete. If not provided, the current buffer is deleted
---@field force? boolean Delete the buffer even if it is modified
---@field filter? fun(buf: number): boolean Filter buffers to delete
---@field wipe? boolean Wipe the buffer instead of deleting it (see `:h :bwipeout`)
{}
<
==============================================================================
2. Module *snacks-bufdelete-module*
SNACKS.BUFDELETE() *snacks-bufdelete-module-snacks.bufdelete()*

View file

@ -1,4 +1,4 @@
*snacks-debug.txt* For Neovim Last change: 2024 November 08
*snacks-debug.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-debug-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-git.txt* For Neovim Last change: 2024 November 08
*snacks-git.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-git-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-gitbrowse.txt* For Neovim Last change: 2024 November 08
*snacks-gitbrowse.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-gitbrowse-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-init.txt* For Neovim Last change: 2024 November 08
*snacks-init.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-init-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-lazygit.txt* For Neovim Last change: 2024 November 08
*snacks-lazygit.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-lazygit-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-notifier.txt* For Neovim Last change: 2024 November 08
*snacks-notifier.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-notifier-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-notify.txt* For Neovim Last change: 2024 November 08
*snacks-notify.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-notify-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-quickfile.txt* For Neovim Last change: 2024 November 08
*snacks-quickfile.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-quickfile-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-rename.txt* For Neovim Last change: 2024 November 08
*snacks-rename.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-rename-table-of-contents*

View file

@ -1,9 +1,12 @@
*snacks-statuscolumn.txt* For Neovim Last change: 2024 November 08
*snacks-statuscolumn.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-statuscolumn-table-of-contents*
1. Config |snacks-statuscolumn-config|
2. Module |snacks-statuscolumn-module|
- Snacks.statuscolumn() |snacks-statuscolumn-module-snacks.statuscolumn()|
- Snacks.statuscolumn.get()|snacks-statuscolumn-module-snacks.statuscolumn.get()|
==============================================================================
1. Config *snacks-statuscolumn-config*
@ -25,6 +28,26 @@ Table of Contents *snacks-statuscolumn-table-of-contents*
}
<
==============================================================================
2. Module *snacks-statuscolumn-module*
SNACKS.STATUSCOLUMN() *snacks-statuscolumn-module-snacks.statuscolumn()*
>lua
---@type fun(): string
Snacks.statuscolumn()
<
SNACKS.STATUSCOLUMN.GET()*snacks-statuscolumn-module-snacks.statuscolumn.get()*
>lua
---@return string
Snacks.statuscolumn.get()
<
Generated by panvimdoc <https://github.com/kdheepak/panvimdoc>
vim:tw=78:ts=8:noet:ft=help:norl:

View file

@ -1,4 +1,4 @@
*snacks-terminal.txt* For Neovim Last change: 2024 November 08
*snacks-terminal.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-terminal-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-toggle.txt* For Neovim Last change: 2024 November 08
*snacks-toggle.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-toggle-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-win.txt* For Neovim Last change: 2024 November 08
*snacks-win.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-win-table-of-contents*

View file

@ -1,4 +1,4 @@
*snacks-words.txt* For Neovim Last change: 2024 November 08
*snacks-words.txt* snacks.nvim
==============================================================================
Table of Contents *snacks-words-table-of-contents*
@ -21,6 +21,8 @@ Auto-show LSP references and quickly navigate between them
debounce = 200, -- time in ms to wait before updating
notify_jump = false, -- show a notification when jumping
notify_end = true, -- show a notification when reaching the end
foldopen = true, -- open folds after jumping
jumplist = true, -- set jump point before jumping
modes = { "n", "i", "c" }, -- modes to show references
}
<

View file

@ -20,3 +20,19 @@
refresh = 50, -- refresh at most every 50ms
}
```
## 📦 Module
### `Snacks.statuscolumn()`
```lua
---@type fun(): string
Snacks.statuscolumn()
```
### `Snacks.statuscolumn.get()`
```lua
---@return string
Snacks.statuscolumn.get()
```

View file

@ -16,6 +16,7 @@ local defaults = {
end,
}
---@private
function M.setup()
local opts = Snacks.config.get("bigfile", defaults)

View file

@ -1,5 +1,4 @@
---@class snacks.bufdelete
---@hide
---@overload fun(buf?: number)
local M = setmetatable({}, {
__call = function(t, ...)

View file

@ -1,5 +1,4 @@
---@class snacks.debug
---@hide
---@overload fun(...)
local M = setmetatable({}, {
__call = function(t, ...)

View file

@ -1,63 +1,154 @@
local M = {}
local query = vim.treesitter.query.parse(
"lua",
[[
;; top-level locals
((variable_declaration (
assignment_statement
(variable_list name: (identifier) @local_name)
(expression_list value: (_) @local_value)
(#match? @local_value "(setmetatable|\\{)")
)) @local
(#any-of? @local_name "M" "defaults" "config")
(#has-parent? @local chunk))
;; top-level functions/methods
(function_declaration
name: (_) @fun_name (#match? @fun_name "^M")
parameters: (_) @fun_params
) @fun
;; styles
(function_call
name: (dot_index_expression) @_sf (#eq? @_sf "Snacks.config.style")
arguments: (arguments
(string content: (string_content) @style_name)
(table_constructor) @style_config)
) @style
]]
)
---@class snacks.docs.Capture
---@field name string
---@field line number
---@field node TSNode
---@field text string
---@field comment string
---@field fields table<string, string>
---@class snacks.docs.Parse
---@field captures snacks.docs.Capture[]
---@field comments string[]
---@class snacks.docs.Info
---@field config? string
---@field mod? string
---@field methods {name: string, args: string, comment?: string, types?: string, type: "method"|"function"}[]
---@field types string[]
---@field styles {name:string, opts:string}[]
---@param lines string[]
function M.extract(lines)
local code = table.concat(lines, "\n")
local config = code:match("\n(%-%-%- ?@class snacks%.%w+%.Config.-\n})")
config = config or code:match("\n(%-%-%- ?@class snacks%.Config.-\n})")
local mod ---@type string
function M.parse(lines)
local source = table.concat(lines, "\n")
local parser = vim.treesitter.get_string_parser(source, "lua")
parser:parse()
local comments = {} ---@type string[]
local types = {} ---@type string[]
local styles = {} ---@type {name:string, opts:string}[]
local style_pattern = 'Snacks%.config%.style%("([^"]+)"%s*,%s*({.-}%s*)%)'
for style_name, style in code:gmatch(style_pattern) do
table.insert(styles, { name = style_name, opts = style })
end
---@type {name: string, args: string, comment?: string, types?: string, type: "method"|"function"}[]
local methods = {}
for _, line in ipairs(lines) do
if line:match("^%-%-") then
table.insert(comments, line)
else
local comment = table.concat(comments, "\n")
if line:find("^local M =") then
mod = comment
elseif comment:find("@private") then
else
local t, name, args = line:match("^function M([:%.])([%w_%.]+)%((.-)%)")
if name and args then
if not name:find("^_") then
table.insert(methods, {
name = name,
args = args,
type = t,
comment = comment,
})
end
elseif #comments > 0 and line == "" then
table.insert(types, table.concat(comments, "\n"))
end
for l, line in ipairs(lines) do
if line:find("^%-%-") then
comments[l] = line
if comments[l - 1] then
comments[l] = comments[l - 1] .. "\n" .. comments[l]
comments[l - 1] = nil
end
comments = {}
end
end
local private = mod and mod:find("@private")
config = config and config:gsub("local defaults = ", ""):gsub("local config = ", "") or nil
---@type snacks.docs.Parse
local ret = { captures = {}, comments = {} }
---@class snacks.docs.Info
for id, node in query:iter_captures(parser:trees()[1]:root(), source) do
local name = query.captures[id]
if not name:find("_") then
-- add fields
local fields = {}
for id2, node2 in query:iter_captures(node, source) do
local c = query.captures[id2]
if c:find(".+_") then
fields[c:gsub("^.*_", "")] = vim.treesitter.get_node_text(node2, source)
end
end
-- add comments
local comment = "" ---@type string
if comments[node:start()] then
comment = comments[node:start()]
comments[node:start()] = nil
end
table.insert(ret.captures, {
text = vim.treesitter.get_node_text(node, source),
name = name,
comment = comment,
line = node:start() + 1,
node = node,
fields = fields,
})
end
end
-- remove comments that are followed by code
for l in pairs(comments) do
if lines[l + 1] and lines[l + 1]:find("^.+$") then
comments[l] = nil
end
end
for l in ipairs(lines) do
if comments[l] then
table.insert(ret.comments, comments[l])
end
end
return ret
end
---@param lines string[]
function M.extract(lines)
local parse = M.parse(lines)
---@type snacks.docs.Info
local ret = {
config = config,
mod = mod,
methods = methods,
types = types,
styles = styles,
methods = {},
types = vim.tbl_filter(function(c)
return not c:find("@private")
end, parse.comments),
styles = {},
}
return private and { config = config, methods = {}, types = {}, styles = styles } or ret
for _, c in ipairs(parse.captures) do
if c.comment:find("@private") then
-- skip private
elseif c.name == "local" then
if vim.tbl_contains({ "defaults", "config" }, c.fields.name) then
ret.config = vim.trim(c.comment .. "\n" .. c.fields.value)
elseif c.fields.name == "M" then
ret.mod = c.comment
end
elseif c.name == "fun" then
local name = c.fields.name:sub(2)
local args = (c.fields.params or ""):sub(2, -2)
local comment = c.comment
local type = name:sub(1, 1)
name = name:sub(2)
if not name:find("^_") then
table.insert(ret.methods, { name = name, args = args, comment = comment, type = type })
end
elseif c.name == "style" then
table.insert(ret.styles, { name = c.fields.name, opts = c.fields.config })
end
end
return ret
end
---@param tag string
@ -75,6 +166,7 @@ end
---@param str string
---@param opts? {extract_comment: boolean} -- default true
function M.md(str, opts)
str = str or ""
opts = opts or {}
if opts.extract_comment == nil then
opts.extract_comment = true
@ -161,7 +253,8 @@ function M.render(name, info)
end
return true
end, mod_lines)
if not info.mod:find("@hide") then
local hide = #mod_lines == 1 and mod_lines[1]:find("@class")
if not hide then
table.insert(mod_lines, prefix .. " = {}")
add(M.md(table.concat(mod_lines, "\n")))
end

View file

@ -1,5 +1,4 @@
---@class snacks.git
---@hide
local M = {}
Snacks.config.style("blame_line", {

View file

@ -1,5 +1,4 @@
---@class snacks.gitbrowse
---@hide
---@overload fun(opts?: snacks.gitbrowse.Config)
local M = setmetatable({}, {
__call = function(t, ...)

View file

@ -1,4 +1,3 @@
---@hide
---@class snacks.lazygit
---@overload fun(opts?: snacks.lazygit.Config): snacks.win
local M = setmetatable({}, {

View file

@ -1,4 +1,3 @@
---@hide
---@class snacks.notifier
---@overload fun(msg: string, level?: snacks.notifier.level|number, opts?: snacks.notifier.Notif.opts): number|string
local M = setmetatable({}, {

View file

@ -1,4 +1,3 @@
---@hide
---@class snacks.notify
---@overload fun(msg: string|string[], opts?: snacks.notify.Opts)
local M = setmetatable({}, {

View file

@ -8,6 +8,7 @@ local defaults = {
exclude = { "latex" },
}
---@private
function M.setup()
local opts = Snacks.config.get("quickfile", defaults)
-- Skip if we already entered vim

View file

@ -1,4 +1,3 @@
---@hide
---@class snacks.rename
local M = {}

View file

@ -1,4 +1,3 @@
---@private
---@class snacks.statuscolumn
---@overload fun(): string
local M = setmetatable({}, {
@ -24,6 +23,7 @@ local defaults = {
local config = Snacks.config.get("statuscolumn", defaults)
---@private
---@alias snacks.statuscolumn.Sign.type "mark"|"sign"|"fold"|"git"
---@alias snacks.statuscolumn.Sign {name:string, text:string, texthl:string, priority:number, type:snacks.statuscolumn.Sign.type}
@ -33,6 +33,7 @@ local cache = {}
local did_setup = false
---@private
function M.setup()
if did_setup then
return
@ -44,6 +45,7 @@ function M.setup()
end)
end
---@private
---@param name string
function M.is_git_sign(name)
for _, pattern in ipairs(config.git.patterns) do
@ -54,6 +56,7 @@ function M.is_git_sign(name)
end
-- Returns a list of regular and extmark signs sorted by priority (low to high)
---@private
---@return table<number, snacks.statuscolumn.Sign[]>
---@param buf number
function M.buf_signs(buf)
@ -110,10 +113,11 @@ function M.buf_signs(buf)
end
-- Returns a list of regular and extmark signs sorted by priority (high to low)
---@return snacks.statuscolumn.Sign[]
---@private
---@param win number
---@param buf number
---@param lnum number
---@return snacks.statuscolumn.Sign[]
function M.line_signs(win, buf, lnum)
local signs = M.buf_signs(buf)[lnum] or {}
@ -133,6 +137,7 @@ function M.line_signs(win, buf, lnum)
return signs
end
---@private
---@param sign? snacks.statuscolumn.Sign
---@param len? number
function M.icon(sign, len)
@ -143,6 +148,7 @@ function M.icon(sign, len)
return sign.texthl and ("%#" .. sign.texthl .. "#" .. text .. "%*") or text
end
---@return string
function M.get()
M.setup()
local win = vim.g.statusline_winid

View file

@ -1,4 +1,3 @@
---@hide
---@class snacks.words
local M = {}

View file

@ -13,5 +13,11 @@ fi
PANVIMDOC=~/projects/panvimdoc
for f in docs/*.md; do
$PANVIMDOC/panvimdoc.sh --project-name "snacks-$(basename "$f" .md)" --input-file "$f" --vim-version "Neovim" --demojify "true" --shift-heading-level-by -1
$PANVIMDOC/panvimdoc.sh \
--project-name "snacks-$(basename "$f" .md)" \
--description "snacks.nvim" \
--input-file "$f" \
--vim-version "Neovim" \
--demojify "true" \
--shift-heading-level-by -1
done