feat(docs): nested lists in HTML, update :help parser

- Docs HTML: improvements in https://github.com/neovim/tree-sitter-vimdoc
  allow us to many hacks in `gen_help_html.lua`.
- Docs HTML: support nested lists.
- Docs HTML: avoid extra newlines (too much whitespace) in old
  (preformatted) layout.
- Docs HTML: disable golden-grid for narrow viewport.
- Workaround for https://github.com/neovim/neovim/issues/20404

closes https://github.com/neovim/neovim/issues/20404
This commit is contained in:
Justin M. Keyes 2022-09-29 13:46:44 +02:00
parent 28fbdd3385
commit 088abbeb6e
10 changed files with 108 additions and 61 deletions

View File

@ -201,8 +201,8 @@ set(TREESITTER_LUA_SHA256 564594fe0ffd2f2fb3578a15019b723e1bc94ac82cb6a0103a6b3b
set(TREESITTER_VIM_URL https://github.com/vigoux/tree-sitter-viml/archive/v0.2.0.tar.gz)
set(TREESITTER_VIM_SHA256 608dcc31a7948cb66ae7f45494620e2e9face1af75598205541f80d782ec4501)
set(TREESITTER_HELP_URL https://github.com/neovim/tree-sitter-vimdoc/archive/v1.1.0.tar.gz)
set(TREESITTER_HELP_SHA256 4c0ef80c6dc09acab362478950ec6be58a4ab1cbf2d95754b8fbb566e4c647a1)
set(TREESITTER_HELP_URL https://github.com/neovim/tree-sitter-vimdoc/archive/c27e3e21a54f6d90dfb791f37d90eab5b28de971.tar.gz)
set(TREESITTER_HELP_SHA256 54a6a5b52a395097775f06f96ac1e1c9efdab10243550a467e1198a286b8c59c)
set(TREESITTER_URL https://github.com/tree-sitter/tree-sitter/archive/v0.20.7.tar.gz)
set(TREESITTER_SHA256 b355e968ec2d0241bbd96748e00a9038f83968f85d822ecb9940cbe4c42e182e)

View File

@ -434,7 +434,7 @@ its config is non-empty: >
if vim.api.nvim_win_get_config(window_id).relative ~= '' then
-- window with this window_id is floating
end
>
<
Buffer text can be highlighted by typical mechanisms (syntax highlighting,
|api-highlights|). The |hl-NormalFloat| group highlights normal text;
@ -456,7 +456,7 @@ Example: create a float with scratch buffer: >
let win = nvim_open_win(buf, 0, opts)
" optional: change highlight, otherwise Pmenu is used
call nvim_win_set_option(win, 'winhl', 'Normal:MyHighlight')
>
<
==============================================================================
Extended marks *api-extended-marks* *extmarks*
@ -500,8 +500,8 @@ Let's set an extmark at the first row (row=0) and third column (column=2).
01 2345678
0 ex|ample..
< ^ extmark position
>
^ extmark position
let g:mark_ns = nvim_create_namespace('myplugin')
let g:mark_id = nvim_buf_set_extmark(0, g:mark_ns, 0, 2, {})
<
@ -520,8 +520,8 @@ use |nvim_buf_del_extmark()|. Deleting "x" in our example: >
0 12345678
0 e|ample..
< ^ extmark position
>
^ extmark position
echo nvim_buf_get_extmark_by_id(0, g:mark_ns, g:mark_id, {})
=> [0, 1]
<

View File

@ -7716,7 +7716,7 @@ sqrt({expr}) *sqrt()*
:echo sqrt(100)
< 10.0 >
:echo sqrt(-4.01)
< str2float('nan')
< str2float("nan")
NaN may be different, it depends on system libraries.
Can also be used as a |method|: >

View File

@ -21,6 +21,7 @@ For a list of Vim variables see |vim-variable|.
tag char action in Insert mode ~
-----------------------------------------------------------------------
|i_CTRL-@| CTRL-@ insert previously inserted text and stop
insert
|i_CTRL-A| CTRL-A insert previously inserted text
@ -184,6 +185,7 @@ note: 1 = cursor movement command; 2 = can be undone/redone
tag char note action in Normal mode ~
------------------------------------------------------------------------------
CTRL-@ not used
|CTRL-A| CTRL-A 2 add N to number at/after cursor
|CTRL-B| CTRL-B 1 scroll N screens Backwards
@ -469,6 +471,7 @@ These can be used after an operator or in Visual mode to select an object.
tag command action in op-pending and Visual mode ~
------------------------------------------------------------------------------
|v_aquote| a" double quoted string
|v_a'| a' single quoted string
|v_a(| a( same as ab
@ -511,6 +514,7 @@ tag command action in op-pending and Visual mode ~
tag command action in Normal mode ~
------------------------------------------------------------------------------
|CTRL-W_CTRL-B| CTRL-W CTRL-B same as "CTRL-W b"
|CTRL-W_CTRL-C| CTRL-W CTRL-C same as "CTRL-W c"
|CTRL-W_CTRL-D| CTRL-W CTRL-D same as "CTRL-W d"
@ -609,6 +613,7 @@ tag command action in Normal mode ~
tag char note action in Normal mode ~
------------------------------------------------------------------------------
|[_CTRL-D| [ CTRL-D jump to first #define found in current and
included files matching the word under the
cursor, start searching at beginning of
@ -699,6 +704,7 @@ tag char note action in Normal mode ~
tag char note action in Normal mode ~
------------------------------------------------------------------------------
g_CTRL-A g CTRL-A dump a memory profile
|g_CTRL-G| g CTRL-G show information about current cursor
position
@ -802,6 +808,7 @@ g_CTRL-A g CTRL-A dump a memory profile
tag char note action in Normal mode ~
------------------------------------------------------------------------------
|z<CR>| z<CR> redraw, cursor line to top of window,
cursor on first non-blank
|zN<CR>| z{height}<CR> redraw, make window {height} lines high
@ -876,6 +883,7 @@ These can be used after an operator, but before a {motion} has been entered.
tag char action in Operator-pending mode ~
-----------------------------------------------------------------------
|o_v| v force operator to work charwise
|o_V| V force operator to work linewise
|o_CTRL-V| CTRL-V force operator to work blockwise
@ -888,6 +896,7 @@ here are those that are different.
tag command note action in Visual mode ~
------------------------------------------------------------------------------
|v_CTRL-\_CTRL-N| CTRL-\ CTRL-N stop Visual mode
|v_CTRL-\_CTRL-G| CTRL-\ CTRL-G go to Normal mode
|v_CTRL-A| CTRL-A 2 add N to number in highlighted text
@ -1008,6 +1017,7 @@ file names, tags, commands etc. as appropriate.
tag command action in Command-line editing mode ~
------------------------------------------------------------------------------
CTRL-@ not used
|c_CTRL-A| CTRL-A do completion on the pattern in front of the
cursor and insert all matches
@ -1118,6 +1128,7 @@ The commands are sorted on the non-optional part of their name.
tag command action ~
------------------------------------------------------------------------------
|:| : nothing
|:range| :{range} go to last line in {range}
|:!| :! filter lines or execute an external command

View File

@ -61,7 +61,7 @@ other words, this section describes which tokens are valid, how they can be
combined, and what their combinations mean.
The language constructs will be explained using the usual extended BNF
notation, in which { `a` } means 0 or more `a`'s, and [ `a` ] means an optional `a`.
notation, in which `{ a }` means 0 or more `a`'s, and `[ a ]` means an optional `a`.
==============================================================================
2.1 Lexical Conventions *luaref-langLexConv*

View File

@ -2782,7 +2782,7 @@ your browsing preferences. (see also: |netrw-settings|)
history are saved (as .netrwbook and
.netrwhist).
Netrw uses |expand()| on the string.
default: stdpath('data') (see |stdpath()|)
default: stdpath("data") (see |stdpath()|)
*g:netrw_keepdir* =1 (default) keep current directory immune from
the browsing directory.

View File

@ -471,7 +471,7 @@ flag when defining the function, it is not relevant when executing it. >
:set cpo-=C
<
*line-continuation-comment*
To add a comment in between the lines start with '"\ '. Notice the space
To add a comment in between the lines start with `'"\ '`. Notice the space
after the backslash. Example: >
let array = [
"\ first entry comment
@ -491,7 +491,7 @@ Rationale:
continuation lines to be part of the comment. Since it was like this
for a long time, when making it possible to add a comment halfway a
sequence of continuation lines, it was not possible to use \", since
that was a valid continuation line. Using '"\ ' comes closest, even
that was a valid continuation line. Using `'"\ '` comes closest, even
though it may look a bit weird. Requiring the space after the
backslash is to make it very unlikely this is a normal comment line.

View File

@ -1357,7 +1357,7 @@ LOG FILE *$NVIM_LOG_FILE* *E5430*
Besides 'debug' and 'verbose', Nvim keeps a general log file for internal
debugging, plugins and RPC clients. >
:echo $NVIM_LOG_FILE
By default, the file is located at stdpath('log')/log unless that path
By default, the file is located at stdpath("log")/log unless that path
is inaccessible or if $NVIM_LOG_FILE was set before |startup|.

View File

@ -67,7 +67,7 @@ local exclude_invalid = {
["v:_null_string"] = "builtin.txt",
["vim.lsp.buf_request()"] = "lsp.txt",
["vim.lsp.util.get_progress_messages()"] = "lsp.txt",
["vim.treesitter.start()"] = "treesitter.txt"
["vim.treesitter.start()"] = "treesitter.txt",
}
local function tofile(fname, text)
@ -96,11 +96,6 @@ local function url_encode(s)
'g')
end
-- Removes the ">" and "<" chars that delineate a codeblock in Vim :help files.
local function trim_gt_lt(s)
return s:gsub('^%s*>%s*\n', ''):gsub('\n<', '')
end
local function expandtabs(s)
return s:gsub('\t', (' '):rep(8))
end
@ -123,15 +118,29 @@ local function basename_noext(f)
end
local function is_blank(s)
return not not s:find('^%s*$')
return not not s:find([[^[\t ]*$]])
end
local function trim(s)
return vim.trim(s)
local function trim(s, dir)
return vim.fn.trim(s, '\r\t\n ', dir or 0)
end
local function trim_bullet(s)
return s:gsub('^%s*[-*•]%s', '')
-- Remove common punctuation from URLs.
--
-- TODO: fix this in the parser instead... https://github.com/neovim/tree-sitter-vimdoc
--
-- @returns (fixed_url, removed_chars) where `removed_chars` is in the order found in the input.
local function fix_url(url)
local removed_chars = ''
local fixed_url = url
-- Remove up to one of each char from end of the URL, in this order.
for _, c in ipairs({ '.', ')', }) do
if fixed_url:sub(-1) == c then
removed_chars = c .. removed_chars
fixed_url = fixed_url:sub(1, -2)
end
end
return fixed_url, removed_chars
end
-- Checks if a given line is a "noise" line that doesn't look good in HTML form.
@ -357,7 +366,8 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
if node_name == 'help_file' then -- root node
return text
elseif node_name == 'url' then
return ('%s<a href="%s">%s</a>'):format(ws(), trimmed, trimmed)
local fixed_url, removed_chars = fix_url(trimmed)
return ('%s<a href="%s">%s</a>%s'):format(ws(), fixed_url, fixed_url, removed_chars)
elseif node_name == 'word' or node_name == 'uppercase_name' then
return html_esc(text)
elseif node_name == 'h1' or node_name == 'h2' or node_name == 'h3' then
@ -383,38 +393,37 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
return ''
end
if opt.old then
-- XXX: Treat old docs as preformatted; random indentation is used for layout there.
return ('<div class="old-help-para">%s</div>\n'):format(text)
-- XXX: Treat "old" docs as preformatted: they use indentation for layout.
-- Trim trailing newlines to avoid too much whitespace between divs.
return ('<div class="old-help-para">%s</div>\n'):format(trim(text, 2))
end
return string.format('<div class="help-para">\n%s\n</div>\n', text)
elseif node_name == 'line' then
local sib = root:prev_sibling()
local sib_last = sib and sib:named_child(sib:named_child_count() - 1)
local in_li = false
-- XXX: parser bug: (codeblock) without terminating "<" consumes first char of the next (line). Recover it here.
local recovered = (sib_last and sib_last:type() == 'codeblock') and node_text(root:prev_sibling()):sub(-1) or ''
recovered = recovered == '<' and '' or html_esc(recovered)
-- XXX: see if we are currently "in" a listitem.
while sib ~= nil and not in_li do
in_li = (sib:type() == 'line_li')
sib = sib:prev_sibling()
if parent ~= 'codeblock' and (is_blank(text) or is_noise(text, stats.noise_lines)) then
return '' -- Discard common "noise" lines.
end
-- Close the current listitem.
local close = (in_li and next_ ~= 'line') and '</div>' or ''
if is_blank(text) or is_noise(text, stats.noise_lines) then
return close -- Discard common "noise" lines.
end
local div = (root:child(0) and root:child(0):type() == 'column_heading') or close ~= ''
return string.format('%s%s%s%s', recovered, div and trim(text) or text, div and '' or '\n', close)
-- XXX: Avoid newlines (too much whitespace) after block elements in old (preformatted) layout.
local div = opt.old and root:child(0) and vim.tbl_contains({'column_heading', 'h1', 'h2', 'h3'}, root:child(0):type())
return string.format('%s%s', div and trim(text) or text, div and '' or '\n')
elseif node_name == 'line_li' then
-- Close the listitem immediately if the next sibling is not a line.
local close = (next_ ~= 'line') and '</div>' or ''
return string.format('<div class="help-li">%s %s', trim_bullet(text), close)
local sib = root:prev_sibling()
local prev_li = sib and sib:type() == 'line_li'
if not prev_li then
opt.indent = 1
else
-- The previous listitem _sibling_ is _logically_ the _parent_ if it is indented less.
local parent_indent = get_indent(node_text(sib))
local this_indent = get_indent(node_text())
if this_indent > parent_indent then
opt.indent = opt.indent + 1
elseif this_indent < parent_indent then
opt.indent = math.max(1, opt.indent - 1)
end
end
local margin = opt.indent == 1 and '' or ('margin-left: %drem;'):format((1.5 * opt.indent))
return string.format('<div class="help-li" style="%s">%s</div>', margin, text)
elseif node_name == 'taglink' or node_name == 'optionlink' then
if root:has_error() then
return text
@ -429,7 +438,10 @@ local function visit_node(root, level, lang_tree, headings, opt, stats)
elseif node_name == 'argument' then
return ('%s<code>{%s}</code>'):format(ws(), text)
elseif node_name == 'codeblock' then
return ('<pre>%s</pre>'):format(html_esc(trim_indent(trim_gt_lt(text))))
if is_blank(text) then
return ''
end
return ('<pre>%s</pre>'):format(html_esc(trim(trim_indent(text), 2)))
elseif node_name == 'tag' then -- anchor
if root:has_error() then
return text
@ -630,7 +642,9 @@ local function gen_one(fname, to_fname, old, commit)
local main = ''
for _, tree in ipairs(lang_tree:trees()) do
main = main .. (visit_node(tree:root(), 0, tree, headings, { buf = buf, old = old, fname = fname, to_fname = to_fname }, stats))
main = main .. (visit_node(tree:root(), 0, tree, headings,
{ buf = buf, old = old, fname = fname, to_fname = to_fname, indent = 1, },
stats))
end
main = ([[
@ -718,6 +732,17 @@ local function gen_css(fname)
position: fixed;
left: 67%;
}
.golden-grid {
display: grid;
grid-template-columns: 65% auto;
grid-gap: 1em;
}
}
@media (max-width: 40em) {
.golden-grid {
/* Disable grid for narrow viewport (mobile phone). */
display: block;
}
}
@media (prefers-color-scheme: dark) {
:root {
@ -831,11 +856,6 @@ local function gen_css(fname)
color: gray;
font-size: smaller;
}
.golden-grid {
display: grid;
grid-template-columns: 65% auto;
grid-gap: 1em;
}
]]
tofile(fname, css)
end
@ -869,6 +889,22 @@ function M._test()
eq(5, get_indent(' a\n \n b\n c\n d\n e\n'))
eq('a\n \n b\n c\n d\n e\n', trim_indent(' a\n \n b\n c\n d\n e\n'))
local fixed_url, removed_chars = fix_url('https://example.com).')
eq('https://example.com', fixed_url)
eq(').', removed_chars)
fixed_url, removed_chars = fix_url('https://example.com.)')
eq('https://example.com.', fixed_url)
eq(')', removed_chars)
fixed_url, removed_chars = fix_url('https://example.com.')
eq('https://example.com', fixed_url)
eq('.', removed_chars)
fixed_url, removed_chars = fix_url('https://example.com)')
eq('https://example.com', fixed_url)
eq(')', removed_chars)
fixed_url, removed_chars = fix_url('https://example.com')
eq('https://example.com', fixed_url)
eq('', removed_chars)
print('all tests passed')
end

View File

@ -21,7 +21,7 @@ describe(':help docs', function()
ok(rv.helpfiles > 100, '>100 :help files', rv.helpfiles)
-- Check that parse errors did not increase wildly.
-- TODO: Fix all parse errors in :help files.
ok(rv.err_count < 250, '<250 parse errors', rv.err_count)
ok(rv.err_count < 280, '<280 parse errors', rv.err_count)
eq({}, rv.invalid_links, exec_lua([[return 'found invalid :help tag links:\n'..vim.inspect(...)]], rv.invalid_links))
end)