runtime(helptoc): add s keymap to split and jump to selected entry
Commit:
https://github.com/vim/vim/commit/9340aa1bf81b48c32481ca5de88eda30409c8eeb
Author: lacygoill <[email protected]>
Date: Wed Aug 6 13:06:34 2025 +0200
runtime(helptoc): add s keymap to split and jump to selected entry
closes: https://github.com/vim/vim/issues/17876
Signed-off-by: lacygoill <[email protected]>
Signed-off-by: Christian Brabandt <[email protected]>
diff --git a/runtime/doc/helphelp.txt b/runtime/doc/helphelp.txt
index f3c5293b7..967078135 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 Jul 07
+*helphelp.txt* For Vim version 9.1. Last change: 2025 Aug 06
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
packadd helptoc
-
+<
+ *HelpToc-mappings*
Then you can use the `:HelpToc` command to open a popup menu.
The latter supports the following normal commands: >
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
g | select first entry
G | select last entry
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
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
q | quit menu
+ s | split window, and jump to selected entry
z | redraw menu with current entry at center
+ | increase width of popup menu
- | decrease width of popup menu
diff --git a/runtime/doc/tags b/runtime/doc/tags
index 2b4202606..116682504 100644
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -5656,6 +5656,7 @@ GetLatestVimScripts_dat pi_getscript.txt
/*GetLatestVimScripts_dat*
Gnome gui_x11.txt /*Gnome*
H motion.txt /*H*
Haiku os_haiku.txt /*Haiku*
+HelpToc-mappings helphelp.txt /*HelpToc-mappings*
I insert.txt /*I*
ICCF uganda.txt /*ICCF*
IM-server mbyte.txt /*IM-server*
diff --git a/runtime/pack/dist/opt/helptoc/autoload/helptoc.vim
b/runtime/pack/dist/opt/helptoc/autoload/helptoc.vim
index cf31d33d2..2b57cebae 100644
--- a/runtime/pack/dist/opt/helptoc/autoload/helptoc.vim
+++ b/runtime/pack/dist/opt/helptoc/autoload/helptoc.vim
@@ -104,6 +104,7 @@ const HELP_TEXT: list<string> =<< trim END
<C-D> scroll down half a page
<C-U> scroll up half a page
+ s split window, and jump to selected entry
<PageUp> scroll down a whole page
<PageDown> scroll up a whole page
<Home> select first entry
@@ -134,6 +135,23 @@ const MATCH_ENTRY: dict<dict<func: bool>> = {
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:
# https://docs.asciidoctor.org/asciidoc/latest/sections/titles-and-levels/
asciidoc: {
@@ -281,8 +299,8 @@ export def Open() #{{{2
# invalidate the cache if the buffer's contents has changed
if exists('b:toc') && &filetype != 'man'
if b:toc.changedtick != b:changedtick
- # in a terminal buffer, `b:changedtick` does not change
- || g:helptoc.type == 'terminal' && line('$') > b:toc.linecount
+ # in a terminal buffer, `b:changedtick` does not change
+ || g:helptoc.type == 'terminal' && line('$') > b:toc.linecount
unlet! b:toc
endif
endif
@@ -616,25 +634,25 @@ def SetTocHelp() #{{{2
# Do not assume that a list ends on an empty line.
# See the list at `:help gdb` for a counter-example.
if in_list
- && curline !~ '^\d\+.\s'
- && curline !~ '^\s*$'
- && curline !~ '^[< ]'
+ && curline !~ '^\d\+.\s'
+ && curline !~ '^\s*$'
+ && curline !~ '^[<[:blank:]]'
in_list = false
endif
if prevline =~ '^\d\+\.\s'
- && curline !~ '^\s*$'
- && curline !~ $'^\s*{HELP_TAG}'
+ && curline !~ '^\s*$'
+ && curline !~ $'^\s*{HELP_TAG}'
in_list = true
endif
# 1.
if prevline =~ '^\d\+\.\s'
- # 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
- && !in_list
+ # 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
+ && !in_list
var current_numbered_entry: number = prevline
->matchstr('^\d\+\ze\.\s')
->str2nr()
@@ -649,9 +667,9 @@ def SetTocHelp() #{{{2
# 1.2
if curline =~ '^\d\+\.\d\+\s'
if curline =~ $'\%({HELP_TAG}\s*\|\~\)$'
- || (prevline =~ $'^\s*{HELP_TAG}' || nextline =~ $'^\s*{HELP_TAG}')
- || (prevline =~ HELP_RULER || nextline =~ HELP_RULER)
- || (prevline =~ '^\s*$' && nextline =~ '^\s*$')
+ || (prevline =~ $'^\s*{HELP_TAG}' || nextline =~
$'^\s*{HELP_TAG}')
+ || (prevline =~ HELP_RULER || nextline =~ HELP_RULER)
+ || (prevline =~ '^\s*$' && nextline =~ '^\s*$')
AddEntryInTocHelp('1.2', lnum, curline)
endif
# 1.2.3
@@ -661,21 +679,21 @@ def SetTocHelp() #{{{2
# HEADLINE
if curline =~ HELP_HEADLINE
- && curline !~ '^CTRL-'
- && prevline->IsSpecialHelpLine()
- && (nextline ->IsSpecialHelpLine()
- || nextline =~ '^\s*(\|^ \|^N[oO][tT][eE]:')
+ && curline !~ '^CTRL-'
+ && prevline->IsSpecialHelpLine()
+ && (nextline ->IsSpecialHelpLine()
+ || nextline =~ '^\s*(\|^ \|^N[oO][tT][eE]:')
AddEntryInTocHelp('HEADLINE', lnum, curline)
endif
# header ~
if curline =~ '\~$'
- && curline =~ '\w'
- && curline !~ '^[ <]\| \|---+---\|^NOTE:'
- && curline !~ '^\d\+\.\%(\d\+\%(\.\d\+\)\=\)\=\s'
- && prevline !~ $'^\s*{HELP_TAG}'
- && prevline !~ '\~$'
- && nextline !~ '\~$'
+ && curline =~ '\w'
+ && curline !~ '^[[:blank:]<]\| \|---+---\|^NOTE:'
+ && curline !~ '^\d\+\.\%(\d\+\%(\.\d\+\)\=\)\=\s'
+ && prevline !~ $'^\s*{HELP_TAG}'
+ && prevline !~ '\~$'
+ && nextline !~ '\~$'
AddEntryInTocHelp('header ~', lnum, curline)
endif
@@ -728,8 +746,8 @@ def SetTocHelp() #{{{2
endif
b:toc.entries
->map((_, entry: dict<any>) => entry.lvl == 0
- ? entry->extend({lvl: b:toc.maxlvl})
- : entry)
+ ? entry->extend({lvl: b:toc.maxlvl})
+ : entry)
# fix indentation
var min_lvl: number = b:toc.entries
@@ -763,7 +781,7 @@ def AddEntryInTocHelp(type: string, lnum: number, line:
string) #{{{2
text = tags
# we ignore errors and warnings because those are meaningless in
# a TOC where no context is available
- ->filter((_, tag: string) => tag !~ '\*[EW]\d\+\*')
+ ->filter((_, tag: string): bool => tag !~ '\*[EW]\d\+\*')
->join()
if text !~ HELP_TAG
return
@@ -871,12 +889,25 @@ enddef
def SelectNearestEntryFromCursor(winid: number) #{{{2
var lnum: number = line('.')
- var firstline: number = b:toc.entries
- ->copy()
- ->filter((_, line: dict<any>): bool =>
- line.lvl <= b:toc.curlvl && line.lnum <= lnum)
- ->len()
- if firstline == 0
+ if lnum == 1
+ Win_execute(winid, 'normal! 1G')
+ return
+ endif
+
+ 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
endif
Win_execute(winid, $'normal! {firstline}Gzz')
@@ -885,12 +916,12 @@ enddef
def Filter(winid: number, key: string): bool #{{{2
# support various normal commands for moving/scrolling
if [
- 'j', 'J', 'k', 'K', "\<Down>", "\<Up>", "\<C-N>", "\<C-P>",
- "\<C-D>", "\<C-U>",
- "\<PageUp>", "\<PageDown>",
- 'g', 'G', "\<Home>", "\<End>",
- 'z'
- ]->index(key) >= 0
+ 'j', 'J', 'k', 'K', "\<Down>", "\<Up>", "\<C-N>", "\<C-P>",
+ "\<C-D>", "\<C-U>",
+ "\<PageUp>", "\<PageDown>",
+ 'g', 'G', "\<Home>", "\<End>",
+ 'z'
+ ]->index(key) >= 0
var scroll_cmd: string = {
J: 'j',
K: 'k',
@@ -937,18 +968,21 @@ def Filter(winid: number, key: string): bool #{{{2
SetTitle(winid)
return true
+ endif
- elseif key == 'c'
+ if key == 'c'
SelectNearestEntryFromCursor(winid)
return true
+ endif
# when we press `p`, print the selected line (useful when it's truncated)
- elseif key == 'p'
+ if key == 'p'
PrintEntry(winid)
return true
+ endif
# same thing, but automatically
- elseif key == 'P'
+ if key == 'P'
print_entry = !print_entry
if print_entry
PrintEntry(winid)
@@ -956,17 +990,20 @@ def Filter(winid: number, key: string): bool #{{{2
echo ''
endif
return true
+ endif
- elseif key == 'q'
+ if key == 'q'
popup_close(winid, -1)
return true
+ endif
- elseif key == '?'
+ if key == '?'
ToggleHelp(winid)
return true
+ endif
# 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)
if scroll_cmd == 'j' && line('.', help_winid) == line('$', help_winid)
scroll_cmd = '1G'
@@ -975,12 +1012,19 @@ def Filter(winid: number, key: string): bool #{{{2
endif
Win_execute(help_winid, $'normal! {scroll_cmd}')
return true
+ endif
+
+ # split main window
+ if key == 's'
+ split
+ return popup_filter_menu(winid, "\<CR>")
+ endif
# increase/decrease the popup's width
- elseif key == '+' || key == '-'
+ if key == '+' || key == '-'
var width: number = winid->popup_getoptions().minwidth
if key == '-' && width == 1
- || key == '+' && winid->popup_getpos().col == 1
+ || key == '+' && winid->popup_getpos().col == 1
return true
endif
width = width + (key == '+' ? 1 : -1)
@@ -988,13 +1032,15 @@ def Filter(winid: number, key: string): bool #{{{2
b:toc.width = width
popup_setoptions(winid, {minwidth: width, maxwidth: width})
return true
+ endif
- elseif key == 'H' && b:toc.curlvl > 1
- || key == 'L' && b:toc.curlvl < b:toc.maxlvl
+ if key == 'H' && b:toc.curlvl > 1
+ || key == 'L' && b:toc.curlvl < b:toc.maxlvl
CollapseOrExpand(winid, key)
return true
+ endif
- elseif key == '/'
+ if key == '/'
# This is probably what the user expects if they've started a first
# fuzzy search, press Escape, then start a new one.
DisplayNonFuzzyToc(winid)
@@ -1005,7 +1051,8 @@ def Filter(winid: number, key: string): bool #{{{2
pattern: '@',
cmd: $'FuzzySearch({winid})',
replace: true,
- }, {
+ },
+ {
group: 'HelpToc',
event: 'CmdlineLeave',
pattern: '@',
@@ -1081,7 +1128,7 @@ def FuzzySearch(winid: number) #{{{2
col: col + 1,
length: 1,
type: 'help-fuzzy-toc',
- }))}))
+ }))}))
endif
Win_execute(winid, 'normal! 1Gzt')
Popup_settext(winid, text)
@@ -1167,7 +1214,8 @@ def Callback(winid: number, choice: number) #{{{2
if choice == -1
fuzzy_entries = null_list
return
- elseif choice == -2 # Button X is clicked (when close: 'button')
+ endif
+ if choice == -2 # Button X is clicked (when close: 'button')
return
endif
@@ -1181,8 +1229,10 @@ def Callback(winid: number, choice: number) #{{{2
return
endif
- cursor(lnum, 1)
- normal! zvzt
+ # Moving the cursor with `normal! 123G` instead of `cursor()` adds an
+ # entry in the jumplist (which is useful if you want to come back where
+ # you were).
+ execute $'normal! {lnum}Gzvzt'
enddef
def ToggleHelp(menu_winid: number) #{{{2
@@ -1235,8 +1285,8 @@ def ToggleHelp(menu_winid: number) #{{{2
enddef
def Win_execute(winid: number, cmd: any) #{{{2
-# wrapper around `win_execute()` to enforce a redraw, which might be necessary
-# whenever we change the cursor position
+ # wrapper around `win_execute()` to enforce a redraw, which might be
necessary
+ # whenever we change the cursor position
win_execute(winid, cmd)
redraw
enddef
diff --git a/runtime/pack/dist/opt/helptoc/doc/helptoc.txt
b/runtime/pack/dist/opt/helptoc/doc/helptoc.txt
index 4bd87e625..3e9760e47 100644
--- a/runtime/pack/dist/opt/helptoc/doc/helptoc.txt
+++ b/runtime/pack/dist/opt/helptoc/doc/helptoc.txt
@@ -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
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
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:
+structure of a Vim buffer. See |Helptoc-mappings| for a list of supported key
+mappings in the popup window.
+
+It was designed initially for help buffers, but it also works with buffers of
+the following types:
- asciidoc
- html
- man
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/vim_dev/E1ujc6p-00Fq48-5W%40256bit.org.