diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1df31355e..4623f8689 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,10 @@ fish ?.?.? (released ???) ========================= +Other improvements +------------------ +- ``fish_update_completions`` now handles groff ``\X'...'`` device control escapes, fixing completion generation for man pages produced by help2man 1.50 and later (such as coreutils 9.10). + fish 4.6.0 (released March 28, 2026) ==================================== diff --git a/share/tools/create_manpage_completions.py b/share/tools/create_manpage_completions.py index 624bed4f9..cc990fbb6 100755 --- a/share/tools/create_manpage_completions.py +++ b/share/tools/create_manpage_completions.py @@ -501,6 +501,19 @@ class Deroffer: return True return False + def device_control(self): + # groff \X'...' device control escape (and \Z'...' zero-width). + # help2man 1.50+ uses \X'tty: link URL' for hyperlinks. + # We just skip the entire escape. + if self.str_at(1) in "XZ" and self.str_at(2) == "'": + self.skip_char(3) + while self.str_at(0) and self.str_at(0) != "'": + self.skip_char() + if self.str_at(0) == "'": + self.skip_char() + return True + return False + def var(self): reg = "" s0s1 = self.s[0:2] @@ -650,6 +663,8 @@ class Deroffer: return self.size() elif c in "hvwud": return self.numreq() + elif c in "XZ": + return self.device_control() elif c in "n*": return self.var() elif c == "(": @@ -1314,6 +1329,9 @@ def built_command(options, description): def remove_groff_formatting(data): + # Strip groff \X'...' device control escapes (help2man 1.50+ hyperlinks) + # and \Z'...' zero-width escapes. + data = re.sub(r"\\[XZ]'[^']*'", "", data) data = data.replace("\\fI", "") data = data.replace("\\fP", "") data = data.replace("\\f1", "") diff --git a/tests/checks/manpage-completions-groff-x.fish b/tests/checks/manpage-completions-groff-x.fish new file mode 100644 index 000000000..e939e143d --- /dev/null +++ b/tests/checks/manpage-completions-groff-x.fish @@ -0,0 +1,38 @@ +#RUN: %fish %s +#REQUIRES: command -v python3 + +# Regression test for groff \X'...' device control escapes in man pages. +# help2man 1.50+ emits \X'tty: link URL' hyperlinks which broke the parser. +# See: coreutils 9.10 man pages. + +set -l script (status dirname)/../../share/tools/create_manpage_completions.py +set -l tmpdir (mktemp -d) + +# Minimal man page with \X'tty: link' escapes as produced by help2man 1.50 +printf '%s\n' \ + '.TH TESTCMD "1" "March 2026" "test 1.0" "User Commands"' \ + '.SH NAME' \ + 'testcmd \\- test command' \ + '.SH DESCRIPTION' \ + 'A test command.' \ + '.TP' \ + '\\X'"'"'tty: link https://example.com/a'"'"'\\fB\\-a, \\-\\-all\\fP\\X'"'"'tty: link'"'"'' \ + 'show all entries' \ + '.TP' \ + '\\X'"'"'tty: link https://example.com/v'"'"'\\fB\\-v, \\-\\-verbose\\fP\\X'"'"'tty: link'"'"'' \ + 'be verbose' \ + '.TP' \ + '\\X'"'"'tty: link https://example.com/h'"'"'\\fB\\-\\-help\\fP\\X'"'"'tty: link'"'"'' \ + 'display help' \ + '.PP' \ + 'Some trailing paragraph text.' \ + '.SH AUTHOR' \ + 'Nobody.' \ + > $tmpdir/testcmd.1 + +python3 $script --stdout $tmpdir/testcmd.1 | string match -r '^complete.*' +#CHECK: complete -c testcmd -s a -l all -d 'show all entries' +#CHECK: complete -c testcmd -s v -l verbose -d 'be verbose' +#CHECK: complete -c testcmd -l help -d 'display help' + +rm -rf $tmpdir