runtime(helptoc): the helptoc package can be improved
Adds the following changes:
- New Maintainer: Pete Kenny
- New filetypes supported (asciidoc, html, tex, vim, xhtml)
- improved Markdown support
- Sanitised ToCs and popup presentation
- Configuration improvements and options
- Add helptoc.txt help file
closes: #17255
Signed-off-by: Peter Kenny <github.com@k1w1.cyou>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/.github/MAINTAINERS b/.github/MAINTAINERS
index 19a65c0..b5e2bc6 100644
--- a/.github/MAINTAINERS
+++ b/.github/MAINTAINERS
@@ -431,6 +431,7 @@
runtime/lang/menu_ru_ru.utf-8.vim @RestorerZ
runtime/pack/dist/opt/cfilter/plugin/cfilter.vim @yegappan
runtime/pack/dist/opt/comment/ @habamax
+runtime/pack/dist/opt/helptoc/ @kennypete
runtime/pack/dist/opt/matchit/ @chrisbra
runtime/pack/dist/opt/nohlsearch/ @habamax
runtime/plugin/manpager.vim @Konfekt
diff --git a/Filelist b/Filelist
index a7a937c..17ec8df 100644
--- a/Filelist
+++ b/Filelist
@@ -808,6 +808,8 @@
runtime/pack/dist/opt/editorconfig/ftdetect/editorconfig.vim \
runtime/pack/dist/opt/editorconfig/plugin/editorconfig.vim \
runtime/pack/dist/opt/helptoc/autoload/helptoc.vim \
+ runtime/pack/dist/opt/helptoc/doc/helptoc.txt \
+ runtime/pack/dist/opt/helptoc/doc/tags \
runtime/pack/dist/opt/helptoc/plugin/helptoc.vim \
runtime/pack/dist/opt/hlyank/plugin/hlyank.vim \
runtime/pack/dist/opt/justify/plugin/justify.vim \
diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt
index 647c35a..4e08d90 100644
--- a/runtime/doc/helphelp.txt
+++ b/runtime/doc/helphelp.txt
@@ -1,4 +1,4 @@
-*helphelp.txt* For Vim version 9.1. Last change: 2025 Apr 21
+*helphelp.txt* For Vim version 9.1. Last change: 2025 May 04
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -287,9 +287,11 @@
<Home> | select first entry
<End> | select last entry
-The plugin can also provide a table of contents in man pages, markdown files,
-and terminal buffers. In the latter, the entries will be the past executed
-shell commands. To find those, the following pattern is used: >
+The plugin can also provide a table of contents in buffers of the following
+filetypes: asciidoc, html, man, markdown, tex, vim, and xhtml. In addition
+it also provide a table of contents for a terminal buffer, which produces
+entries that are the past executed shell commands. To find those, by default,
+the following pattern is used: >
^\w\+@\w\+:\f\+\$\s
@@ -303,6 +305,9 @@
<Esc> instead of <CR>, you can then get more context for each remaining entry
by pressing `J` or `K`.
+Refer |helptoc.vim| for more details about helptoc, particularly about using
+it with filetypes other than help, and configuring its options.
+
==============================================================================
2. Translated help files *help-translated*
diff --git a/runtime/pack/dist/opt/helptoc/autoload/helptoc.vim b/runtime/pack/dist/opt/helptoc/autoload/helptoc.vim
index c0d86a4..a22d4ba 100644
--- a/runtime/pack/dist/opt/helptoc/autoload/helptoc.vim
+++ b/runtime/pack/dist/opt/helptoc/autoload/helptoc.vim
@@ -1,24 +1,71 @@
vim9script noclear
-
# Config {{{1
+# g:helptoc {{{2
+# Create the g:helptoc dict (used to specify the shell_prompt and other
+# options) when it does not exist
+g:helptoc = exists('g:helptoc') ? g:helptoc : {}
-var SHELL_PROMPT: string = ''
+# Set the initial shell_prompt pattern matching a default bash prompt
+g:helptoc.shell_prompt = get(g:helptoc, 'shell_prompt', '^\w\+@\w\+:\f\+\$\s')
+
+# Track the prior prompt (used to reset b:toc if 'shell_prompt' changes)
+g:helptoc.prior_shell_prompt = g:helptoc.shell_prompt
def UpdateUserSettings() #{{{2
- var new_prompt: string = g:
- ->get('helptoc', {})
- ->get('shell_prompt', '^\w\+@\w\+:\f\+\$\s')
- if new_prompt != SHELL_PROMPT
- SHELL_PROMPT = new_prompt
+
+ if g:helptoc.shell_prompt != g:helptoc.prior_shell_prompt
# invalidate cache: user config has changed
unlet! b:toc
+ # reset the prior prompt to the new prompt
+ g:helptoc.prior_shell_prompt = g:helptoc.shell_prompt
endif
+
+ # helptoc popup presentation options{{{
+ # Enable users to choose whether, in toc and help text popups, to have:
+ # - border (default [], which is a border, so is usually wanted)
+ # - borderchars (default single box drawing; use [] for Vim's defaults)
+ # - borderhighlight (default [], but a user may prefer something else)
+ # - close (default 'none'; mouse users may prefer 'button')
+ # - drag (default true, which is a popup_menu's default)
+ # - scrollbar (default false; for long tocs/HELP_TEXT true may be better)
+ # For example, in a Vim9 script .vimrc, these settings will produce tocs
+ # with borders that have the same highlight group as the inactive
+ # statusline, a scrollbar, and an 'X' close button:
+ # g:helptoc.popup_borderchars = get(g:helptoc, 'popup_borderchars', [' '])
+ # g:helptoc.popup_borderhighlight = get(g:helptoc,
+ # 'popup_borderhighlight', ['StatusLineNC'])
+ # g:helptoc.popup_close = get(g:helptoc, 'popup_close', 'button')
+ # g:helptoc.popup_scrollbar = get(g:helptoc, 'popup_scrollbar', true)
+ # }}}
+ g:helptoc.popup_border = get(g:helptoc, 'popup_border', [])
+ g:helptoc.popup_borderchars = get(g:helptoc, 'popup_borderchars',
+ ['─', '│', '─', '│', '┌', '┐', '┘', '└'])
+ g:helptoc.popup_borderhighlight = get(g:helptoc, 'popup_borderhighlight',
+ [])
+ g:helptoc.popup_drag = get(g:helptoc, 'popup_drag', true)
+ g:helptoc.popup_close = get(g:helptoc, 'popup_close', 'none')
+ g:helptoc.popup_scrollbar = get(g:helptoc, 'popup_scrollbar', false)
+ # For sanitized tocs, allow the user to specify the level indicator
+ g:helptoc.level_indicator = get(g:helptoc, 'level_indicator', '| ')
enddef
UpdateUserSettings()
-# Init {{{1
+# Syntax {{{1
+# Used by sanitized tocs (asciidoc, html, markdown, tex, vim, and xhtml)
+def SanitizedTocSyntax(): void
+ silent execute "syntax match helptocLevel _^\\(" ..
+ g:helptoc.level_indicator .. "\\)*_ contained"
+ silent execute "syntax region helptocText start=_^\\(" ..
+ g:helptoc.level_indicator .. "\\)*_ end=_$_ contains=helptocLevel"
+ highlight link helptocText Normal
+ highlight link helptocLevel NonText
+enddef
+
+# Init {{{1
+# Constants {{{2
+# HELP_TEXT {{{3
const HELP_TEXT: list<string> =<< trim END
normal commands in help window
──────────────────────────────
@@ -73,38 +120,108 @@
more context for each remaining entry by pressing J or K
END
+# UPTOINC_H {{{3
+const UPTOINC_H: string = '\v\c^%(%([<][^h][^>]*[>])|\s)*[<]h'
+
+# MATCH_ENTRY {{{3
const MATCH_ENTRY: dict<dict<func: bool>> = {
+
help: {},
- man: {
- 1: (line: string, _): bool => line =~ '^\S',
- 2: (line: string, _): bool => line =~ '^\%( \{3\}\)\=\S',
- 3: (line: string, _): bool => line =~ '^\s\+\(\%(+\|-\)\S\+,\s\+\)*\%(+\|-\)\S\+',
+ # For asciidoc, these patterns should match:
+ # https://docs.asciidoctor.org/asciidoc/latest/sections/titles-and-levels/
+ asciidoc: {
+ 1: (l: string, _): bool => l =~ '\v^%(\=|#)\s',
+ 2: (l: string, _): bool => l =~ '\v^%(\={2}|#{2})\s',
+ 3: (l: string, _): bool => l =~ '\v^%(\={3}|#{3})\s',
+ 4: (l: string, _): bool => l =~ '\v^%(\={4}|#{4})\s',
+ 5: (l: string, _): bool => l =~ '\v^%(\={5}|#{5})\s',
+ 6: (l: string, _): bool => l =~ '\v^%(\={6}|#{6})\s',
},
+ html: {
+ 1: (l: string, _): bool => l =~ $"{UPTOINC_H}1",
+ 2: (l: string, _): bool => l =~ $"{UPTOINC_H}2",
+ 3: (l: string, _): bool => l =~ $"{UPTOINC_H}3",
+ 4: (l: string, _): bool => l =~ $"{UPTOINC_H}4",
+ 5: (l: string, _): bool => l =~ $"{UPTOINC_H}5",
+ 6: (l: string, _): bool => l =~ $"{UPTOINC_H}6",
+ },
+
+ man: {
+ 1: (l: string, _): bool => l =~ '^\S',
+ 2: (l: string, _): bool => l =~ '\v^%( {3})=\S',
+ 3: (l: string, _): bool => l =~ '\v^\s+%(%(\+|-)\S+,\s+)*(\+|-)\S+'
+ },
+
+ # For markdown, these patterns should match:
+ # https://spec.commonmark.org/0.31.2/#atx-headings and
+ # https://spec.commonmark.org/0.31.2/#setext-headings
markdown: {
- 1: (line: string, nextline: string): bool =>
- (line =~ '^#[^#]' || nextline =~ '^=\+$') && line =~ '\w',
- 2: (line: string, nextline: string): bool =>
- (line =~ '^##[^#]' || nextline =~ '^-\+$') && line =~ '\w',
- 3: (line: string, _): bool => line =~ '^###[^#]',
- 4: (line: string, _): bool => line =~ '^####[^#]',
- 5: (line: string, _): bool => line =~ '^#####[^#]',
- 6: (line: string, _): bool => line =~ '^######[^#]',
+ 1: (l: string, nextline: string): bool =>
+ (l =~ '\v^ {0,3}#%(\s|$)' || nextline =~ '\v^ {0,3}\=+$') &&
+ l =~ '\S',
+ 2: (l: string, nextline: string): bool =>
+ (l =~ '\v^ {0,3}##%(\s|$)' || nextline =~ '\v^ {0,3}-+$') &&
+ l =~ '\S',
+ 3: (l: string, _): bool => l =~ '\v {0,3}#{3}%(\s|$)',
+ 4: (l: string, _): bool => l =~ '\v {0,3}#{4}%(\s|$)',
+ 5: (l: string, _): bool => l =~ '\v {0,3}#{5}%(\s|$)',
+ 6: (l: string, _): bool => l =~ '\v {0,3}#{6}%(\s|$)',
},
terminal: {
- 1: (line: string, _): bool => line =~ SHELL_PROMPT,
+ 1: (l: string, _): bool => l =~ g:helptoc.shell_prompt
+ },
+
+ # For LaTeX, this should meet
+ # https://mirrors.rit.edu/CTAN/info/latex2e-help-texinfo/latex2e.pdf
+ # including:
+ # para 6.3:
+ # \section{Heading}
+ # \section[Alternative ToC Heading]{Heading}
+ # para 25.1.2:
+ # \section*{Not for the TOC heading}
+ # \addcontentsline{toc}{section}{Alternative ToC Heading}
+ tex: {
+ 1: (l: string, _): bool => l =~ '^[\\]\(\%(part\|chapter\)' ..
+ '\%([\u005B{]\)\|addcontentsline{toc}{\%(part\|chapter\)\)',
+ 2: (l: string, _): bool => l =~ '^[\\]\%(section' ..
+ '\%([\u005B{]\)\|addcontentsline{toc}{section}\)',
+ 3: (l: string, _): bool => l =~ '^[\\]\%(subsection' ..
+ '\%([\u005B{]\)\|addcontentsline{toc}{subsection}\)',
+ 4: (l: string, _): bool => l =~ '^[\\]\%(subsubsection' ..
+ '\%([\u005B{]\)\|addcontentsline{toc}{subsubsection}\)',
+ },
+
+ vim: {
+ 1: (l: string, _): bool => l =~ '\v\{{3}1',
+ 2: (l: string, _): bool => l =~ '\v\{{3}2',
+ 3: (l: string, _): bool => l =~ '\v\{{3}3',
+ 4: (l: string, _): bool => l =~ '\v\{{3}4',
+ 5: (l: string, _): bool => l =~ '\v\{{3}5',
+ 6: (l: string, _): bool => l =~ '\v\{{3}6',
+ },
+
+ xhtml: {
+ 1: (l: string, _): bool => l =~ $"{UPTOINC_H}1",
+ 2: (l: string, _): bool => l =~ $"{UPTOINC_H}2",
+ 3: (l: string, _): bool => l =~ $"{UPTOINC_H}3",
+ 4: (l: string, _): bool => l =~ $"{UPTOINC_H}4",
+ 5: (l: string, _): bool => l =~ $"{UPTOINC_H}5",
+ 6: (l: string, _): bool => l =~ $"{UPTOINC_H}6",
}
}
+# HELP_RULERS {{{3
const HELP_RULERS: dict<string> = {
'=': '^=\{40,}$',
'-': '^-\{40,}',
}
const HELP_RULER: string = HELP_RULERS->values()->join('\|')
-# the regex is copied from the help syntax plugin
+# HELP_TAG {{{3
+# The regex is copied from the help syntax plugin
const HELP_TAG: string = '\*[#-)!+-~]\+\*\%(\s\|$\)\@='
# Adapted from `$VIMRUNTIME/syntax/help.vim`.{{{
@@ -113,13 +230,15 @@
#
# ^[-A-Z .][-A-Z0-9 .()_]*\ze\(\s\+\*\|$\)
#
-# Allowing a space or a hyphen at the start can give false positives, and is
+# Allowing a space or a hyphen at the start can give false positives, and is
# useless, so we don't allow them.
#}}}
+
+# HELP_HEADLINE {{{3
const HELP_HEADLINE: string = '^\C[A-Z.][-A-Z0-9 .()_]*\%(\s\+\*+\@!\|$\)'
# ^--^
# To prevent some false positives under `:help feature-list`.
-
+# Others {{{2
var lvls: dict<number>
def InitHelpLvls()
lvls = {
@@ -133,7 +252,6 @@
}
enddef
-const AUGROUP: string = 'HelpToc'
var fuzzy_entries: list<dict<any>>
var help_winid: number
var print_entry: bool
@@ -141,11 +259,11 @@
# Interface {{{1
export def Open() #{{{2
- var type: string = GetType()
- if !MATCH_ENTRY->has_key(type)
+ g:helptoc.type = GetType()
+ if !MATCH_ENTRY->has_key(g:helptoc.type)
return
endif
- if type == 'terminal' && win_gettype() == 'popup'
+ if g:helptoc.type == 'terminal' && win_gettype() == 'popup'
# trying to deal with a popup menu on top of a popup terminal seems
# too tricky for now
echomsg 'does not work in a popup window; only in a regular window'
@@ -158,7 +276,7 @@
if exists('b:toc') && &filetype != 'man'
if b:toc.changedtick != b:changedtick
# in a terminal buffer, `b:changedtick` does not change
- || type == 'terminal' && line('$') > b:toc.linecount
+ || g:helptoc.type == 'terminal' && line('$') > b:toc.linecount
unlet! b:toc
endif
endif
@@ -187,66 +305,257 @@
line: winpos[0],
col: winpos[1] + width - 1,
pos: 'topright',
- scrollbar: false,
- highlight: type == 'terminal' ? 'Terminal' : 'Normal',
- border: [],
- borderchars: ['─', '│', '─', '│', '┌', '┐', '┘', '└'],
+ highlight: g:helptoc.type == 'terminal' ? 'Terminal' : 'Normal',
minheight: height,
maxheight: height,
minwidth: b:toc.width,
maxwidth: b:toc.width,
filter: Filter,
callback: Callback,
+ border: g:helptoc.popup_border,
+ borderchars: g:helptoc.popup_borderchars,
+ borderhighlight: g:helptoc.popup_borderhighlight,
+ close: g:helptoc.popup_close,
+ drag: g:helptoc.popup_drag,
+ scrollbar: g:helptoc.popup_scrollbar,
})
- Win_execute(winid, [$'ownsyntax {&filetype}', '&l:conceallevel = 3'])
+ # Specify filetypes using sanitized toc syntax{{{
+ # Those filetypes have a normalized toc structure. The top level is
+ # unprefixed and levels 2 to 6 are prefixed, by default, with a vertical
+ # line and space for each level below 1:
+ # Level 1
+ # | Level 2
+ # ...
+ # | | | | | Level 6 }}}
+ final SanitizedTocSyntaxTypes: list<string> =
+ ['asciidoc', 'html', 'markdown', 'tex', 'vim', 'xhtml']
+ if index(SanitizedTocSyntaxTypes, g:helptoc.type) != -1
+ # Specified types' toc popups use a common syntax
+ Win_execute(winid, 'SanitizedTocSyntax()')
+ else
+ # Other types' toc popups use the same syntax as the buffer itself
+ Win_execute(winid, [$'ownsyntax {&filetype}', '&l:conceallevel = 3'])
+ endif
# In a help file, we might reduce some noisy tags to a trailing asterisk.
# Hide those.
- if type == 'help'
+ if g:helptoc.type == 'help'
matchadd('Conceal', '\*$', 0, -1, {window: winid})
endif
SelectNearestEntryFromCursor(winid)
- # can't set the title before jumping to the relevant line, otherwise the
+ # Can't set the title before jumping to the relevant line, otherwise the
# indicator in the title might be wrong
SetTitle(winid)
enddef
-#}}}1
+
# Core {{{1
def SetToc() #{{{2
- var toc: dict<any> = {entries: []}
- var type: string = GetType()
+ # Lambdas:
+ # CHARACTER_REFERENCES_TO_CHARACTERS {{{3
+ # These are used for AsciiDoc, Markdown, and [X]HTML, all of which allow
+ # for decimal, hexadecimal, and XML predefined entities.
+ # Decimal character references: e.g., § to §
+ # Hexadecimal character references: e.g., § to §
+ # XML predefined entities to chars: e.g., < to <
+ # All HTML5 named character references could be handled, though is that
+ # warranted for the few that may appear in a toc entry, especially when
+ # they are often mnemonic? Future: A common Vim dict/enum could be useful?
+ const CHARACTER_REFERENCES_TO_CHARACTERS = (text: string): string =>
+ text->substitute('\v\�*([1-9]\d{0,6});',
+ '\=nr2char(str2nr(submatch(1), 10), 1)', 'g')
+ ->substitute('\c\v\�*([1-9a-f][[:xdigit:]]{1,5});',
+ '\=nr2char(str2nr(submatch(1), 16), 1)', 'g')
+ ->substitute('\C&', '\="\u0026"', 'g')
+ ->substitute('\C'', "\u0027", 'g')
+ ->substitute('\C>', "\u003E", 'g')
+ ->substitute('\C<', "\u003C", 'g')
+ ->substitute('\C"', "\u0022", 'g')
+
+ # SANITIZE_ASCIIDOC {{{3
+ # 1 - Substitute the = or # heading markup with the level indicator
+ # 2 - Substitute XML predefined, dec, and hex char refs in the entry
+ # AsciiDoc recommends only using named char refs defined in XML:
+ # https://docs.asciidoctor.org/asciidoc/latest/subs/replacements/
+ const SANITIZE_ASCIIDOC = (text: string): string =>
+ text->substitute('\v^(\={1,6}|#{1,6})\s+',
+ '\=repeat(g:helptoc.level_indicator, len(submatch(1)) - 1)', '')
+ ->CHARACTER_REFERENCES_TO_CHARACTERS()
+
+ # SANITIZE_HTML {{{3
+ # 1 - Remove any leading spaces or tabs
+ # 2 - Remove any <!--HTML comments-->
+ # 3 - Remove any <?processing_instructions?>
+ # 4 - Remove any leading tags (and any blanks) other than <h1 to <h6
+ # 5 - Remove any persisting leading blanks
+ # 6 - Handle empty XHTML headings, e.g., <h6 />
+ # 7 - Remove trailing content following the </h[1-6]>
+ # 8 - Remove the <h1
+ # 9 - Substitute the h2 to h6 heading tags with level indicator/level
+ # 10 - Remove intra-heading tags like <em>, </em>, <strong>, etc.
+ # 11 - Substitute XML predefined, dec and hex character references
+ const SANITIZE_HTML = (text: string): string =>
+ text->substitute('^\s*', '', '')
+ ->substitute('[<]!--.\{-}--[>]', '', 'g')
+ ->substitute('[<]?[^?]\+?[>]', '', 'g')
+ ->substitute('\v%([<][^Hh][^1-6]?[^>][>])*\s*', '', '')
+ ->substitute('^\s\+', '', '')
+ ->substitute('\v[<][Hh]([1-6])\s*[/][>].*',
+ '\=repeat(g:helptoc.level_indicator, ' ..
+ 'str2nr(submatch(1)) - 1) ' ..
+ '.. "[Empty heading " .. submatch(1) .. "]"', '')
+ ->substitute('[<][/][Hh][1-6][>].*$', '', '')
+ ->substitute('[<][Hh]1[^>]*[>]', '', '')
+ ->substitute('\v[<][Hh]([2-6])[^>]*[>]',
+ '\=repeat(g:helptoc.level_indicator, ' ..
+ 'str2nr(submatch(1)) - 1)', '')
+ ->substitute('[<][/]\?[[:alpha:]][^>]*[>]', '', 'g')
+ ->CHARACTER_REFERENCES_TO_CHARACTERS()
+
+ # SANITIZE_MARKDOWN #{{{3
+ # 1 - Hyperlink incl image, e.g. [](\uri), to Vim...
+ # 2 - Hyperlink [text](/uri) to text
+ # 3 - Substitute the # ATX heading markup with the level indicator/level
+ # The omitted markup reflects CommonMark Spec:
+ # https://spec.commonmark.org/0.31.2/#atx-headings
+ # 4 - Substitute decimal, hexadecimal, and XML predefined char refs
+ const SANITIZE_MARKDOWN = (text: string): string =>
+ text->substitute('\v[\u005B][\u005D]'
+ .. '[(][^)]+[)][\u005D][(][^)]+[)]', '\1', '')
+ ->substitute('\v[\u005B]([^\u005D]+)[\u005D][(][^)]+[)]',
+ '\1', '')
+ ->substitute('\v^ {0,3}(#{1,6})\s*',
+ '\=repeat(g:helptoc.level_indicator, len(submatch(1)) - 1)',
+ '')
+ ->CHARACTER_REFERENCES_TO_CHARACTERS()
+
+ # SANITIZE_TERMINAL {{{3
+ # Omit the prompt, which may be very long and otherwise just adds clutter
+ const SANITIZE_TERMINAL = (text: string): string =>
+ text->substitute('^' .. g:helptoc.shell_prompt, '', '')
+
+ # SANITIZE_TEX #{{{3
+ # 1 - Use any [toc-title] overrides to move its content into the
+ # {heading} instead of the (non-ToC) heading's text
+ # 2 - Replace \part{ or \addcontentsline{toc}{part} with '[PART] '
+ # 3 - Omit \chapter{ or \addcontentsline{toc}{chapter}
+ # 4 - Omit \section{ or \addcontentsline{toc}{section}
+ # 5 - Omit \subsection{ or \addcontentsline{toc}{subsection}
+ # 6 - Omit \subsubsection{ or \addcontentsline{toc}{subsubsection}
+ # 7 - Omit the trailing }
+ # 8 - Unescape common escaped characters &%$_#{}~^\
+ const SANITIZE_TEX = (text: string): string =>
+ text->substitute('\v^[\\](part|chapter|%(sub){0,2}section)' ..
+ '[\u005B]([^\u005D]+).*', '\\\1{\2}', '')
+ ->substitute('^[\\]\(part\|addcontentsline{toc}{part}\){',
+ '[PART] ', '')
+ ->substitute('^[\\]\(chapter\|addcontentsline{toc}{chapter}\){',
+ '', '')
+ ->substitute('^[\\]\(section\|addcontentsline{toc}{section}\){',
+ '\=g:helptoc.level_indicator', '')
+ ->substitute('^[\\]\(subsection\|' ..
+ 'addcontentsline{toc}{subsection}\){',
+ '\=repeat(g:helptoc.level_indicator, 2)', '')
+ ->substitute('^[\\]\(subsubsection\|' ..
+ 'addcontentsline{toc}{subsubsection}\){',
+ '\=repeat(g:helptoc.level_indicator, 3)', '')
+ ->substitute('}[^}]*$', '', '')
+ ->substitute('\\\([&%$_#{}~\\^]\)', '\1', 'g')
+
+ # SANITIZE_VIM {{{3
+ # #1 - Omit leading Vim9 script # or vimscript " markers and blanks
+ # #2 - Omit numbered 3x { markers
+ const SANITIZE_VIM = (text: string): string =>
+ text->substitute('\v^[#[:blank:]"]*(.+)\ze[{]{3}([1-6])',
+ '\=submatch(2) == "1" ? submatch(1) : ' ..
+ 'repeat(g:helptoc.level_indicator, str2nr(submatch(2)) - 1)' ..
+ ' .. submatch(1)', 'g')
+ ->substitute('[#[:blank:]"]*{\{3}[1-6]', '', '')
+ #}}}3
+
+ final toc: dict<any> = {entries: []}
toc.changedtick = b:changedtick
if !toc->has_key('width')
toc.width = 0
endif
# We cache the toc in `b:toc` to get better performance.{{{
#
- # Without caching, when we press `H`, `L`, `H`, `L`, ... quickly for a few
+ # Without caching, when we press `H`, `L`, `H`, `L`, ... quickly for a few
# seconds, there is some lag if we then try to move with `j` and `k`.
# This can only be perceived in big man pages like with `:Man ffmpeg-all`.
#}}}
b:toc = toc
- if type == 'help'
+ if g:helptoc.type == 'help'
SetTocHelp()
return
endif
- if type == 'terminal'
+ if g:helptoc.type == 'terminal'
b:toc.linecount = line('$')
endif
var curline: string = getline(1)
var nextline: string
var lvl_and_test: list<list<any>> = MATCH_ENTRY
- ->get(type, {})
+ ->get(g:helptoc.type, {})
->items()
- ->sort((l: list<any>, ll: list<any>): number => l[0]->str2nr() - ll[0]->str2nr())
+ ->sort((l: list<any>, ll: list<any>): number =>
+ l[0]->str2nr() - ll[0]->str2nr())
+ var skip_next: bool = false
+
+ # Non-help headings processing
for lnum: number in range(1, line('$'))
+ if skip_next
+ skip_next = false
+ curline = nextline
+ continue
+ endif
+
nextline = getline(lnum + 1)
+
+ # Special handling for markdown filetype using setext headings
+ if g:helptoc.type == 'markdown'
+ # Check for setext formatted headings (= or - underlined)
+ if nextline =~ '^\s\{0,3}=\+$' && curline =~ '\S'
+ # Level 1 heading (one or more =, up to three spaces preceding)
+ b:toc.entries->add({
+ lnum: lnum,
+ lvl: 1,
+ text: SANITIZE_MARKDOWN('# ' .. trim(curline)),
+ })
+ skip_next = true
+ curline = nextline
+ continue
+ elseif nextline =~ '^\s\{0,3}-\+$' && curline =~ '\S'
+ # Level 2 heading (one or more -, up to three spaces preceding)
+ b:toc.entries->add({
+ lnum: lnum,
+ lvl: 2,
+ text: SANITIZE_MARKDOWN('## ' .. trim(curline)),
+ })
+ skip_next = true
+ curline = nextline
+ continue
+ endif
+ endif
+
+ # Regular processing for markdown ATX-style headings + other filetypes
for [lvl: string, IsEntry: func: bool] in lvl_and_test
if IsEntry(curline, nextline)
+ if g:helptoc.type == 'asciidoc'
+ curline = curline->SANITIZE_ASCIIDOC()
+ elseif g:helptoc.type == 'html' || g:helptoc.type == 'xhtml'
+ curline = curline->SANITIZE_HTML()
+ elseif g:helptoc.type == 'markdown'
+ curline = curline->SANITIZE_MARKDOWN()
+ elseif g:helptoc.type == 'terminal'
+ curline = curline->SANITIZE_TERMINAL()
+ elseif g:helptoc.type == 'tex'
+ curline = curline->SANITIZE_TEX()
+ elseif g:helptoc.type == 'vim'
+ curline = curline->SANITIZE_VIM()
+ endif
b:toc.entries->add({
lnum: lnum,
lvl: lvl->str2nr(),
@@ -281,9 +590,9 @@
if main_ruler != '' && curline =~ main_ruler
last_numbered_entry = 0
- # The information gathered in `lvls` might not be applicable to all
- # the main sections of a help file. Let's reset it whenever we find
- # a ruler.
+ # The information gathered in `lvls` might not be applicable to
+ # all the main sections of a help file. Let's reset it whenever
+ # we find a ruler.
InitHelpLvls()
endif
@@ -304,7 +613,7 @@
# 1.
if prevline =~ '^\d\+\.\s'
- # let's assume that the start of a main entry is always followed by an
+ # Let's assume that the start of a main entry is always followed by an
# empty line, or a line starting with a tag
&& (curline =~ '^>\=\s*$' || curline =~ $'^\s*{HELP_TAG}')
# ignore a numbered line in a list
@@ -337,7 +646,8 @@
if curline =~ HELP_HEADLINE
&& curline !~ '^CTRL-'
&& prevline->IsSpecialHelpLine()
- && (nextline->IsSpecialHelpLine() || nextline =~ '^\s*(\|^\t\|^N[oO][tT][eE]:')
+ && (nextline ->IsSpecialHelpLine()
+ || nextline =~ '^\s*(\|^\t\|^N[oO][tT][eE]:')
AddEntryInTocHelp('HEADLINE', lnum, curline)
endif
@@ -411,7 +721,8 @@
->min()
for entry: dict<any> in b:toc.entries
entry.text = entry.text
- ->substitute('^\s*', () => repeat(' ', (entry.lvl - min_lvl) * 3), '')
+ ->substitute('^\s*', () =>
+ repeat(' ', (entry.lvl - min_lvl) * 3), '')
endfor
enddef
@@ -455,29 +766,30 @@
# Ignore noisy tags.{{{
#
- # 14. Linking groups *:hi-link* *:highlight-link* *E412* *E413*
- # ^----------------------------------------^
- # ^\s*\d\+\.\%(\d\+\.\=\)*\s\+.\{-}\zs\*.*
+ # 14. Linking groups *:hi-link* *:highlight-link* *E412* *E413*
+ # ^----------------------------------------^
+ # ^\s*\d\+\.\%(\d\+\.\=\)*\s\+.\{-}\zs\*.*
# ---
#
- # We don't use conceal because then, `matchfuzzypos()` could match concealed
- # characters, which would be confusing.
+ # We don't use conceal because then, `matchfuzzypos()` could match
+ # concealed characters, which would be confusing.
#}}}
- # MAKING YOUR OWN SYNTAX FILES *mysyntaxfile*
- # ^------------^
- # ^\s*[A-Z].\{-}\*\zs.*
+ # MAKING YOUR OWN SYNTAX FILES *mysyntaxfile*
+ # ^------------^
+ # ^\s*[A-Z].\{-}\*\zs.*
#
var after_HEADLINE: string = '^\s*[A-Z].\{-}\*\zs.*'
- # 14. Linking groups *:hi-link* *:highlight-link* *E412* *E413*
- # ^----------------------------------------^
- # ^\s*\d\+\.\%(\d\+\.\=\)*\s\+.\{-}\*\zs.*
+ # 14. Linking groups *:hi-link* *:highlight-link* *E412* *E413*
+ # ^----------------------------------------^
+ # ^\s*\d\+\.\%(\d\+\.\=\)*\s\+.\{-}\*\zs.*
var after_numbered: string = '^\s*\d\+\.\%(\d\+\.\=\)*\s\+.\{-}\*\zs.*'
- # 01.3 Using the Vim tutor *tutor* *vimtutor*
- # ^----------------^
+ # 01.3 Using the Vim tutor *tutor* *vimtutor*
+ # ^----------------^
var after_numbered_tutor: string = '^\*\d\+\.\%(\d\+\.\=\)*.\{-}\t\*\zs.*'
- var noisy_tags: string = $'{after_HEADLINE}\|{after_numbered}\|{after_numbered_tutor}'
+ var noisy_tags: string =
+ $'{after_HEADLINE}\|{after_numbered}\|{after_numbered_tutor}'
text = text->substitute(noisy_tags, '', '')
- # We don't remove the trailing asterisk, because the help syntax plugin
+ # We don't remove the trailing asterisk, because the help syntax plugin
# might need it to highlight some headlines.
b:toc.entries->add({
@@ -498,7 +810,7 @@
def Popup_settext(winid: number, entries: list<dict<any>>) #{{{2
var text: list<any>
- # When we fuzzy search the toc, the dictionaries in `entries` contain a
+ # When we fuzzy search the toc, the dictionaries in `entries` contain a
# `props` key, to highlight each matched character individually.
# We don't want to process those dictionaries further.
# The processing should already have been done by the caller.
@@ -544,7 +856,8 @@
var lnum: number = line('.')
var firstline: number = b:toc.entries
->copy()
- ->filter((_, line: dict<any>): bool => line.lvl <= b:toc.curlvl && line.lnum <= lnum)
+ ->filter((_, line: dict<any>): bool =>
+ line.lvl <= b:toc.curlvl && line.lnum <= lnum)
->len()
if firstline == 0
return
@@ -599,8 +912,8 @@
if key == 'J' || key == 'K'
var lnum: number = GetBufLnum(winid)
execute $'normal! 0{lnum}zt'
- # install a match in the regular buffer to highlight the position of
- # the entry in the latter
+ # Install a match in the regular buffer to highlight the position
+ # of the entry in the latter
MatchDelete()
selected_entry_match = matchaddpos('IncSearch', [lnum], 0, -1)
endif
@@ -665,41 +978,44 @@
return true
elseif key == '/'
- # This is probably what the user expect if they've started a first fuzzy
- # search, press Escape, then start a new one.
+ # This is probably what the user expects if they've started a first
+ # fuzzy search, press Escape, then start a new one.
DisplayNonFuzzyToc(winid)
[{
- group: AUGROUP,
+ group: 'HelpToc',
event: 'CmdlineChanged',
pattern: '@',
cmd: $'FuzzySearch({winid})',
replace: true,
}, {
- group: AUGROUP,
+ group: 'HelpToc',
event: 'CmdlineLeave',
pattern: '@',
cmd: 'TearDown()',
replace: true,
}]->autocmd_add()
- # Need to evaluate `winid` right now with an `eval`'ed and `execute()`'ed heredoc because:{{{
+ # Need to evaluate `winid` right now{{{
+ # with an `eval`'ed and `execute()`'ed heredoc because:
#
- # - the mappings can only access the script-local namespace
- # - `winid` is in the function namespace; not in the script-local one
+ # - the mappings can only access the script-local namespace
+ # - `winid` is in the function namespace; not in the script-local one
#}}}
var input_mappings: list<string> =<< trim eval END
- cnoremap <buffer><nowait> <Down> <ScriptCmd>Filter({winid}, 'j')<CR>
- cnoremap <buffer><nowait> <Up> <ScriptCmd>Filter({winid}, 'k')<CR>
- cnoremap <buffer><nowait> <C-N> <ScriptCmd>Filter({winid}, 'j')<CR>
- cnoremap <buffer><nowait> <C-P> <ScriptCmd>Filter({winid}, 'k')<CR>
+ cnoremap <buffer><nowait> <Down> <ScriptCmd>Filter({winid}, 'j')<CR>
+ cnoremap <buffer><nowait> <Up> <ScriptCmd>Filter({winid}, 'k')<CR>
+ cnoremap <buffer><nowait> <C-N> <ScriptCmd>Filter({winid}, 'j')<CR>
+ cnoremap <buffer><nowait> <C-P> <ScriptCmd>Filter({winid}, 'k')<CR>
END
input_mappings->execute()
var look_for: string
try
popup_setoptions(winid, {mapping: true})
- look_for = input('look for: ', '', $'custom,{Complete->string()}') | redraw | echo ''
+ look_for = input('look for: ', '', $'custom,{Complete->string()}')
+ | redraw
+ | echo ''
catch /Vim:Interrupt/
TearDown()
finally
@@ -718,9 +1034,9 @@
return
endif
- # We match against *all* entries; not just the currently visible ones.
- # Rationale: If we use a (fuzzy) search, we're probably lost. We don't know
- # where the info is.
+ # We match against *all* entries; not just the currently visible ones.
+ # Rationale: If we use a (fuzzy) search, we're probably lost. We don't
+ # know where the info is.
var matches: list<list<any>> = b:toc.entries
->copy()
->matchfuzzypos(look_for, {key: 'text'})
@@ -764,7 +1080,7 @@
enddef
def CollapseOrExpand(winid: number, key: string) #{{{2
- # Must be saved before we reset the popup contents, so we can
+ # Must be saved before we reset the popup contents, so we can
# automatically select the least unexpected entry in the updated popup.
var buf_lnum: number = GetBufLnum(winid)
@@ -798,11 +1114,11 @@
endwhile
endif
- # update the popup contents
+ # Update the popup contents
var toc_entries: list<dict<any>> = GetTocEntries()
Popup_settext(winid, toc_entries)
- # Try to select the same entry; if it's no longer visible, select its
+ # Try to select the same entry; if it's no longer visible, select its
# direct parent.
var toc_lnum: number = 0
for entry: dict<any> in toc_entries
@@ -834,6 +1150,8 @@
if choice == -1
fuzzy_entries = null_list
return
+ elseif choice == -2 # Button X is clicked (when close: 'button')
+ return
endif
var lnum: number = GetTocEntries()
@@ -851,36 +1169,38 @@
enddef
def ToggleHelp(menu_winid: number) #{{{2
+ # Show/hide HELP_TEXT in a second popup when '?' is typed{{{
+ # (when a helptoc popup is open). A scrollbar on this popup makes sense
+ # because it is very long and, even if it's not used for scrolling, works
+ # well as an indicator of how far through the HELP_TEXT popup you are. }}}
if help_winid == 0
var height: number = [HELP_TEXT->len(), winheight(0) * 2 / 3]->min()
var longest_line: number = HELP_TEXT
->copy()
->map((_, line: string) => line->strcharlen())
->max()
- var width: number = [longest_line, winwidth(0) * 2 / 3]->min()
- var pos: dict<number> = popup_getpos(menu_winid)
- var [line: number, col: number] = [pos.line, pos.col]
- --col
+ var width: number = [longest_line, winwidth(0) - 4]->min()
var zindex: number = popup_getoptions(menu_winid).zindex
++zindex
help_winid = HELP_TEXT->popup_create({
- line: line,
- col: col,
- pos: 'topright',
+ pos: 'center',
minheight: height,
maxheight: height,
minwidth: width,
maxwidth: width,
- border: [],
- borderchars: ['─', '│', '─', '│', '┌', '┐', '┘', '└'],
highlight: &buftype == 'terminal' ? 'Terminal' : 'Normal',
- scrollbar: false,
zindex: zindex,
+ border: g:helptoc.popup_border,
+ borderchars: g:helptoc.popup_borderchars,
+ borderhighlight: g:helptoc.popup_borderhighlight,
+ close: g:helptoc.popup_close,
+ scrollbar: true,
})
setwinvar(help_winid, '&cursorline', true)
setwinvar(help_winid, '&linebreak', true)
- matchadd('Special', '^<\S\+\|^\S\{,2} \@=', 0, -1, {window: help_winid})
+ matchadd('Special', '^<\S\+\|^\S\{,2} \@=', 0, -1,
+ {window: help_winid})
matchadd('Number', '\d\+', 0, -1, {window: help_winid})
for lnum: number in HELP_TEXT->len()->range()
if HELP_TEXT[lnum] =~ '^─\+$'
@@ -898,23 +1218,22 @@
enddef
def Win_execute(winid: number, cmd: any) #{{{2
-# wrapper around `win_execute()` to enforce a redraw, which might be necessary
+# wrapper around `win_execute()` to enforce a redraw, which might be necessary
# whenever we change the cursor position
win_execute(winid, cmd)
redraw
enddef
def TearDown() #{{{2
- autocmd_delete([{group: AUGROUP}])
+ autocmd_delete([{group: 'HelpToc'}])
cunmap <buffer> <Down>
cunmap <buffer> <Up>
cunmap <buffer> <C-N>
cunmap <buffer> <C-P>
enddef
-#}}}1
# Util {{{1
def GetType(): string #{{{2
- return &buftype == 'terminal' ? 'terminal' : &filetype
+ return &buftype == 'terminal' ? 'terminal' : &filetype
enddef
def GetTocEntries(): list<dict<any>> #{{{2
@@ -944,10 +1263,12 @@
def Complete(..._): string #{{{2
return b:toc.entries
->copy()
- ->map((_, entry: dict<any>) => entry.text->trim(' ~')->substitute('*', '', 'g'))
+ ->map((_, entry: dict<any>) =>
+ entry.text->trim(' ~')->substitute('*', '', 'g'))
->filter((_, text: string): bool => text =~ '^[-a-zA-Z0-9_() ]\+$')
->sort()
->uniq()
->join("\n")
-enddef
-
+enddef #}}}2
+#}}}1
+# vim:et:ft=vim:fdm=marker:
diff --git a/runtime/pack/dist/opt/helptoc/doc/helptoc.txt b/runtime/pack/dist/opt/helptoc/doc/helptoc.txt
new file mode 100644
index 0000000..4bd87e6
--- /dev/null
+++ b/runtime/pack/dist/opt/helptoc/doc/helptoc.txt
@@ -0,0 +1,349 @@
+*helptoc.txt* For Vim version 9.1. Last change: 2025 May 04
+
+
+ VIM REFERENCE MANUAL
+
+Interactive table of contents for help buffers and several other filetypes
+
+==============================================================================
+
+
+1. OVERVIEW *HelpToc-overview*
+
+The helptoc.vim plugin provides one command, :HelpToc, which generates a
+hierarchical table of contents in a popup window, which is based on the
+structure of a Vim buffer. It was designed initially for help buffers,
+but it also works with buffers of the following types:
+ - asciidoc
+ - html
+ - man
+ - markdown
+ - terminal
+ - tex
+ - vim Note: only with numbered fold markers, e.g. {{{1
+ - xhtml
+
+1.1 The :HelpToc command *HelpToc-:HelpToc*
+
+The :HelpToc command takes no arguments and it cannot be executed from an
+unsupported filetype. Also, it cannot be used to generate a table of contents
+for an inactive buffer.
+
+For most buffers of the supported types, :HelpToc may be entered directly
+in Vim's |Command-line-mode|. How to use it from Vim's |Terminal-Job| mode is
+explained in |HelpToc-terminal-buftype|.
+
+You may choose to map :HelpToc to keys making it easier to use. These are
+examples of what could be used: >
+
+ nnoremap <Leader>ht <Cmd>HelpToc<CR>
+ tnoremap <C-t><C-t> <Cmd>HelpToc<CR>
+<
+
+2. DETAILS *HelpToc-details*
+
+When the :HelpToc command is executed from an active buffer of a supported
+type, a popup window is produced. The window contains a hierarchical and
+interactive table of contents. The entries are based on the "headings" in
+the buffer.
+
+Jumping to an entry's position in the buffer can be achieved by pressing
+enter on the applicable entry. Navigation, and other commands applicable to
+the popup window, such as expanding and contracting levels, fuzzy searching,
+and jumping to the previous or next entry (leaving the table of contents
+itself displayed, using J and K), is provided at |help-TOC|, so that is not
+reproduced in this help file.
+
+
+3. TYPES *HelpToc-types*
+
+Some filetypes have more predictable structures than others. For example,
+markdown and asciidoc make the identification of headings (aside from edge
+cases, such as when in quotes) straightforward. Some filetypes do not have
+such obvious or reliable headings/levels (particularly help buffers).
+Further, in some instances, how to enter the :HelpToc command is not
+necessarily obvious, e.g., when in a Vim |terminal-window|. So, the following
+headings address specific details regarding the in-scope types.
+
+3.1 asciidoc *HelpToc-asciidoc-filetype*
+
+The heading levels in asciidoc are typically identified by lines starting
+with a "=" (up to six, one per level), one or more blank characters, and the
+heading text. :HelpToc will generate a table of contents based on either
+that form of heading type or, because asciidoc also allows for ATX heading
+level syntax (i.e., using the "#" character), headings using that format too.
+
+As asciidoc is very structured, its table of contents entries are presented
+in a standardized form - see |HelpToc-standardized-toc|. So, the initial
+"=" or "#" characters and the following space from the related heading are
+not included in the table of contents' entries.
+
+3.2 html *HelpToc-html-filetype*
+
+HTML provides for six levels of headings, <h1> to <h6>, which may be either
+upper or lower case and preceded by all sorts of content like <!--comments-->.
+:HelpToc will produce a table of contents based on the six heading levels.
+
+As HTML is very structured, its table of contents entries are presented
+in a standardized form - see |HelpToc-standardized-toc|. So, the <h1> to <h6>
+tags, any preceding content, and any trailing content after the heading text,
+is not included in the table of contents' entries.
+
+3.3 man pages *HelpToc-man-filetype*
+
+Retrieving man pages is typically performed in the terminal. To use :HelpToc
+to generate a table of contents, the |man.vim| filetype plugin is a
+prerequisite. It is provided with Vim, and may be sourced with: >
+
+ :runtime ftplugin/man.vim
+<
+Once sourced, the |:Man| command will open the applicable man page in a new
+buffer (of "man" filetype). For example: >
+
+ :Man pwd
+<
+Once in the man buffer, entering :HelpToc opens the table of contents popup,
+with level 1 containing section names like NAME, SYNOPSIS, etc. Levels
+below 1 include subsections and options, with the level depending on the
+number of spaces preceding the content.
+
+The table of contents for a man buffer's popup window has the same syntax
+highlighting as man pages. This reflects that its content is reproduced
+as-is, i.e., no preceding tags, level-indicating data, etc., need be omitted
+for optimal presentation.
+
+3.4 markdown *HelpToc-markdown-filetype*
+
+The headings and levels in markdown typically are ATX formatted headings
+(lines starting with up to three spaces, one to six "#", then (optionally)
+one or blank characters and the heading text). The alternate form,
+setext, uses up to three spaces, the heading 1 text, followed by a
+line of one or more "=". The setext heading 2 is similar, but for one or
+more "-" instead of "=". There is no heading 3+ in setext. ATX and setext
+headings are supported.
+
+As markdown is very structured, its table of contents entries are presented
+in a standardized form - see |HelpToc-standardized-toc|. So, they do not
+include any leading spaces, any initial "#" characters and the following blank
+character(s) in the table of contents' entries.
+
+3.5 terminal *HelpToc-terminal-buftype*
+
+There are no explicit "headings" for a terminal buffer. However, :HelpToc
+displays the history of executed shell commands. Those may be specified
+by changing the pattern used to match the Vim terminal's prompt.
+See |HelpToc-configuration| for examples.
+
+To access the terminal's table of contents, from the Vim's |Terminal-Job| mode
+enter CTRL-W N to go to |Terminal-Normal| mode. From there, enter :HelpToc to
+generate the table of contents. If you use the terminal's table of contents
+a lot, an appropriate mapping may make it easier than using CTRL-W N - e.g.: >
+
+ tnoremap <C-t><C-t> <Cmd>HelpToc<CR>
+<
+As the terminal has only "level 1", the table of contents is presented in a
+standardized form - see |HelpToc-standardized-toc| - including only the history
+list of commands. The prompt itself is also omitted since it adds no value
+repeating it for every entry.
+
+3.6 tex *HelpToc-tex-filetype*
+
+In LaTeX, a document may be structured hierarchically using part, chapter,
+and sectioning commands. Document structure levels are:
+ \part{}
+ \chapter{}
+ \section{}
+ \subsection{}
+ \subsubsection{}
+
+To keep things simpler, \part{} is supported, though treated as being at
+the same level as chapter. Consequently, there are four levels displayed
+for a tex filetype's table of contents, regardless of the \documentclass{},
+i.e., part and chapter (at level 1), section (level 2), subsection (level 3),
+and subsubsection (level 4).
+
+Also supported are:
+ - The "*" used to produce unnumbered headings, which are not intended
+ for reproduction in a table of contents: >
+ \section*{Unnumbered section heading not produced in the TOC}
+< - Manual toc entries using \addcontentsline, for example: >
+ \addcontentsline{toc}{section}{entry in the TOC only!}
+<
+The table of contents for a tex filetype is in a standardized form -
+see |HelpToc-standardized-toc|. Omitted are: the "\", the part, chapter,
+*section, or addcontentsline, and the left and right curly
+brackets preceding and following each heading's text.
+
+3.7 vim *HelpToc-vim-filetype*
+
+Vimscript and Vim9 script do not have headings or levels inherently like
+markup languages. However, Vim provides for |folds| defined by markers (|{{{|),
+which themselves may be succeeded by a number explicitly indicating the fold
+level. This is the structure recognized and supported by helptoc.vim.
+So, for example, the following would produce three table of contents entries: >
+
+ vim9script
+ # Variables {{{1
+ var b: bool = true
+ var s: string = $"Fold markers are great? {b}!"
+ # Functions {{{1
+ def MyFunction(): void #{{{2
+ echo s
+ enddef
+ MyFunction()
+<
+The table of contents for that script would appear like this:
+ Variables
+ Functions
+ | MyFunction(): void
+
+Note: The numbered |{{{| marker structure is the only one supported by
+ helptoc.vim for the vim filetype.
+
+As the {{{1 to {{{6 markers make the "headings" explicit, the table of
+contents is in a standardized form - see |HelpToc-standardized-toc|.
+It does not include any leading comment markers (i.e., either # or ") and
+omits the markers themselves.
+
+3.8 xhtml *HelpToc-xhtml-filetype*
+
+Although XHTML, being XML, is more strictly structured than HTML/HTML5,
+there is no practical difference in treatment required for the xhtml filetype
+because, at the heading level, the tags that matter are very similar.
+See |HelpToc-html-filetype| for how an xhtml filetype's table of contents is
+supported.
+
+
+4. STANDARDIZED TOC *HelpToc-standardized-toc*
+
+The table of contents for a help buffer, terminal, or man page, make sense
+being presented in the form they appear, minus trailing content (such as tags).
+
+The table of contents for a markdown, asciidoc, [x]html, terminal, or tex
+buffer have superfluous content if the entire line was to be returned.
+For example:
+- Markdown has "#" characters before headings when using ATX heading notation.
+- Asciidoc will have either those or, more often, "=" characters before its
+ headings.
+- HTML, aside from the "<h" headings, may have additional tags, comments,
+ and whitespace before its headings.
+- The Vim terminal has the shell prompt, which adds nothing if repeated for
+ every heading (and may be very long).
+- LaTeX has "\" level indicators like "\section{" and a trailing "}".
+Consequently, standardising these filetypes' tables of contents, removing
+the "noise", and indicating the contents level of each entry, makes sense.
+
+HelpToc standardizes the markdown, asciidoc, [x]html, terminal and tex tables
+of contents by removing extraneous characters, markup indicators, and tags.
+It also applies simple, unobtrusive syntax highlighting to the text and level
+indicators. By default, it will appear like the following example (though
+any level indicators will be less prominent, using |NonText| highlight group).
+
+ Level 1
+ | Level 2
+ | | Level 3
+ | | | Level 4
+ | | | | Level 5
+ | | | | | Level 6
+
+Note: The "| " level indicator may be changed - see |HelpToc-configuration|.
+
+
+5. CONFIGURATION *HelpToc-configuration*
+
+All configuration is achieved utilizing the g:helptoc dictionary. Any of the
+following may be adjusted to meet your needs or preferences:
+
+g:helptoc key what it controls
+------------- ----------------
+shell_prompt The terminal prompt, used for creating a table of contents
+ for the terminal (history list). The default is,
+ '^\w\+@\w\+:\f\+\$\s', which should match many users' bash
+ prompt. To change it, either interactively or in your .vimrc,
+ use (for example for a bare Bourne shell "$ " prompt): >
+ vim9 g:helptoc.shell_prompt = '^\$\s'
+
+<level_indicator This key's value controls the level indicator used in
+ standardized tables of contents. The default is '| '
+ (i.e., a vertical bar and a space), but may be changed to
+ whatever you want. For example, for a broken bar and space: >
+ vim9 g:helptoc.level_indicator = '¦ '
+<
+popup_border By default, the table of contents border will appear above,
+ right, below, and left of the popup window. If you prefer
+ not to have the border on the right and left (for example
+ only), you can achieve that with: >
+ vim9 g:helptoc.popup_border = [1, 0, 1, 0]
+<popup_borderchars
+ The default border characters for the table of contents popup
+ window is the list ['─', '│', '─', '│', '┌', '┐', '┘', '└'].
+ There's nothing wrong with those box drawing characters,
+ though, for example, if you wanted a border that only uses
+ ASCII characters, you could make the border spaces only: >
+ vim9 g:helptoc.popup_borderchars = [' ']
+<popup_borderhighlight
+ The default border highlight group is Normal. You can change
+ that, perhaps in combination with popup_borderchars, above,
+ to create a very clearly prominent border. For example, if
+ the popup_borderchars are made [' '], like above, the border
+ could be made a solid colour different to the background
+ with: >
+ vim9 g:helptoc.popup_borderhighlight = ['Cursor']
+
+< Note: Choosing a highlight group that persists when
+ colorschemes change may be a good idea if you
+ do choose to customize this.
+
+popup_drag By default, table of contents popup windows may be dragged
+ with a mouse. If you want to prevent that from happening,
+ for whatever reason, you may deactivate it with: >
+ vim9 g:helptoc.popup_drag = false
+<
+popup_close Table of contents popups have "none" as the default setting
+ for this option. If you use a mouse, you may want either
+ to have the option to close popup windows by clicking on them
+ or to have a clickable "X" in the top right corner. For the
+ former, use "click", and for the latter, use "button", e.g.: >
+ vim9 g:helptoc.popup_close = "button"
+<popup_scrollbar
+ No scrollbar is provided on helptoc popups by default. If you
+ do want scrollbars (which may be useful as an indicator of how
+ far through the table of contents you are, not just for using
+ with a mouse) you may choose to have them with: >
+ vim9 g:helptoc.popup_scrollbar = true
+<
+NOTE: Information about the "popup_*" options, above, relate to popup options,
+which are explained at the 'second argument' part of |popup_create-arguments|.
+
+
+6. LIMITATIONS *HelpToc-limitations*
+
+- The help filetype may have edge case formatting patterns. Those may result
+ in some "headings" not being identified and/or may impact the heading levels
+ of entries in the table of contents itself.
+- Terminal window table of contents may not be active (insofar as jumping to
+ entries going to the Vim terminal's related command line). For example, if
+ Vim's terminal is set to Windows PowerShell Core, the table of contents will
+ display successfully, though the entries go nowhere when Enter, J, or K are
+ entered on them.
+- The tex filetype may have variable sectioning commands depending on the
+ document class. Consequently, some compromises are made, though they should
+ have minimal impact. Specifically:
+ * In instances where \part{} and \chapter{} appear in the same buffer, they
+ will both present at the top level in the table of contents. This should
+ be a minor matter because, in many instances, chapters will be in a
+ separate document using \include{}.
+ * An article or beamer \documentclass without a \part{} (or any document
+ with neither any \part{} nor any \chapter{} command) will have no content
+ at level 1. Consequently, its table of contents entries will all appear
+ preceded by at least one "| " (by default) because its headings start at
+ level 2 (presuming \section{} is present).
+- The vim filetype is only supported where numbered fold markers are applied.
+ This is intentional (including not handling unnumbered markers, which, when
+ used in combination with numbered ones, may be used for folding comments).
+ helptoc.vim itself provides an exemplar of how to use numbered fold markers,
+ not only for folds, but to support generating a useful table of contents
+ using :HelpToc.
+
+==============================================================================
+vim:tw=78:ts=8:fo=tcq2:ft=help:
diff --git a/runtime/pack/dist/opt/helptoc/doc/tags b/runtime/pack/dist/opt/helptoc/doc/tags
new file mode 100644
index 0000000..cd62dec
--- /dev/null
+++ b/runtime/pack/dist/opt/helptoc/doc/tags
@@ -0,0 +1,16 @@
+HelpToc-:HelpToc helptoc.txt /*HelpToc-:HelpToc*
+HelpToc-asciidoc-filetype helptoc.txt /*HelpToc-asciidoc-filetype*
+HelpToc-configuration helptoc.txt /*HelpToc-configuration*
+HelpToc-details helptoc.txt /*HelpToc-details*
+HelpToc-html-filetype helptoc.txt /*HelpToc-html-filetype*
+HelpToc-limitations helptoc.txt /*HelpToc-limitations*
+HelpToc-man-filetype helptoc.txt /*HelpToc-man-filetype*
+HelpToc-markdown-filetype helptoc.txt /*HelpToc-markdown-filetype*
+HelpToc-overview helptoc.txt /*HelpToc-overview*
+HelpToc-standardized-toc helptoc.txt /*HelpToc-standardized-toc*
+HelpToc-terminal-buftype helptoc.txt /*HelpToc-terminal-buftype*
+HelpToc-tex-filetype helptoc.txt /*HelpToc-tex-filetype*
+HelpToc-types helptoc.txt /*HelpToc-types*
+HelpToc-vim-filetype helptoc.txt /*HelpToc-vim-filetype*
+HelpToc-xhtml-filetype helptoc.txt /*HelpToc-xhtml-filetype*
+helptoc.txt helptoc.txt /*helptoc.txt*