fish-shell/src/builtin_cd.cpp
Johannes Altmanninger 61486954bc 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
2019-10-28 18:36:07 +01:00

102 lines
3.5 KiB
C++

// Implementation of the cd builtin.
#include "config.h" // IWYU pragma: keep
#include "builtin_cd.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "builtin.h"
#include "common.h"
#include "env.h"
#include "fallback.h" // IWYU pragma: keep
#include "io.h"
#include "parser.h"
#include "path.h"
#include "proc.h"
#include "wutil.h" // IWYU pragma: keep
/// The cd builtin. Changes the current directory to the one specified or to $HOME if none is
/// specified. The directory can be relative to any directory in the CDPATH variable.
/// The cd builtin. Changes the current directory to the one specified or to $HOME if none is
/// specified. The directory can be relative to any directory in the CDPATH variable.
int builtin_cd(parser_t &parser, io_streams_t &streams, wchar_t **argv) {
const wchar_t *cmd = argv[0];
int argc = builtin_count_args(argv);
help_only_cmd_opts_t opts;
int optind;
int retval = parse_help_only_cmd_opts(opts, &optind, argc, argv, parser, streams);
if (retval != STATUS_CMD_OK) return retval;
if (opts.print_help) {
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
wcstring dir_in;
if (argv[optind]) {
dir_in = argv[optind];
} else {
auto maybe_dir_in = parser.vars().get(L"HOME");
if (maybe_dir_in.missing_or_empty()) {
streams.err.append_format(_(L"%ls: Could not find home directory\n"), cmd);
return STATUS_CMD_ERROR;
}
dir_in = maybe_dir_in->as_string();
}
wcstring pwd = parser.vars().get_pwd_slash();
maybe_t<wcstring> mdir = path_get_cdpath(dir_in, pwd, parser.vars());
if (!mdir) {
if (errno == ENOTDIR) {
streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), cmd, dir_in.c_str());
} else if (errno == ENOENT) {
streams.err.append_format(_(L"%ls: The directory '%ls' does not exist\n"), cmd,
dir_in.c_str());
} else if (errno == EROTTEN) {
streams.err.append_format(_(L"%ls: '%ls' is a rotten symlink\n"), cmd, dir_in.c_str());
} else {
streams.err.append_format(_(L"%ls: Unknown error trying to locate directory '%ls'\n"),
cmd, dir_in.c_str());
}
if (!parser.is_interactive()) streams.err.append(parser.current_line());
return STATUS_CMD_ERROR;
}
const wcstring &dir = *mdir;
wcstring norm_dir = normalize_path(dir);
// We need to keep around the fd for this directory, in the parser.
autoclose_fd_t dir_fd(wopen_cloexec(norm_dir, O_RDONLY));
bool success = dir_fd.valid() && fchdir(dir_fd.fd()) == 0;
if (!success) {
struct stat buffer;
int status;
status = wstat(dir, &buffer);
if (!status && S_ISDIR(buffer.st_mode)) {
streams.err.append_format(_(L"%ls: Permission denied: '%ls'\n"), cmd, dir_in.c_str());
} else {
streams.err.append_format(_(L"%ls: '%ls' is not a directory\n"), cmd, dir_in.c_str());
}
if (!parser.is_interactive()) {
streams.err.append(parser.current_line());
}
return STATUS_CMD_ERROR;
}
parser.libdata().cwd_fd = std::make_shared<const autoclose_fd_t>(std::move(dir_fd));
std::vector<event_t> evts;
parser.vars().set_one(L"PWD", ENV_EXPORT | ENV_GLOBAL, std::move(norm_dir), &evts);
for (const auto &evt : evts) {
event_fire(parser, evt);
}
return STATUS_CMD_OK;
}