docs: module-level docstrings (@defgroup) #22498

Problem:
gen_vimdoc.py / lua2dox.lua does not support @defgroup or \defgroup
except for "api-foo" modules.

Solution:
Modify `gen_vimdoc.py` to look for section names based on `helptag_fmt`.

TODO:
- Support @module ?
  https://github.com/LuaLS/lua-language-server/wiki/Annotations#module
This commit is contained in:
Justin M. Keyes 2023-03-05 18:15:29 -05:00 committed by GitHub
parent 1b49841969
commit 533d671271
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 96 additions and 59 deletions

View File

@ -28,42 +28,6 @@ A parser can also be loaded manually using a full path: >lua
vim.treesitter.require_language("python", "/path/to/python.so") vim.treesitter.require_language("python", "/path/to/python.so")
< <
==============================================================================
LANGUAGE TREES *treesitter-languagetree*
*LanguageTree*
As buffers can contain multiple languages (e.g., Vimscript commands in a Lua
file), multiple parsers may be needed to parse the full buffer. These are
combined in a |LanguageTree| object.
To create a LanguageTree (parser object) for a buffer and a given language,
use >lua
tsparser = vim.treesitter.get_parser(bufnr, lang)
<
`bufnr=0` can be used for current buffer. `lang` will default to 'filetype'.
Currently, the parser will be retained for the lifetime of a buffer but this
is subject to change. A plugin should keep a reference to the parser object as
long as it wants incremental updates.
Whenever you need to access the current syntax tree, parse the buffer: >lua
tstree = tsparser:parse()
<
This will return a table of immutable |treesitter-tree|s that represent the
current state of the buffer. When the plugin wants to access the state after a
(possible) edit it should call `parse()` again. If the buffer wasn't edited,
the same tree will be returned again without extra work. If the buffer was
parsed before, incremental parsing will be done of the changed parts.
Note: To use the parser directly inside a |nvim_buf_attach()| Lua callback,
you must call |vim.treesitter.get_parser()| before you register your callback.
But preferably parsing shouldn't be done directly in the change callback
anyway as they will be very frequent. Rather a plugin that does any kind of
analysis on a tree should use a timer to throttle too frequent updates.
See |lua-treesitter-languagetree| for the list of available methods.
============================================================================== ==============================================================================
TREESITTER TREES *treesitter-tree* TREESITTER TREES *treesitter-tree*
*TSTree* *TSTree*
@ -221,7 +185,7 @@ Nvim looks for queries as `*.scm` files in a `queries` directory under
purpose, e.g., `queries/lua/highlights.scm` for highlighting Lua files. purpose, e.g., `queries/lua/highlights.scm` for highlighting Lua files.
By default, the first query on `runtimepath` is used (which usually implies By default, the first query on `runtimepath` is used (which usually implies
that user config takes precedence over plugins, which take precedence over that user config takes precedence over plugins, which take precedence over
queries bundled with Neovim). If a query should extend other queries instead queries bundled with Nvim). If a query should extend other queries instead
of replacing them, use |treesitter-query-modeline-extends|. of replacing them, use |treesitter-query-modeline-extends|.
See |lua-treesitter-query| for the list of available methods for working with See |lua-treesitter-query| for the list of available methods for working with
@ -321,7 +285,7 @@ Use |vim.treesitter.list_directives()| to list all available directives.
TREESITTER QUERY MODELINES *treesitter-query-modeline* TREESITTER QUERY MODELINES *treesitter-query-modeline*
Neovim supports to customize the behavior of the queries using a set of Nvim supports to customize the behavior of the queries using a set of
"modelines", that is comments in the queries starting with `;`. Here are the "modelines", that is comments in the queries starting with `;`. Here are the
currently supported modeline alternatives: currently supported modeline alternatives:
@ -938,6 +902,44 @@ TSHighlighter:destroy({self}) *TSHighlighter:destroy()*
============================================================================== ==============================================================================
Lua module: vim.treesitter.languagetree *lua-treesitter-languagetree* Lua module: vim.treesitter.languagetree *lua-treesitter-languagetree*
A *LanguageTree* contains a tree of parsers: the root treesitter parser
for {lang} and any "injected" language parsers, which themselves may
inject other languages, recursively. For example a Lua buffer containing
some Vimscript commands needs multiple parsers to fully understand its
contents.
To create a LanguageTree (parser object) for a given buffer and language, use:
>lua
local parser = vim.treesitter.get_parser(bufnr, lang)
<
(where `bufnr=0` means current buffer). `lang` defaults to 'filetype'.
Note: currently the parser is retained for the lifetime of a buffer but
this may change; a plugin should keep a reference to the parser object if
it wants incremental updates.
Whenever you need to access the current syntax tree, parse the buffer:
>lua
local tree = parser:parse()
<
This returns a table of immutable |treesitter-tree| objects representing
the current state of the buffer. When the plugin wants to access the state
after a (possible) edit it must call `parse()` again. If the buffer wasn't
edited, the same tree will be returned again without extra work. If the
buffer was parsed before, incremental parsing will be done of the changed
parts.
Note: To use the parser directly inside a |nvim_buf_attach()| Lua
callback, you must call |vim.treesitter.get_parser()| before you register
your callback. But preferably parsing shouldn't be done directly in the
change callback anyway as they will be very frequent. Rather a plugin that
does any kind of analysis on a tree should use a timer to throttle too
frequent updates.
LanguageTree:children({self}) *LanguageTree:children()* LanguageTree:children({self}) *LanguageTree:children()*
Returns a map of language to child tree. Returns a map of language to child tree.

