0
0
mirror of https://github.com/vim/vim.git synced 2025-09-23 03:43:49 -04:00

runtime(helptoc): add s keymap to split and jump to selected entry

closes: #17876

Signed-off-by: lacygoill <lacygoill@lacygoill.me>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
lacygoill
2025-08-06 13:06:34 +02:00
committed by Christian Brabandt
parent af2c8e256a
commit 9340aa1bf8
4 changed files with 123 additions and 68 deletions

View File

@@ -1,4 +1,4 @@
*helphelp.txt* For Vim version 9.1. Last change: 2025 Jul 07 *helphelp.txt* For Vim version 9.1. Last change: 2025 Aug 06
VIM REFERENCE MANUAL by Bram Moolenaar VIM REFERENCE MANUAL by Bram Moolenaar
@@ -253,20 +253,21 @@ If you want to access an interactive table of contents, from any position in
the file, you can use the helptoc plugin. Load the plugin with: >vim the file, you can use the helptoc plugin. Load the plugin with: >vim
packadd helptoc packadd helptoc
<
*HelpToc-mappings*
Then you can use the `:HelpToc` command to open a popup menu. Then you can use the `:HelpToc` command to open a popup menu.
The latter supports the following normal commands: > The latter supports the following normal commands: >
key | effect key | effect
----+--------------------------------------------------------- ----+---------------------------------------------------------
j | select next entry
k | select previous entry
J | same as j, and jump to corresponding line in main buffer
K | same as k, and jump to corresponding line in main buffer
c | select nearest entry from cursor position in main buffer c | select nearest entry from cursor position in main buffer
g | select first entry g | select first entry
G | select last entry G | select last entry
H | collapse one level H | collapse one level
j | select next entry
J | same as j, and jump to corresponding line in main buffer
k | select previous entry
K | same as k, and jump to corresponding line in main buffer
L | expand one level L | expand one level
p | print current entry on command-line p | print current entry on command-line
@@ -274,6 +275,7 @@ The latter supports the following normal commands: >
| press multiple times to toggle feature on/off | press multiple times to toggle feature on/off
q | quit menu q | quit menu
s | split window, and jump to selected entry
z | redraw menu with current entry at center z | redraw menu with current entry at center
+ | increase width of popup menu + | increase width of popup menu
- | decrease width of popup menu - | decrease width of popup menu

View File

