Use a pager to view long outputs of builtin --help

Every builtin or function shipped with fish supports flag -h or --help to
print a slightly condensed version of its manpage.
Some of those help messages are longer than a typical screen;
this commit pipes the help to a pager to make it easier to read.

As in other places in fish we assume that either $PAGER or "less" is a
valid pager and use that.

In three places (error messages for bg, break and continue) the help is
printed to stderr instead of stdout.  To make sure the error message is
visible in the pager, we pass it to builtin_print_help, every call of which
needs to be updated.

Fixes #6227
This commit is contained in:
Johannes Altmanninger 2019-10-20 11:38:17 +02:00
parent d992480204
commit 61486954bc
33 changed files with 78 additions and 75 deletions

View file

@ -1,4 +1,4 @@
function __fish_print_help --description "Print help message for the specified fish function or builtin" --argument item
function __fish_print_help --description "Print help message for the specified fish function or builtin" --argument item error_message
if test "$item" = '.'
set item source
end
@ -53,6 +53,8 @@ function __fish_print_help --description "Print help message for the specified f
# the `-s` flag to `less` that `man` passes.
set -l state blank
set -l have_name
begin
string join \n $error_message
for line in $help
# categorize the line
set -l line_type
@ -103,5 +105,22 @@ function __fish_print_help --description "Print help message for the specified f
# skip it
end
end
end | string replace -ra '^ ' '' | ul # post-process with `ul`, to interpret the old-style grotty escapes
end
end | string replace -ra '^ ' '' | ul | # post-process with `ul`, to interpret the old-style grotty escapes
begin
set -l pager less
set -q PAGER
and set pager $PAGER
not isatty stdout
and set pager cat # cannot use a builtin here
# similar to man, but add -F to quit paging when the help output is brief (#6227)
set -xl LESS "$LESS"isrFX
# less options:
# -i (--ignore-case) search case-insensitively, like man
# -s (--squeeze-blank-lines) not strictly necessary since we already do that above
# -r (--raw-control-chars) to display bold, underline and colors
# -F (--quit-if-one-screen) to maintain the non-paging behavior for small outputs
# -X (--no-init) not sure if this is needed but git uses it
$pager
end
end

View file

