// Query mode. Return the number of variables that do not exist out of the specified variables. static int builtin_set_query(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { int retval = 0; int scope = compute_scope(opts); for (int i = 0; i < argc; i++) { wchar_t *arg = argv[i]; wchar_t *dest = wcsdup(arg); assert(dest); std::vector<long> indexes; int idx_count = parse_index(indexes, dest, scope, streams, parser.vars()); if (idx_count == -1) { free(dest); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_CMD_ERROR; } if (idx_count) { wcstring_list_t result; auto dest_str = parser.vars().get(dest, scope); if (dest_str) dest_str->to_list(result); for (auto idx : indexes) { if (idx < 1 || (size_t)idx > result.size()) retval++; } } else { if (!parser.vars().get(arg, scope)) retval++; } free(dest); } return retval; }
/// This handles the more difficult case of setting individual slices of a var. static int set_var_slices(const wchar_t *cmd, set_cmd_opts_t &opts, const wchar_t *varname, wcstring_list_t &new_values, std::vector<long> &indexes, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { UNUSED(parser); if (opts.append || opts.prepend) { streams.err.append_format( L"%ls: Cannot use --append or --prepend when assigning to a slice", cmd); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } if (indexes.size() != static_cast<size_t>(argc)) { streams.err.append_format(BUILTIN_SET_MISMATCHED_ARGS, cmd, indexes.size(), argc); return STATUS_INVALID_ARGS; } int scope = compute_scope(opts); // calculate the variable scope based on the provided options const auto var_str = parser.vars().get(varname, scope); if (var_str) var_str->to_list(new_values); // Slice indexes have been calculated, do the actual work. wcstring_list_t result; for (int i = 0; i < argc; i++) result.push_back(argv[i]); int retval = update_values(new_values, indexes, result); if (retval != STATUS_CMD_OK) { streams.err.append_format(BUILTIN_SET_ARRAY_BOUNDS_ERR, cmd); return retval; } return STATUS_CMD_OK; }
/// This handles the common case of setting the entire var to a set of values. static int set_var_array(const wchar_t *cmd, set_cmd_opts_t &opts, const wchar_t *varname, wcstring_list_t &new_values, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { UNUSED(cmd); UNUSED(parser); UNUSED(streams); if (opts.prepend || opts.append) { if (opts.prepend) { for (int i = 0; i < argc; i++) new_values.push_back(argv[i]); } auto var_str = parser.vars().get(varname, ENV_DEFAULT); wcstring_list_t var_array; if (var_str) var_str->to_list(var_array); new_values.insert(new_values.end(), var_array.begin(), var_array.end()); if (opts.append) { for (int i = 0; i < argc; i++) new_values.push_back(argv[i]); } } else { for (int i = 0; i < argc; i++) new_values.push_back(argv[i]); } return STATUS_CMD_OK; }
/// Erase a variable. static int builtin_set_erase(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { if (argc != 1) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT2, cmd, L"--erase", 1, argc); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_CMD_ERROR; } int scope = compute_scope(opts); // calculate the variable scope based on the provided options wchar_t *dest = argv[0]; std::vector<long> indexes; int idx_count = parse_index(indexes, dest, scope, streams, parser.vars()); if (idx_count == -1) { builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_CMD_ERROR; } int retval; if (!valid_var_name(dest)) { streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, dest); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } if (idx_count == 0) { // unset the var retval = parser.vars().remove(dest, scope); // When a non-existent-variable is unset, return ENV_NOT_FOUND as $status // but do not emit any errors at the console as a compromise between user // friendliness and correctness. if (retval != ENV_NOT_FOUND) { handle_env_return(retval, cmd, dest, streams); } } else { // remove just the specified indexes of the var const auto dest_var = parser.vars().get(dest, scope); if (!dest_var) return STATUS_CMD_ERROR; wcstring_list_t result; dest_var->to_list(result); erase_values(result, indexes); retval = env_set_reporting_errors(cmd, dest, scope, result, streams, parser.vars()); } if (retval != STATUS_CMD_OK) return retval; return check_global_scope_exists(cmd, opts, dest, streams, parser.vars()); }
/// Set a variable. static int builtin_set_set(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { if (argc == 0) { streams.err.append_format(BUILTIN_ERR_MIN_ARG_COUNT1, cmd, 1); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } int scope = compute_scope(opts); // calculate the variable scope based on the provided options wchar_t *varname = argv[0]; argv++; argc--; std::vector<long> indexes; int idx_count = parse_index(indexes, varname, scope, streams, parser.vars()); if (idx_count == -1) { builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } if (!valid_var_name(varname)) { streams.err.append_format(BUILTIN_ERR_VARNAME, cmd, varname); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } int retval; wcstring_list_t new_values; if (idx_count == 0) { // Handle the simple, common, case. Set the var to the specified values. retval = set_var_array(cmd, opts, varname, new_values, argc, argv, parser, streams); } else { // Handle the uncommon case of setting specific slices of a var. retval = set_var_slices(cmd, opts, varname, new_values, indexes, argc, argv, parser, streams); } if (retval != STATUS_CMD_OK) return retval; retval = env_set_reporting_errors(cmd, varname, scope, new_values, streams, parser.vars()); if (retval != STATUS_CMD_OK) return retval; return check_global_scope_exists(cmd, opts, varname, streams, parser.vars()); }
int builtin_pwd(parser_t &parser, io_streams_t &streams, wchar_t **argv) { UNUSED(parser); const wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); bool resolve_symlinks = false; wgetopter_t w; int opt; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'L': resolve_symlinks = false; break; case 'P': resolve_symlinks = true; break; case 'h': builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (w.woptind != argc) { streams.err.append_format(BUILTIN_ERR_ARG_COUNT1, cmd, 0, argc - 1); return STATUS_INVALID_ARGS; } wcstring pwd; if (auto tmp = parser.vars().get(L"PWD")) { pwd = tmp->as_string(); } if (resolve_symlinks) { if (auto real_pwd = wrealpath(pwd)) { pwd = std::move(*real_pwd); } else { const char *error = strerror(errno); streams.err.append_format(L"%ls: realpath failed:", cmd, error); return STATUS_CMD_ERROR; } } if (pwd.empty()) { return STATUS_CMD_ERROR; } streams.out.append(pwd); streams.out.push_back(L'\n'); return STATUS_CMD_OK; }
/// Show mode. Show information about the named variable(s). static int builtin_set_show(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { UNUSED(opts); auto &vars = parser.vars(); if (argc == 0) { // show all vars wcstring_list_t names = parser.vars().get_names(ENV_USER); sort(names.begin(), names.end()); for (auto it : names) { show_scope(it.c_str(), ENV_LOCAL, streams, vars); show_scope(it.c_str(), ENV_GLOBAL, streams, vars); show_scope(it.c_str(), ENV_UNIVERSAL, streams, vars); streams.out.push_back(L'\n'); } } else { for (int i = 0; i < argc; i++) { wchar_t *arg = argv[i]; if (!valid_var_name(arg)) { streams.err.append_format(_(L"$%ls: invalid var name\n"), arg); continue; } if (std::wcschr(arg, L'[')) { streams.err.append_format( _(L"%ls: `set --show` does not allow slices with the var names\n"), cmd); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_CMD_ERROR; } show_scope(arg, ENV_LOCAL, streams, vars); show_scope(arg, ENV_GLOBAL, streams, vars); show_scope(arg, ENV_UNIVERSAL, streams, vars); streams.out.push_back(L'\n'); } } return STATUS_CMD_OK; }
/// Print the names of all environment variables in the scope. It will include the values unless the /// `set --list` flag was used. static int builtin_set_list(const wchar_t *cmd, set_cmd_opts_t &opts, int argc, wchar_t **argv, parser_t &parser, io_streams_t &streams) { UNUSED(cmd); UNUSED(argc); UNUSED(argv); UNUSED(parser); bool names_only = opts.list; wcstring_list_t names = parser.vars().get_names(compute_scope(opts)); sort(names.begin(), names.end()); for (size_t i = 0; i < names.size(); i++) { const wcstring key = names.at(i); const wcstring e_key = escape_string(key, 0); streams.out.append(e_key); if (!names_only) { auto var = parser.vars().get(key, compute_scope(opts)); if (!var.missing_or_empty()) { bool shorten = false; wcstring val = expand_escape_variable(*var); if (opts.shorten_ok && val.length() > 64) { shorten = true; val.resize(60); } streams.out.append(L" "); streams.out.append(val); if (shorten) streams.out.push_back(ellipsis_char); } } streams.out.append(L"\n"); } return STATUS_CMD_OK; }
/// The complete builtin. Used for specifying programmable tab-completions. Calls the functions in // complete.cpp for any heavy lifting. int builtin_complete(parser_t &parser, io_streams_t &streams, wchar_t **argv) { ASSERT_IS_MAIN_THREAD(); static int recursion_level = 0; wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); int result_mode = SHARED; int remove = 0; wcstring short_opt; wcstring_list_t gnu_opt, old_opt; const wchar_t *comp = L"", *desc = L"", *condition = L""; bool do_complete = false; wcstring do_complete_param; wcstring_list_t cmd_to_complete; wcstring_list_t path; wcstring_list_t wrap_targets; bool preserve_order = false; static const wchar_t *const short_options = L":a:c:p:s:l:o:d:frxeuAn:C::w:hk"; static const struct woption long_options[] = {{L"exclusive", no_argument, NULL, 'x'}, {L"no-files", no_argument, NULL, 'f'}, {L"require-parameter", no_argument, NULL, 'r'}, {L"path", required_argument, NULL, 'p'}, {L"command", required_argument, NULL, 'c'}, {L"short-option", required_argument, NULL, 's'}, {L"long-option", required_argument, NULL, 'l'}, {L"old-option", required_argument, NULL, 'o'}, {L"description", required_argument, NULL, 'd'}, {L"arguments", required_argument, NULL, 'a'}, {L"erase", no_argument, NULL, 'e'}, {L"unauthoritative", no_argument, NULL, 'u'}, {L"authoritative", no_argument, NULL, 'A'}, {L"condition", required_argument, NULL, 'n'}, {L"wraps", required_argument, NULL, 'w'}, {L"do-complete", optional_argument, NULL, 'C'}, {L"help", no_argument, NULL, 'h'}, {L"keep-order", no_argument, NULL, 'k'}, {NULL, 0, NULL, 0}}; int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'x': { result_mode |= EXCLUSIVE; break; } case 'f': { result_mode |= NO_FILES; break; } case 'r': { result_mode |= NO_COMMON; break; } case 'k': { preserve_order = true; break; } case 'p': case 'c': { wcstring tmp; if (unescape_string(w.woptarg, &tmp, UNESCAPE_SPECIAL)) { if (opt == 'p') path.push_back(tmp); else cmd_to_complete.push_back(tmp); } else { streams.err.append_format(_(L"%ls: Invalid token '%ls'\n"), cmd, w.woptarg); return STATUS_INVALID_ARGS; } break; } case 'd': { desc = w.woptarg; break; } case 'u': { // This option was removed in commit 1911298 and is now a no-op. break; } case 'A': { // This option was removed in commit 1911298 and is now a no-op. break; } case 's': { short_opt.append(w.woptarg); if (w.woptarg[0] == '\0') { streams.err.append_format(_(L"%ls: -s requires a non-empty string\n"), cmd); return STATUS_INVALID_ARGS; } break; } case 'l': { gnu_opt.push_back(w.woptarg); if (w.woptarg[0] == '\0') { streams.err.append_format(_(L"%ls: -l requires a non-empty string\n"), cmd); return STATUS_INVALID_ARGS; } break; } case 'o': { old_opt.push_back(w.woptarg); if (w.woptarg[0] == '\0') { streams.err.append_format(_(L"%ls: -o requires a non-empty string\n"), cmd); return STATUS_INVALID_ARGS; } break; } case 'a': { comp = w.woptarg; break; } case 'e': { remove = 1; break; } case 'n': { condition = w.woptarg; break; } case 'w': { wrap_targets.push_back(w.woptarg); break; } case 'C': { do_complete = true; const wchar_t *arg = w.woptarg ? w.woptarg : reader_get_buffer(); if (arg == NULL) { // This corresponds to using 'complete -C' in non-interactive mode. // See #2361. builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } do_complete_param = arg; break; } case 'h': { builtin_print_help(parser, streams, cmd, streams.out); return STATUS_CMD_OK; } case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } if (w.woptind != argc) { streams.err.append_format(BUILTIN_ERR_TOO_MANY_ARGUMENTS, cmd); builtin_print_error_trailer(parser, streams.err, cmd); return STATUS_INVALID_ARGS; } if (condition && std::wcslen(condition)) { const wcstring condition_string = condition; parse_error_list_t errors; if (parse_util_detect_errors(condition_string, &errors, false /* do not accept incomplete */)) { streams.err.append_format(L"%ls: Condition '%ls' contained a syntax error", cmd, condition); for (size_t i = 0; i < errors.size(); i++) { streams.err.append_format(L"\n%s: ", cmd); streams.err.append(errors.at(i).describe(condition_string)); } return STATUS_CMD_ERROR; } } if (comp && std::wcslen(comp)) { wcstring prefix; prefix.append(cmd); prefix.append(L": "); wcstring err_text; if (parser.detect_errors_in_argument_list(comp, &err_text, prefix.c_str())) { streams.err.append_format(L"%ls: Completion '%ls' contained a syntax error\n", cmd, comp); streams.err.append(err_text); streams.err.push_back(L'\n'); return STATUS_CMD_ERROR; } } if (do_complete) { const wchar_t *token; parse_util_token_extent(do_complete_param.c_str(), do_complete_param.size(), &token, 0, 0, 0); // Create a scoped transient command line, so that bulitin_commandline will see our // argument, not the reader buffer. builtin_commandline_scoped_transient_t temp_buffer(do_complete_param); if (recursion_level < 1) { recursion_level++; std::vector<completion_t> comp; complete(do_complete_param, &comp, COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_FUZZY_MATCH, parser.vars()); for (size_t i = 0; i < comp.size(); i++) { const completion_t &next = comp.at(i); // Make a fake commandline, and then apply the completion to it. const wcstring faux_cmdline = token; size_t tmp_cursor = faux_cmdline.size(); wcstring faux_cmdline_with_completion = completion_apply_to_command_line( next.completion, next.flags, faux_cmdline, &tmp_cursor, false); // completion_apply_to_command_line will append a space unless COMPLETE_NO_SPACE // is set. We don't want to set COMPLETE_NO_SPACE because that won't close // quotes. What we want is to close the quote, but not append the space. So we // just look for the space and clear it. if (!(next.flags & COMPLETE_NO_SPACE) && string_suffixes_string(L" ", faux_cmdline_with_completion)) { faux_cmdline_with_completion.resize(faux_cmdline_with_completion.size() - 1); } // The input data is meant to be something like you would have on the command // line, e.g. includes backslashes. The output should be raw, i.e. unescaped. So // we need to unescape the command line. See #1127. unescape_string_in_place(&faux_cmdline_with_completion, UNESCAPE_DEFAULT); streams.out.append(faux_cmdline_with_completion); // Append any description. if (!next.description.empty()) { streams.out.push_back(L'\t'); streams.out.append(next.description); } streams.out.push_back(L'\n'); } recursion_level--; } } else if (cmd_to_complete.empty() && path.empty()) { // No arguments specified, meaning we print the definitions of all specified completions // to stdout. streams.out.append(complete_print()); } else { int flags = COMPLETE_AUTO_SPACE; if (preserve_order) { flags |= COMPLETE_DONT_SORT; } if (remove) { builtin_complete_remove(cmd_to_complete, path, short_opt.c_str(), gnu_opt, old_opt); } else { builtin_complete_add(cmd_to_complete, path, short_opt.c_str(), gnu_opt, old_opt, result_mode, condition, comp, desc, flags); } // Handle wrap targets (probably empty). We only wrap commands, not paths. for (size_t w = 0; w < wrap_targets.size(); w++) { const wcstring &wrap_target = wrap_targets.at(w); for (size_t i = 0; i < cmd_to_complete.size(); i++) { (remove ? complete_remove_wrapper : complete_add_wrapper)(cmd_to_complete.at(i), wrap_target); } } } return STATUS_CMD_OK; }
/// set_color builtin. int builtin_set_color(parser_t &parser, io_streams_t &streams, wchar_t **argv) { // By the time this is called we should have initialized the curses subsystem. assert(curses_initialized); // Hack in missing italics and dim capabilities omitted from MacOS xterm-256color terminfo // Helps Terminal.app/iTerm #if __APPLE__ const auto term_prog = parser.vars().get(L"TERM_PROGRAM"); if (!term_prog.missing_or_empty() && (term_prog->as_string() == L"Apple_Terminal" || term_prog->as_string() == L"iTerm.app")) { const auto term = parser.vars().get(L"TERM"); if (!term.missing_or_empty() && (term->as_string() == L"xterm-256color")) { enter_italics_mode = sitm_esc; exit_italics_mode = ritm_esc; enter_dim_mode = dim_esc; } } #endif // Variables used for parsing the argument list. wchar_t *cmd = argv[0]; int argc = builtin_count_args(argv); // Some code passes variables to set_color that don't exist, like $fish_user_whatever. As a // hack, quietly return failure. if (argc <= 1) { return EXIT_FAILURE; } const wchar_t *bgcolor = NULL; bool bold = false, underline = false, italics = false, dim = false, reverse = false; // Parse options to obtain the requested operation and the modifiers. int opt; wgetopter_t w; while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, NULL)) != -1) { switch (opt) { case 'b': { bgcolor = w.woptarg; break; } case 'h': { builtin_print_help(parser, streams, argv[0], streams.out); return STATUS_CMD_OK; } case 'o': { bold = true; break; } case 'i': { italics = true; break; } case 'd': { dim = true; break; } case 'r': { reverse = true; break; } case 'u': { underline = true; break; } case 'c': { print_colors(streams); return STATUS_CMD_OK; } case ':': { builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1]); return STATUS_INVALID_ARGS; } case '?': { return STATUS_INVALID_ARGS; } default: { DIE("unexpected retval from wgetopt_long"); break; } } } // Remaining arguments are foreground color. std::vector<rgb_color_t> fgcolors; for (; w.woptind < argc; w.woptind++) { rgb_color_t fg = rgb_color_t(argv[w.woptind]); if (fg.is_none()) { streams.err.append_format(_(L"%ls: Unknown color '%ls'\n"), argv[0], argv[w.woptind]); return STATUS_INVALID_ARGS; } fgcolors.push_back(fg); } if (fgcolors.empty() && bgcolor == NULL && !bold && !underline && !italics && !dim && !reverse) { streams.err.append_format(_(L"%ls: Expected an argument\n"), argv[0]); return STATUS_INVALID_ARGS; } // #1323: We may have multiple foreground colors. Choose the best one. If we had no foreground // color, we'll get none(); if we have at least one we expect not-none. const rgb_color_t fg = best_color(fgcolors, output_get_color_support()); assert(fgcolors.empty() || !fg.is_none()); const rgb_color_t bg = rgb_color_t(bgcolor ? bgcolor : L""); if (bgcolor && bg.is_none()) { streams.err.append_format(_(L"%ls: Unknown color '%ls'\n"), argv[0], bgcolor); return STATUS_INVALID_ARGS; } // Test if we have at least basic support for setting fonts, colors and related bits - otherwise // just give up... if (cur_term == NULL || !exit_attribute_mode) { return STATUS_CMD_ERROR; } outputter_t outp; if (bold && enter_bold_mode) { writembs_nofail(outp, tparm((char *)enter_bold_mode)); } if (underline && enter_underline_mode) { writembs_nofail(outp, enter_underline_mode); } if (italics && enter_italics_mode) { writembs_nofail(outp, enter_italics_mode); } if (dim && enter_dim_mode) { writembs_nofail(outp, enter_dim_mode); } if (reverse && enter_reverse_mode) { writembs_nofail(outp, enter_reverse_mode); } else if (reverse && enter_standout_mode) { writembs_nofail(outp, enter_standout_mode); } if (bgcolor != NULL && bg.is_normal()) { writembs_nofail(outp, tparm((char *)exit_attribute_mode)); } if (!fg.is_none()) { if (fg.is_normal() || fg.is_reset()) { writembs_nofail(outp, tparm((char *)exit_attribute_mode)); } else { if (!outp.write_color(fg, true /* is_fg */)) { // We need to do *something* or the lack of any output messes up // when the cartesian product here would make "foo" disappear: // $ echo (set_color foo)bar outp.set_color(rgb_color_t::reset(), rgb_color_t::none()); } } } if (bgcolor != NULL && !bg.is_normal() && !bg.is_reset()) { outp.write_color(bg, false /* not is_fg */); } // Output the collected string. streams.out.append(str2wcstring(outp.contents())); return STATUS_CMD_OK; }