@@ -5656,6 +5656,7 @@ GetLatestVimScripts_dat pi_getscript.txt /*GetLatestVimScripts_dat*
Gnome gui_x11.txt /*Gnome* Gnome gui_x11.txt /*Gnome*
H motion.txt /*H* H motion.txt /*H*
Haiku os_haiku.txt /*Haiku* Haiku os_haiku.txt /*Haiku*
HelpToc-mappings helphelp.txt /*HelpToc-mappings*
I insert.txt /*I* I insert.txt /*I*
ICCF uganda.txt /*ICCF* ICCF uganda.txt /*ICCF*
IM-server mbyte.txt /*IM-server* IM-server mbyte.txt /*IM-server*

View File

@@ -104,6 +104,7 @@ const HELP_TEXT: list<string> =<< trim END
<C-D> scroll down half a page <C-D> scroll down half a page
<C-U> scroll up half a page <C-U> scroll up half a page
s split window, and jump to selected entry
<PageUp> scroll down a whole page <PageUp> scroll down a whole page
<PageDown> scroll up a whole page <PageDown> scroll up a whole page
<Home> select first entry <Home> select first entry
@@ -134,6 +135,23 @@ const MATCH_ENTRY: dict<dict<func: bool>> = {
help: {}, help: {},
# This lets the user get a TOC when piping `info(1)` to Vim:{{{
#
# $ info coreutils | vim -
#}}}
# But it assumes that they have some heuristics to set the `info` filetype.{{{
#
# Possibly by inspecting the first line from `scripts.vim`:
#
# if getline(1) =~ '^File: .*\.info, Node: .*, \%(Next\|Prev\): .*, Up: \|This is the top of the INFO tree.'
# setfiletype info
# endif
#}}}
info: {
1: (l: string, nextline): bool => l =~ '^\d\+\%(\.\d\+\)\+ ' && nextline =~ '^=\+$',
2: (l: string, nextline): bool => l =~ '^\d\+\%(\.\d\+\)\+ ' && nextline =~ '^-\+$',
},
# For asciidoc, these patterns should match: # For asciidoc, these patterns should match:
# https://docs.asciidoctor.org/asciidoc/latest/sections/titles-and-levels/ # https://docs.asciidoctor.org/asciidoc/latest/sections/titles-and-levels/
asciidoc: { asciidoc: {
@@ -281,8 +299,8 @@ export def Open() #{{{2
# invalidate the cache if the buffer's contents has changed # invalidate the cache if the buffer's contents has changed
if exists('b:toc') && &filetype != 'man' if exists('b:toc') && &filetype != 'man'
if b:toc.changedtick != b:changedtick if b:toc.changedtick != b:changedtick
# in a terminal buffer, `b:changedtick` does not change # in a terminal buffer, `b:changedtick` does not change
|| g:helptoc.type == 'terminal' && line('$') > b:toc.linecount || g:helptoc.type == 'terminal' && line('$') > b:toc.linecount
unlet! b:toc unlet! b:toc
endif endif
endif endif
@@ -616,25 +634,25 @@ def SetTocHelp() #{{{2
# Do not assume that a list ends on an empty line. # Do not assume that a list ends on an empty line.
# See the list at `:help gdb` for a counter-example. # See the list at `:help gdb` for a counter-example.
if in_list if in_list
&& curline !~ '^\d\+.\s' && curline !~ '^\d\+.\s'
&& curline !~ '^\s*$' && curline !~ '^\s*$'
&& curline !~ '^[< \t]' && curline !~ '^[<[:blank:]]'
in_list = false in_list = false
endif endif
if prevline =~ '^\d\+\.\s' if prevline =~ '^\d\+\.\s'
&& curline !~ '^\s*$' && curline !~ '^\s*$'
&& curline !~ $'^\s*{HELP_TAG}' && curline !~ $'^\s*{HELP_TAG}'
in_list = true in_list = true
endif endif
# 1. # 1.
if prevline =~ '^\d\+\.\s' 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 # empty line, or a line starting with a tag
&& (curline =~ '^>\=\s*$' || curline =~ $'^\s*{HELP_TAG}') && (curline =~ '^>\=\s*$' || curline =~ $'^\s*{HELP_TAG}')
# ignore a numbered line in a list # ignore a numbered line in a list
&& !in_list && !in_list
var current_numbered_entry: number = prevline var current_numbered_entry: number = prevline
->matchstr('^\d\+\ze\.\s') ->matchstr('^\d\+\ze\.\s')
->str2nr() ->str2nr()
@@ -649,9 +667,9 @@ def SetTocHelp() #{{{2
# 1.2 # 1.2
if curline =~ '^\d\+\.\d\+\s' if curline =~ '^\d\+\.\d\+\s'
if curline =~ $'\%({HELP_TAG}\s*\|\~\)$' if curline =~ $'\%({HELP_TAG}\s*\|\~\)$'
|| (prevline =~ $'^\s*{HELP_TAG}' || nextline =~ $'^\s*{HELP_TAG}') || (prevline =~ $'^\s*{HELP_TAG}' || nextline =~ $'^\s*{HELP_TAG}')
|| (prevline =~ HELP_RULER || nextline =~ HELP_RULER) || (prevline =~ HELP_RULER || nextline =~ HELP_RULER)
|| (prevline =~ '^\s*$' && nextline =~ '^\s*$') || (prevline =~ '^\s*$' && nextline =~ '^\s*$')
AddEntryInTocHelp('1.2', lnum, curline) AddEntryInTocHelp('1.2', lnum, curline)
endif endif
# 1.2.3 # 1.2.3
@@ -661,21 +679,21 @@ def SetTocHelp() #{{{2
# HEADLINE # HEADLINE
if curline =~ HELP_HEADLINE if curline =~ HELP_HEADLINE
&& curline !~ '^CTRL-' && curline !~ '^CTRL-'
&& prevline->IsSpecialHelpLine() && prevline->IsSpecialHelpLine()
&& (nextline ->IsSpecialHelpLine() && (nextline ->IsSpecialHelpLine()
|| nextline =~ '^\s*(\|^\t\|^N[oO][tT][eE]:') || nextline =~ '^\s*(\|^\t\|^N[oO][tT][eE]:')
AddEntryInTocHelp('HEADLINE', lnum, curline) AddEntryInTocHelp('HEADLINE', lnum, curline)
endif endif
# header ~ # header ~
if curline =~ '\~$' if curline =~ '\~$'
&& curline =~ '\w' && curline =~ '\w'
&& curline !~ '^[ \t<]\|\t\|---+---\|^NOTE:' && curline !~ '^[[:blank:]<]\|\t\|---+---\|^NOTE:'
&& curline !~ '^\d\+\.\%(\d\+\%(\.\d\+\)\=\)\=\s' && curline !~ '^\d\+\.\%(\d\+\%(\.\d\+\)\=\)\=\s'
&& prevline !~ $'^\s*{HELP_TAG}' && prevline !~ $'^\s*{HELP_TAG}'
&& prevline !~ '\~$' && prevline !~ '\~$'
&& nextline !~ '\~$' && nextline !~ '\~$'
AddEntryInTocHelp('header ~', lnum, curline) AddEntryInTocHelp('header ~', lnum, curline)
endif endif
@@ -728,8 +746,8 @@ def SetTocHelp() #{{{2
endif endif
b:toc.entries b:toc.entries
->map((_, entry: dict<any>) => entry.lvl == 0 ->map((_, entry: dict<any>) => entry.lvl == 0
? entry->extend({lvl: b:toc.maxlvl}) ? entry->extend({lvl: b:toc.maxlvl})
: entry) : entry)
# fix indentation # fix indentation
var min_lvl: number = b:toc.entries var min_lvl: number = b:toc.entries
@@ -763,7 +781,7 @@ def AddEntryInTocHelp(type: string, lnum: number, line: string) #{{{2
text = tags text = tags
# we ignore errors and warnings because those are meaningless in # we ignore errors and warnings because those are meaningless in
# a TOC where no context is available # a TOC where no context is available
->filter((_, tag: string) => tag !~ '\*[EW]\d\+\*') ->filter((_, tag: string): bool => tag !~ '\*[EW]\d\+\*')
->join() ->join()
if text !~ HELP_TAG if text !~ HELP_TAG
return return
@@ -871,12 +889,25 @@ enddef
def SelectNearestEntryFromCursor(winid: number) #{{{2 def SelectNearestEntryFromCursor(winid: number) #{{{2
var lnum: number = line('.') var lnum: number = line('.')
var firstline: number = b:toc.entries if lnum == 1
->copy() Win_execute(winid, 'normal! 1G')
->filter((_, line: dict<any>): bool => return
line.lvl <= b:toc.curlvl && line.lnum <= lnum) endif
->len()
if firstline == 0 if lnum == line('$')
Win_execute(winid, 'normal! G')
return
endif
var collapsed_entries: list<dict<any>> = b:toc.entries
->deepcopy()
->filter((_, entry: dict<any>): bool => entry.lvl <= b:toc.curlvl)
var firstline: number = collapsed_entries
->reverse()
->indexof((_, entry: dict<any>): bool => entry.lnum <= lnum)
firstline = len(collapsed_entries) - firstline
if firstline <= 0
return return
endif endif
Win_execute(winid, $'normal! {firstline}Gzz') Win_execute(winid, $'normal! {firstline}Gzz')
@@ -885,12 +916,12 @@ enddef
def Filter(winid: number, key: string): bool #{{{2 def Filter(winid: number, key: string): bool #{{{2
# support various normal commands for moving/scrolling # support various normal commands for moving/scrolling
if [ if [
'j', 'J', 'k', 'K', "\<Down>", "\<Up>", "\<C-N>", "\<C-P>", 'j', 'J', 'k', 'K', "\<Down>", "\<Up>", "\<C-N>", "\<C-P>",
"\<C-D>", "\<C-U>", "\<C-D>", "\<C-U>",
"\<PageUp>", "\<PageDown>", "\<PageUp>", "\<PageDown>",
'g', 'G', "\<Home>", "\<End>", 'g', 'G', "\<Home>", "\<End>",
'z' 'z'
]->index(key) >= 0 ]->index(key) >= 0
var scroll_cmd: string = { var scroll_cmd: string = {
J: 'j', J: 'j',
K: 'k', K: 'k',
@@ -937,18 +968,21 @@ def Filter(winid: number, key: string): bool #{{{2
SetTitle(winid) SetTitle(winid)
return true return true
endif
elseif key == 'c' if key == 'c'
SelectNearestEntryFromCursor(winid) SelectNearestEntryFromCursor(winid)
return true return true
endif
# when we press `p`, print the selected line (useful when it's truncated) # when we press `p`, print the selected line (useful when it's truncated)
elseif key == 'p' if key == 'p'
PrintEntry(winid) PrintEntry(winid)
return true return true
endif
# same thing, but automatically # same thing, but automatically
elseif key == 'P' if key == 'P'
print_entry = !print_entry print_entry = !print_entry
if print_entry if print_entry
PrintEntry(winid) PrintEntry(winid)
@@ -956,17 +990,20 @@ def Filter(winid: number, key: string): bool #{{{2
echo '' echo ''
endif endif
return true return true
endif
elseif key == 'q' if key == 'q'
popup_close(winid, -1) popup_close(winid, -1)
return true return true
endif
elseif key == '?' if key == '?'
ToggleHelp(winid) ToggleHelp(winid)
return true return true
endif
# scroll help window # scroll help window
elseif key == "\<C-J>" || key == "\<C-K>" if key == "\<C-J>" || key == "\<C-K>"
var scroll_cmd: string = {"\<C-J>": 'j', "\<C-K>": 'k'}->get(key, key) var scroll_cmd: string = {"\<C-J>": 'j', "\<C-K>": 'k'}->get(key, key)
if scroll_cmd == 'j' && line('.', help_winid) == line('$', help_winid) if scroll_cmd == 'j' && line('.', help_winid) == line('$', help_winid)
scroll_cmd = '1G' scroll_cmd = '1G'
@@ -975,12 +1012,19 @@ def Filter(winid: number, key: string): bool #{{{2
endif endif
Win_execute(help_winid, $'normal! {scroll_cmd}') Win_execute(help_winid, $'normal! {scroll_cmd}')
return true return true
endif
# split main window
if key == 's'
split
return popup_filter_menu(winid, "\<CR>")
endif
# increase/decrease the popup's width # increase/decrease the popup's width
elseif key == '+' || key == '-' if key == '+' || key == '-'
var width: number = winid->popup_getoptions().minwidth var width: number = winid->popup_getoptions().minwidth
if key == '-' && width == 1 if key == '-' && width == 1
|| key == '+' && winid->popup_getpos().col == 1 || key == '+' && winid->popup_getpos().col == 1
return true return true
endif endif
width = width + (key == '+' ? 1 : -1) width = width + (key == '+' ? 1 : -1)
@@ -988,13 +1032,15 @@ def Filter(winid: number, key: string): bool #{{{2
b:toc.width = width b:toc.width = width
popup_setoptions(winid, {minwidth: width, maxwidth: width}) popup_setoptions(winid, {minwidth: width, maxwidth: width})
return true return true
endif
elseif key == 'H' && b:toc.curlvl > 1 if key == 'H' && b:toc.curlvl > 1
|| key == 'L' && b:toc.curlvl < b:toc.maxlvl || key == 'L' && b:toc.curlvl < b:toc.maxlvl
CollapseOrExpand(winid, key) CollapseOrExpand(winid, key)
return true return true
endif
elseif key == '/' if key == '/'
# This is probably what the user expects if they've started a first # This is probably what the user expects if they've started a first
# fuzzy search, press Escape, then start a new one. # fuzzy search, press Escape, then start a new one.
DisplayNonFuzzyToc(winid) DisplayNonFuzzyToc(winid)
@@ -1005,7 +1051,8 @@ def Filter(winid: number, key: string): bool #{{{2
pattern: '@', pattern: '@',
cmd: $'FuzzySearch({winid})', cmd: $'FuzzySearch({winid})',
replace: true, replace: true,
}, { },
{
group: 'HelpToc', group: 'HelpToc',
event: 'CmdlineLeave', event: 'CmdlineLeave',
pattern: '@', pattern: '@',
@@ -1081,7 +1128,7 @@ def FuzzySearch(winid: number) #{{{2
col: col + 1, col: col + 1,
length: 1, length: 1,
type: 'help-fuzzy-toc', type: 'help-fuzzy-toc',
}))})) }))}))
endif endif
Win_execute(winid, 'normal! 1Gzt') Win_execute(winid, 'normal! 1Gzt')
Popup_settext(winid, text) Popup_settext(winid, text)
@@ -1167,7 +1214,8 @@ def Callback(winid: number, choice: number) #{{{2
if choice == -1 if choice == -1
fuzzy_entries = null_list fuzzy_entries = null_list
return return
elseif choice == -2 # Button X is clicked (when close: 'button') endif
if choice == -2 # Button X is clicked (when close: 'button')
return return
endif endif
@@ -1181,8 +1229,10 @@ def Callback(winid: number, choice: number) #{{{2
return return
endif endif
cursor(lnum, 1) # Moving the cursor with `normal! 123G` instead of `cursor()` adds an
normal! zvzt # entry in the jumplist (which is useful if you want to come back where
# you were).
execute $'normal! {lnum}Gzvzt'
enddef enddef
def ToggleHelp(menu_winid: number) #{{{2 def ToggleHelp(menu_winid: number) #{{{2
@@ -1235,8 +1285,8 @@ def ToggleHelp(menu_winid: number) #{{{2
enddef enddef
def Win_execute(winid: number, cmd: any) #{{{2 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 # whenever we change the cursor position
win_execute(winid, cmd) win_execute(winid, cmd)
redraw redraw
enddef enddef

View File

@@ -1,10 +1,9 @@
*helptoc.txt* For Vim version 9.1. Last change: 2025 May 04 *helptoc.txt* For Vim version 9.1. Last change: 2025 Aug 06
VIM REFERENCE MANUAL VIM REFERENCE MANUAL
Interactive table of contents for help buffers and several other filetypes Interactive table of contents for help buffers and several other filetypes
============================================================================== ==============================================================================
@@ -12,8 +11,11 @@ Interactive table of contents for help buffers and several other filetypes
The helptoc.vim plugin provides one command, :HelpToc, which generates a The helptoc.vim plugin provides one command, :HelpToc, which generates a
hierarchical table of contents in a popup window, which is based on the 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, structure of a Vim buffer. See |Helptoc-mappings| for a list of supported key
but it also works with buffers of the following types: mappings in the popup window.
It was designed initially for help buffers, but it also works with buffers of
the following types:
- asciidoc - asciidoc
- html - html
- man - man