@ -146,42 +146,27 @@ int parse_help_only_cmd_opts(struct help_only_cmd_opts_t &opts, int *optind, int
return STATUS_CMD_OK;
}
/// Obtain help/usage information for the specified builtin from manpage in subshell
/// Display help/usage information for the specified builtin or function from manpage
///
/// @param name
/// builtin name to get up help for
/// builtin or function name to get up help for
///
/// @return
/// A wcstring with a formatted manpage.
///
wcstring builtin_help_get(parser_t &parser, io_streams_t &streams, const wchar_t *name) {
UNUSED(parser);
/// Process and print help for the specified builtin or function.
void builtin_print_help(parser_t &parser, io_streams_t &streams, const wchar_t *name,
wcstring *error_message) {
UNUSED(streams);
// This won't ever work if no_exec is set.
if (no_exec()) return wcstring();
wcstring_list_t lst;
wcstring out;
const wcstring name_esc = escape_string(name, 1);
if (no_exec()) return;
const wcstring name_esc = escape_string(name, ESCAPE_ALL);
wcstring cmd = format_string(L"__fish_print_help %ls ", name_esc.c_str());
if (exec_subshell(cmd, parser, lst, false /* don't apply exit status */) >= 0) {
for (size_t i = 0; i < lst.size(); i++) {
out.append(lst.at(i));
out.push_back(L'\n');
io_chain_t ios;
if (error_message) {
cmd.append(escape_string(*error_message, ESCAPE_ALL));
// If it's an error, redirect the output of __fish_print_help to stderr
ios.push_back(std::make_shared<io_fd_t>(STDOUT_FILENO, STDERR_FILENO, false));
}
}
return out;
}
/// Process and print for the specified builtin. If @c b is `sb_err`, also print the line
/// information.
///
/// If @c b is the buffer representing standard error, and the help message is about to be printed
/// to an interactive screen, it may be shortened to fit the screen.
///
void builtin_print_help(parser_t &parser, io_streams_t &streams, const wchar_t *cmd,
output_stream_t &b) {
b.append(builtin_help_get(parser, streams, cmd));
parser.eval(cmd, ios, TOP);
// ignore the exit status of __fish_print_help
}
/// Perform error reporting for encounter with unknown option.
@ -221,14 +206,14 @@ static int builtin_generic(parser_t &parser, io_streams_t &streams, wchar_t **ar
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
// Hackish - if we have no arguments other than the command, we are a "naked invocation" and we
// just print help.
if (argc == 1) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_INVALID_ARGS;
}
@ -272,9 +257,8 @@ static int builtin_break_continue(parser_t &parser, io_streams_t &streams, wchar
int argc = builtin_count_args(argv);
if (argc != 1) {
streams.err.append_format(BUILTIN_ERR_UNKNOWN, argv[0], argv[1]);
builtin_print_help(parser, streams, argv[0], streams.err);
wcstring error_message = format_string(BUILTIN_ERR_UNKNOWN, argv[0], argv[1]);
builtin_print_help(parser, streams, argv[0], &error_message);
return STATUS_INVALID_ARGS;
}
@ -286,8 +270,8 @@ static int builtin_break_continue(parser_t &parser, io_streams_t &streams, wchar
if (b->type() == FUNCTION_CALL) break;
}
if (!has_loop) {
streams.err.append_format(_(L"%ls: Not inside of loop\n"), argv[0]);
builtin_print_help(parser, streams, argv[0], streams.err);
wcstring error_message = format_string(_(L"%ls: Not inside of loop\n"), argv[0]);
builtin_print_help(parser, streams, argv[0], &error_message);
return STATUS_CMD_ERROR;
}
@ -455,7 +439,7 @@ proc_status_t builtin_run(parser_t &parser, int job_pgid, wchar_t **argv, io_str
// follows the keyword by `-h` or `--help`. Since it isn't really a builtin command we need to
// handle displaying help for it here.
if (argv[1] && !argv[2] && parse_util_argument_is_help(argv[1]) && cmd_needs_help(argv[0])) {
builtin_print_help(parser, streams, argv[0], streams.out);
builtin_print_help(parser, streams, argv[0]);
return proc_status_t::from_exit_code(STATUS_CMD_OK);
}

View file

@ -89,7 +89,7 @@ const wchar_t *builtin_get_desc(const wcstring &b);
wcstring builtin_help_get(parser_t &parser, const wchar_t *cmd);
void builtin_print_help(parser_t &parser, io_streams_t &streams, const wchar_t *cmd,
output_stream_t &b);
wcstring *error_message = nullptr);
int builtin_count_args(const wchar_t *const *argv);
void builtin_unknown_option(parser_t &parser, io_streams_t &streams, const wchar_t *cmd,

View file

@ -699,7 +699,7 @@ int builtin_argparse(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -21,10 +21,10 @@
static int send_to_bg(parser_t &parser, io_streams_t &streams, job_t *j) {
assert(j != NULL);
if (!j->wants_job_control()) {
streams.err.append_format(
wcstring error_message = format_string(
_(L"%ls: Can't put job %d, '%ls' to background because it is not under job control\n"),
L"bg", j->job_id, j->command_wcstr());
builtin_print_help(parser, streams, L"bg", streams.err);
builtin_print_help(parser, streams, L"bg", &error_message);
return STATUS_CMD_ERROR;
}
@ -47,7 +47,7 @@ int builtin_bg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -431,7 +431,7 @@ int builtin_bind_t::builtin_bind(parser_t &parser, io_streams_t &streams, wchar_
return STATUS_CMD_OK;
}
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -81,7 +81,7 @@ int builtin_block(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -77,7 +77,7 @@ int builtin_builtin(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -31,7 +31,7 @@ int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -84,13 +84,13 @@ int builtin_command(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
// Quiet implies find_path.
if (!opts.find_path && !opts.all_paths && !opts.quiet) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_INVALID_ARGS;
}

View file

@ -263,7 +263,7 @@ int builtin_commandline(parser_t &parser, io_streams_t &streams, wchar_t **argv)
break;
}
case 'h': {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
case ':': {

View file

@ -247,7 +247,7 @@ int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
break;
}
case 'h': {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
case ':': {

View file

@ -68,7 +68,7 @@ int builtin_contains(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -52,7 +52,7 @@ int builtin_disown(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -21,7 +21,7 @@ int builtin_emit(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -69,7 +69,7 @@ int builtin_exit(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -31,7 +31,7 @@ int builtin_fg(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -298,7 +298,7 @@ int builtin_functions(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -212,7 +212,7 @@ int builtin_history(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -151,7 +151,7 @@ int builtin_jobs(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
break;
}
case 'h': {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
case ':': {

View file

@ -243,7 +243,7 @@ int builtin_math(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -742,7 +742,7 @@ int builtin_printf(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -33,7 +33,7 @@ int builtin_pwd(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
resolve_symlinks = true;
break;
case 'h':
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
case '?': {
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]);

View file

@ -38,7 +38,7 @@ int builtin_random(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -431,7 +431,7 @@ int builtin_read(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
}
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -28,13 +28,13 @@ int builtin_realpath(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
if (optind + 1 != argc) { // TODO: allow arbitrary args. `realpath *` should print many paths
streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, cmd, 1, argc - optind);
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_INVALID_ARGS;
}

View file

@ -68,7 +68,7 @@ int builtin_return(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -807,7 +807,7 @@ int builtin_set(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
argc -= optind;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -109,7 +109,7 @@ int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
break;
}
case 'h': {
builtin_print_help(parser, streams, argv[0], streams.out);
builtin_print_help(parser, streams, argv[0]);
return STATUS_CMD_OK;
}
case 'o': {

View file

@ -33,7 +33,7 @@ int builtin_source(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -280,7 +280,7 @@ int builtin_status(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}

View file

@ -1358,7 +1358,7 @@ int builtin_string(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
}
if (std::wcscmp(argv[1], L"-h") == 0 || std::wcscmp(argv[1], L"--help") == 0) {
builtin_print_help(parser, streams, L"string", streams.out);
builtin_print_help(parser, streams, L"string");
return STATUS_CMD_OK;
}

View file

@ -239,7 +239,7 @@ int builtin_ulimit(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
}
#endif
case 'h': {
builtin_print_help(parser, streams, cmd, streams.out);
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
case ':': {