View File

@ -1,3 +1,37 @@
--- @defgroup lua-treesitter-languagetree
---
--- @brief A \*LanguageTree\* contains a tree of parsers: the root treesitter parser for {lang} and
--- any "injected" language parsers, which themselves may inject other languages, recursively.
--- For example a Lua buffer containing some Vimscript commands needs multiple parsers to fully
--- understand its contents.
---
--- To create a LanguageTree (parser object) for a given buffer and language, use:
---
--- <pre>lua
--- local parser = vim.treesitter.get_parser(bufnr, lang)
--- </pre>
---
--- (where `bufnr=0` means current buffer). `lang` defaults to 'filetype'.
--- Note: currently the parser is retained for the lifetime of a buffer but this may change;
--- a plugin should keep a reference to the parser object if it wants incremental updates.
---
--- Whenever you need to access the current syntax tree, parse the buffer:
---
--- <pre>lua
--- local tree = parser:parse()
--- </pre>
---
--- This returns a table of immutable |treesitter-tree| objects representing the current state of
--- the buffer. When the plugin wants to access the state after a (possible) edit it must call
--- `parse()` again. If the buffer wasn't edited, the same tree will be returned again without extra
--- work. If the buffer was parsed before, incremental parsing will be done of the changed parts.
---
--- Note: To use the parser directly inside a |nvim_buf_attach()| Lua callback, you must call
--- |vim.treesitter.get_parser()| before you register your callback. But preferably parsing
--- shouldn't be done directly in the change callback anyway as they will be very frequent. Rather
--- a plugin that does any kind of analysis on a tree should use a timer to throttle too frequent
--- updates.
local a = vim.api local a = vim.api
local query = require('vim.treesitter.query') local query = require('vim.treesitter.query')
local language = require('vim.treesitter.language') local language = require('vim.treesitter.language')

View File

