コード例 #1
0
ファイル: builtin_set.cpp プロジェクト: kballard/fish-shell
// 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;
}
コード例 #2
0
ファイル: builtin_set.cpp プロジェクト: kballard/fish-shell
/// 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;
}
コード例 #3
0
ファイル: builtin_set.cpp プロジェクト: kballard/fish-shell
/// 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;
}
コード例 #4
0
ファイル: builtin_set.cpp プロジェクト: kballard/fish-shell
/// 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());
}
コード例 #5
0
ファイル: builtin_set.cpp プロジェクト: kballard/fish-shell
/// 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());
}
コード例 #6
0
ファイル: builtin_pwd.cpp プロジェクト: siteshwar/fish-shell
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;
}
コード例 #7
0
ファイル: builtin_set.cpp プロジェクト: kballard/fish-shell
/// 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;
}
コード例 #8
0
ファイル: builtin_set.cpp プロジェクト: kballard/fish-shell
/// 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;
}
コード例 #9
0
/// 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;
}
コード例 #10
0
/// 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;
}