From fb5d466a35213ef1a4719f94f57194a5fb05ec30 Mon Sep 17 00:00:00 2001 From: "Justin M. Keyes" Date: Mon, 24 Aug 2026 22:43:47 +0200 Subject: [PATCH] fix(help): :helptags on CRLF helpfiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: The vimdoc parser does not treat "\r" as whitespace, so in a CRLF helpfile the codeblock rule fails and "*" pairs inside examples become tags. On Windows this generates duplicate "." and "/" tags: D:/a/neovim/neovim/build/bin/nvim.exe -u NONE -i NONE -e --headless -c "helptags ++t doc" -c "exe 'cquit' !empty(v:errmsg)"" Error in command line: E154: Duplicate tag "." in gui.txt and repeat.txt E154: Duplicate tag "/" in pattern.txt and usr_08.txt E154: Duplicate tag "/" in usr_08.txt and pattern.txt Note: The old C parser was unaffected because it read helpfiles in text mode (`os_fopen(…, "r")`). Solution: Strip "\r" before parsing. --- runtime/lua/vim/_core/help.lua | 2 ++ test/functional/ex_cmds/help_spec.lua | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/runtime/lua/vim/_core/help.lua b/runtime/lua/vim/_core/help.lua index 1e0f665566..a78f325ffb 100644 --- a/runtime/lua/vim/_core/help.lua +++ b/runtime/lua/vim/_core/help.lua @@ -433,6 +433,8 @@ local function extract_tags(tags, file, name) return end --- @cast source string + -- The grammar treats "\r" as part of a word, so CRLF files would yield bogus tags. + source = source:gsub('\r\n', '\n') local query = ts.query.parse('vimdoc', '(tag (word) @tagname)') local parser = ts.get_string_parser(source, 'vimdoc') diff --git a/test/functional/ex_cmds/help_spec.lua b/test/functional/ex_cmds/help_spec.lua index b97fdd4817..6c76255a38 100644 --- a/test/functional/ex_cmds/help_spec.lua +++ b/test/functional/ex_cmds/help_spec.lua @@ -354,11 +354,13 @@ describe(':helptags', function() fn.mkdir('Xhelptags/doc/sub', 'p') -- A "|" in a tag would break |links|, and "*Xd" is not closed, so neither is a tag. write_file('Xhelptags/doc/sub/Xc.txt', '*Xc*\n*X|c*\n*Xd\n') + -- CRLF helpfile: "\r" must not confuse the parser into finding tags in an example. + write_file('Xhelptags/doc/Xe.txt', '*Xe*\r\n>\r\n\t+-----+\r\n\t|/* a.c */ |/* b.c */ |\r\n') command('helptags Xhelptags/doc') eq( - eval("['Xa Xa.txt /*Xa*','Xb Xb.txt /*Xb*','Xc sub/Xc.txt /*Xc*']"), + eval("['Xa Xa.txt /*Xa*','Xb Xb.txt /*Xb*','Xc sub/Xc.txt /*Xc*','Xe Xe.txt /*Xe*']"), eval("readfile('Xhelptags/doc/tags')") )