@ -1054,17 +1054,18 @@ def main(doxygen_config, args):
fn_map_full = {} # Collects all functions as each module is processed. fn_map_full = {} # Collects all functions as each module is processed.
sections = {} sections = {}
intros = {} section_docs = {}
sep = '=' * text_width sep = '=' * text_width
base = os.path.join(output_dir, 'xml') base = os.path.join(output_dir, 'xml')
dom = minidom.parse(os.path.join(base, 'index.xml')) dom = minidom.parse(os.path.join(base, 'index.xml'))
# generate docs for section intros # Generate module-level (section) docs (@defgroup).
for compound in dom.getElementsByTagName('compound'): for compound in dom.getElementsByTagName('compound'):
if compound.getAttribute('kind') != 'group': if compound.getAttribute('kind') != 'group':
continue continue
# Doxygen "@defgroup" directive.
groupname = get_text(find_first(compound, 'name')) groupname = get_text(find_first(compound, 'name'))
groupxml = os.path.join(base, '%s.xml' % groupxml = os.path.join(base, '%s.xml' %
compound.getAttribute('refid')) compound.getAttribute('refid'))
@ -1083,33 +1084,39 @@ def main(doxygen_config, args):
if doc: if doc:
doc_list.append(doc) doc_list.append(doc)
intros[groupname] = "\n".join(doc_list) section_docs[groupname] = "\n".join(doc_list)
# Generate docs for all functions in the current module.
for compound in dom.getElementsByTagName('compound'): for compound in dom.getElementsByTagName('compound'):
if compound.getAttribute('kind') != 'file': if compound.getAttribute('kind') != 'file':
continue continue
filename = get_text(find_first(compound, 'name')) filename = get_text(find_first(compound, 'name'))
if filename.endswith('.c') or filename.endswith('.lua'): if filename.endswith('.c') or filename.endswith('.lua'):
xmlfile = os.path.join(base, xmlfile = os.path.join(base, '{}.xml'.format(compound.getAttribute('refid')))
'{}.xml'.format(compound.getAttribute('refid')))
# Extract unformatted (*.mpack). # Extract unformatted (*.mpack).
fn_map, _ = extract_from_xml(xmlfile, target, 9999, False) fn_map, _ = extract_from_xml(xmlfile, target, 9999, False)
# Extract formatted (:help). # Extract formatted (:help).
functions_text, deprecated_text = fmt_doxygen_xml_as_vimhelp( functions_text, deprecated_text = fmt_doxygen_xml_as_vimhelp(
os.path.join(base, '{}.xml'.format( os.path.join(base, '{}.xml'.format(compound.getAttribute('refid'))), target)
compound.getAttribute('refid'))), target)
if not functions_text and not deprecated_text: if not functions_text and not deprecated_text:
continue continue
else: else:
name = os.path.splitext( filename = os.path.basename(filename)
os.path.basename(filename))[0].lower() name = os.path.splitext(filename)[0].lower()
sectname = name.upper() if name == 'ui' else name.title() sectname = name.upper() if name == 'ui' else name.title()
sectname = CONFIG[target]['section_name'].get(filename, sectname)
title = CONFIG[target]['section_fmt'](sectname)
section_tag = CONFIG[target]['helptag_fmt'](sectname)
# Module/Section id matched against @defgroup.
# "*api-buffer*" => "api-buffer"
section_id = section_tag.strip('*')
doc = '' doc = ''
intro = intros.get(f'api-{name}') section_doc = section_docs.get(section_id)
if intro: if section_doc:
doc += '\n\n' + intro doc += '\n\n' + section_doc
if functions_text: if functions_text:
doc += '\n\n' + functions_text doc += '\n\n' + functions_text
@ -1119,12 +1126,7 @@ def main(doxygen_config, args):
doc += deprecated_text doc += deprecated_text
if doc: if doc:
filename = os.path.basename(filename) sections[filename] = (title, section_tag, doc)
sectname = CONFIG[target]['section_name'].get(
filename, sectname)
title = CONFIG[target]['section_fmt'](sectname)
helptag = CONFIG[target]['helptag_fmt'](sectname)
sections[filename] = (title, helptag, doc)
fn_map_full.update(fn_map) fn_map_full.update(fn_map)
if len(sections) == 0: if len(sections) == 0:
@ -1139,15 +1141,14 @@ def main(doxygen_config, args):
for filename in CONFIG[target]['section_order']: for filename in CONFIG[target]['section_order']:
try: try:
title, helptag, section_doc = sections.pop(filename) title, section_tag, section_doc = sections.pop(filename)
except KeyError: except KeyError:
msg(f'warning: empty docs, skipping (target={target}): {filename}') msg(f'warning: empty docs, skipping (target={target}): {filename}')
msg(f' existing docs: {sections.keys()}') msg(f' existing docs: {sections.keys()}')
continue continue
if filename not in CONFIG[target]['append_only']: if filename not in CONFIG[target]['append_only']:
docs += sep docs += sep
docs += '\n%s%s' % (title, docs += '\n{}{}'.format(title, section_tag.rjust(text_width - len(title)))
helptag.rjust(text_width - len(title)))
docs += section_doc docs += section_doc
docs += '\n\n\n' docs += '\n\n\n'