Files
neovim/src/nvim/option.c
Devon Gardner 71507281fb fix(coverity/530826): validate_opt_idx unchecked negative idx (#32081)
Problem:
opt_idx possible negative value used as index

Solution:
check opt_idx not less than zero (kOptInvalid)
2025-01-19 08:49:53 +08:00

6373 lines
190 KiB
C

// User-settable options. Checklist for adding a new option:
// - Put it in options.lua
// - For a global option: Add a variable for it in option_vars.h.
// - For a buffer or window local option:
// - Add a variable to the window or buffer struct in buffer_defs.h.
// - For a window option, add some code to copy_winopt().
// - For a window string option, add code to check_winopt()
// and clear_winopt(). If setting the option needs parsing,
// add some code to didset_window_options().
// - For a buffer option, add some code to buf_copy_options().
// - For a buffer string option, add code to check_buf_options().
// - If it's a numeric option, add any necessary bounds checks to check_num_option_bounds().
// - If it's a list of flags, add some code in do_set(), search for WW_ALL.
// - Add documentation! "desc" in options.lua, and any other related places.
// - Add an entry in runtime/optwin.vim.
#define IN_OPTION_C
#include <assert.h>
#include <inttypes.h>
#include <limits.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>
#include "auto/config.h"
#include "klib/kvec.h"
#include "nvim/api/extmark.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/api/private/validate.h"
#include "nvim/ascii_defs.h"
#include "nvim/assert_defs.h"
#include "nvim/autocmd.h"
#include "nvim/autocmd_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor_shape.h"
#include "nvim/decoration_defs.h"
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
#include "nvim/eval/window.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/ex_session.h"
#include "nvim/fold.h"
#include "nvim/garray.h"
#include "nvim/garray_defs.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/grid_defs.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
#include "nvim/log.h"
#include "nvim/lua/executor.h"
#include "nvim/macros_defs.h"
#include "nvim/mapping.h"
#include "nvim/math.h"
#include "nvim/mbyte.h"
#include "nvim/memfile.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
#include "nvim/memory_defs.h"
#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/normal.h"
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/os/lang.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/path.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
#include "nvim/regexp_defs.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
#include "nvim/spell.h"
#include "nvim/spellfile.h"
#include "nvim/spellsuggest.h"
#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/terminal.h"
#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/undo.h"
#include "nvim/undo_defs.h"
#include "nvim/vim_defs.h"
#include "nvim/window.h"
#ifdef BACKSLASH_IN_FILENAME
# include "nvim/arglist.h"
#endif
static const char e_unknown_option[]
= N_("E518: Unknown option");
static const char e_not_allowed_in_modeline[]
= N_("E520: Not allowed in a modeline");
static const char e_not_allowed_in_modeline_when_modelineexpr_is_off[]
= N_("E992: Not allowed in a modeline when 'modelineexpr' is off");
static const char e_number_required_after_equal[]
= N_("E521: Number required after =");
static const char e_preview_window_already_exists[]
= N_("E590: A preview window already exists");
static char *p_term = NULL;
static char *p_ttytype = NULL;
// Saved values for when 'bin' is set.
static int p_et_nobin;
static int p_ml_nobin;
static OptInt p_tw_nobin;
static OptInt p_wm_nobin;
// Saved values for when 'paste' is set.
static int p_ai_nopaste;
static int p_et_nopaste;
static OptInt p_sts_nopaste;
static OptInt p_tw_nopaste;
static OptInt p_wm_nopaste;
static char *p_vsts_nopaste;
#define OPTION_COUNT ARRAY_SIZE(options)
/// :set boolean option prefix
typedef enum {
PREFIX_NO = 0, ///< "no" prefix
PREFIX_NONE, ///< no prefix
PREFIX_INV, ///< "inv" prefix
} set_prefix_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "option.c.generated.h"
#endif
// options[] is initialized in options.generated.h.
// The options with a NULL variable are 'hidden': a set command for them is
// ignored and they are not printed.
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "options.generated.h"
# include "options_map.generated.h"
#endif
static int p_bin_dep_opts[] = {
kOptTextwidth, kOptWrapmargin, kOptModeline, kOptExpandtab, kOptInvalid
};
static int p_paste_dep_opts[] = {
kOptAutoindent, kOptExpandtab, kOptRuler, kOptShowmatch, kOptSmarttab, kOptSofttabstop,
kOptTextwidth, kOptWrapmargin, kOptRevins, kOptVarsofttabstop, kOptInvalid
};
void set_init_tablocal(void)
{
// susy baka: cmdheight calls itself OPT_GLOBAL but is really tablocal!
p_ch = options[kOptCmdheight].def_val.data.number;
}
/// Initialize the 'shell' option to a default value.
static void set_init_default_shell(void)
{
// Find default value for 'shell' option.
// Don't use it if it is empty.
const char *shell = os_getenv("SHELL");
if (shell != NULL) {
if (vim_strchr(shell, ' ') != NULL) {
const size_t len = strlen(shell) + 3; // two quotes and a trailing NUL
char *const cmd = xmalloc(len);
snprintf(cmd, len, "\"%s\"", shell);
set_string_default(kOptShell, cmd, true);
} else {
set_string_default(kOptShell, (char *)shell, false);
}
}
}
/// Set the default for 'backupskip' to include environment variables for
/// temp files.
static void set_init_default_backupskip(void)
{
#ifdef UNIX
static char *(names[4]) = { "", "TMPDIR", "TEMP", "TMP" };
#else
static char *(names[3]) = { "TMPDIR", "TEMP", "TMP" };
#endif
garray_T ga;
OptIndex opt_idx = kOptBackupskip;
ga_init(&ga, 1, 100);
for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
bool mustfree = true;
char *p;
size_t plen;
#ifdef UNIX
if (*names[i] == NUL) {
# ifdef __APPLE__
p = "/private/tmp";
plen = STRLEN_LITERAL("/private/tmp");
# else
p = "/tmp";
plen = STRLEN_LITERAL("/tmp");
# endif
mustfree = false;
} else
#endif
{
p = vim_getenv(names[i]);
plen = 0; // will be calculated below
}
if (p != NULL && *p != NUL) {
bool has_trailing_path_sep = false;
if (plen == 0) {
// the value was retrieved from the environment
plen = strlen(p);
// does the value include a trailing path separator?
if (after_pathsep(p, p + plen)) {
has_trailing_path_sep = true;
}
}
// item size needs to be large enough to include "/*" and a trailing NUL
// note: the value (and therefore plen) may already include a path separator
size_t itemsize = plen + (has_trailing_path_sep ? 0 : 1) + 2;
char *item = xmalloc(itemsize);
// add a preceding comma as a separator after the first item
size_t itemseplen = (ga.ga_len == 0) ? 0 : 1;
size_t itemlen = (size_t)vim_snprintf(item, itemsize, "%s%s*", p,
has_trailing_path_sep ? "" : PATHSEPSTR);
if (find_dup_item(ga.ga_data, item, itemlen, options[opt_idx].flags) == NULL) {
ga_grow(&ga, (int)(itemseplen + itemlen + 1));
ga.ga_len += vim_snprintf((char *)ga.ga_data + ga.ga_len,
itemseplen + itemlen + 1,
"%s%s", (itemseplen > 0) ? "," : "", item);
}
xfree(item);
}
if (mustfree) {
xfree(p);
}
}
if (ga.ga_data != NULL) {
set_string_default(kOptBackupskip, ga.ga_data, true);
}
}
/// Initialize the 'cdpath' option to a default value.
static void set_init_default_cdpath(void)
{
char *cdpath = vim_getenv("CDPATH");
if (cdpath == NULL) {
return;
}
char *buf = xmalloc(2 * strlen(cdpath) + 2);
buf[0] = ','; // start with ",", current dir first
int j = 1;
for (int i = 0; cdpath[i] != NUL; i++) {
if (vim_ispathlistsep(cdpath[i])) {
buf[j++] = ',';
} else {
if (cdpath[i] == ' ' || cdpath[i] == ',') {
buf[j++] = '\\';
}
buf[j++] = cdpath[i];
}
}
buf[j] = NUL;
change_option_default(kOptCdpath, CSTR_AS_OPTVAL(buf));
xfree(cdpath);
}
/// Expand environment variables and things like "~" for the defaults.
/// If option_expand() returns non-NULL the variable is expanded. This can
/// only happen for non-indirect options.
/// Also set the default to the expanded value, so ":set" does not list
/// them.
static void set_init_expand_env(void)
{
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
vimoption_T *opt = &options[opt_idx];
if (opt->flags & kOptFlagNoDefExp) {
continue;
}
char *p;
if ((opt->flags & kOptFlagGettext) && opt->var != NULL) {
p = _(*(char **)opt->var);
} else {
p = option_expand(opt_idx, NULL);
}
if (p != NULL) {
set_option_varp(opt_idx, opt->var, CSTR_TO_OPTVAL(p), true);
change_option_default(opt_idx, CSTR_TO_OPTVAL(p));
}
}
}
/// Initialize the encoding used for "default" in 'fileencodings'.
static void set_init_fenc_default(void)
{
// enc_locale() will try to find the encoding of the current locale.
// This will be used when "default" is used as encoding specifier
// in 'fileencodings'.
char *p = enc_locale();
if (p == NULL) {
// Use utf-8 as "default" if locale encoding can't be detected.
p = xmemdupz(S_LEN("utf-8"));
}
fenc_default = p;
}
/// Initialize the options, first part.
///
/// Called only once from main(), just after creating the first buffer.
/// If "clean_arg" is true, Nvim was started with --clean.
///
/// NOTE: ELOG() etc calls are not allowed here, as log location depends on
/// env var expansion which depends on expression evaluation and other
/// editor state initialized here. Do logging in set_init_2 or later.
void set_init_1(bool clean_arg)
{
langmap_init();
// Allocate the default option values.
alloc_options_default();
set_init_default_shell();
set_init_default_backupskip();
set_init_default_cdpath();
char *backupdir = stdpaths_user_state_subpath("backup", 2, true);
const size_t backupdir_len = strlen(backupdir);
backupdir = xrealloc(backupdir, backupdir_len + 3);
memmove(backupdir + 2, backupdir, backupdir_len + 1);
memmove(backupdir, ".,", 2);
set_string_default(kOptBackupdir, backupdir, true);
set_string_default(kOptViewdir, stdpaths_user_state_subpath("view", 2, true),
true);
set_string_default(kOptDirectory, stdpaths_user_state_subpath("swap", 2, true),
true);
set_string_default(kOptUndodir, stdpaths_user_state_subpath("undo", 2, true),
true);
// Set default for &runtimepath. All necessary expansions are performed in
// this function.
char *rtp = runtimepath_default(clean_arg);
if (rtp) {
set_string_default(kOptRuntimepath, rtp, true);
// Make a copy of 'rtp' for 'packpath'
set_string_default(kOptPackpath, rtp, false);
rtp = NULL; // ownership taken
}
// Set all the options (except the terminal options) to their default
// value. Also set the global value for local options.
set_options_default(0);
curbuf->b_p_initialized = true;
curbuf->b_p_ar = -1; // no local 'autoread' value
curbuf->b_p_ul = NO_LOCAL_UNDOLEVEL;
check_buf_options(curbuf);
check_win_options(curwin);
check_options();
// set 'laststatus'
last_status(false);
// Must be before option_expand(), because that one needs vim_isIDc()
didset_options();
// Use the current chartab for the generic chartab. This is not in
// didset_options() because it only depends on 'encoding'.
init_spell_chartab();
// Expand environment variables and things like "~" for the defaults.
set_init_expand_env();
save_file_ff(curbuf); // Buffer is unchanged
// Detect use of mlterm.
// Mlterm is a terminal emulator akin to xterm that has some special
// abilities (bidi namely).
// NOTE: mlterm's author is being asked to 'set' a variable
// instead of an environment variable due to inheritance.
if (os_env_exists("MLTERM")) {
set_option_value_give_err(kOptTermbidi, BOOLEAN_OPTVAL(true), 0);
}
didset_options2();
lang_init();
set_init_fenc_default();
#ifdef HAVE_WORKING_LIBINTL
// GNU gettext 0.10.37 supports this feature: set the codeset used for
// translated messages independently from the current locale.
(void)bind_textdomain_codeset(PROJECT_NAME, p_enc);
#endif
// Set the default for 'helplang'.
set_helplang_default(get_mess_lang());
}
/// Get default value for option, based on the option's type and scope.
///
/// @param opt_idx Option index in options[] table.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
///
/// @return Default value of option for the scope specified in opt_flags.
static OptVal get_option_default(const OptIndex opt_idx, int opt_flags)
{
vimoption_T *opt = &options[opt_idx];
bool is_global_local_option = option_is_global_local(opt_idx);
#ifdef UNIX
if (opt_idx == kOptModeline && getuid() == ROOT_UID) {
// 'modeline' defaults to off for root.
return BOOLEAN_OPTVAL(false);
}
#endif
if ((opt_flags & OPT_LOCAL) && is_global_local_option) {
// Use unset local value instead of default value for local scope of global-local options.
return get_option_unset_value(opt_idx);
} else if (option_has_type(opt_idx, kOptValTypeString) && !(opt->flags & kOptFlagNoDefExp)) {
// For string options, expand environment variables and ~ since the default value was already
// expanded, only required when an environment variable was set later.
char *s = option_expand(opt_idx, opt->def_val.data.string.data);
return s == NULL ? opt->def_val : CSTR_AS_OPTVAL(s);
} else {
return opt->def_val;
}
}
/// Allocate the default values for all options by copying them from the stack.
/// This ensures that we don't need to always check if the option default is allocated or not.
static void alloc_options_default(void)
{
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
options[opt_idx].def_val = optval_copy(options[opt_idx].def_val);
}
}
/// Change the default value for an option.
///
/// @param opt_idx Option index in options[] table.
/// @param value New default value. Must be allocated.
static void change_option_default(const OptIndex opt_idx, OptVal value)
{
optval_free(options[opt_idx].def_val);
options[opt_idx].def_val = value;
}
/// Set an option to its default value.
/// This does not take care of side effects!
///
/// @param opt_idx Option index in options[] table.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
static void set_option_default(const OptIndex opt_idx, int opt_flags)
{
OptVal def_val = get_option_default(opt_idx, opt_flags);
set_option_direct(opt_idx, def_val, opt_flags, current_sctx.sc_sid);
if (opt_idx == kOptScroll) {
win_comp_scroll(curwin);
}
// The default value is not insecure.
uint32_t *flagsp = insecure_flag(curwin, opt_idx, opt_flags);
*flagsp = *flagsp & ~(unsigned)kOptFlagInsecure;
}
/// Set all options (except terminal options) to their default value.
///
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
static void set_options_default(int opt_flags)
{
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
if (!(options[opt_idx].flags & kOptFlagNoDefault)) {
set_option_default(opt_idx, opt_flags);
}
}
// The 'scroll' option must be computed for all windows.
FOR_ALL_TAB_WINDOWS(tp, wp) {
win_comp_scroll(wp);
}
parse_cino(curbuf);
}
/// Set the Vi-default value of a string option.
/// Used for 'sh', 'backupskip' and 'term'.
///
/// @param opt_idx Option index in options[] table.
/// @param val The value of the option.
/// @param allocated If true, do not copy default as it was already allocated.
///
/// TODO(famiu): Remove this.
static void set_string_default(OptIndex opt_idx, char *val, bool allocated)
FUNC_ATTR_NONNULL_ALL
{
assert(opt_idx != kOptInvalid);
change_option_default(opt_idx, CSTR_AS_OPTVAL(allocated ? val : xstrdup(val)));
}
/// For an option value that contains comma separated items, find "newval" in
/// "origval". Return NULL if not found.
static const char *find_dup_item(const char *origval, const char *newval, const size_t newvallen,
uint32_t flags)
FUNC_ATTR_NONNULL_ARG(2)
{
if (origval == NULL) {
return NULL;
}
int bs = 0;
for (const char *s = origval; *s != NUL; s++) {
if ((!(flags & kOptFlagComma) || s == origval || (s[-1] == ',' && !(bs & 1)))
&& strncmp(s, newval, newvallen) == 0
&& (!(flags & kOptFlagComma) || s[newvallen] == ',' || s[newvallen] == NUL)) {
return s;
}
// Count backslashes. Only a comma with an even number of backslashes
// or a single backslash preceded by a comma before it is recognized as
// a separator.
if ((s > origval + 1 && s[-1] == '\\' && s[-2] != ',')
|| (s == origval + 1 && s[-1] == '\\')) {
bs++;
} else {
bs = 0;
}
}
return NULL;
}
#if defined(EXITFREE)
/// Free all options.
void free_all_options(void)
{
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
bool hidden = is_option_hidden(opt_idx);
if (option_is_global_only(opt_idx) || hidden) {
// global option: free value and default value.
// hidden option: free default value only.
if (!hidden) {
optval_free(optval_from_varp(opt_idx, options[opt_idx].var));
}
} else if (!option_is_window_local(opt_idx)) {
// buffer-local option: free global value.
optval_free(optval_from_varp(opt_idx, options[opt_idx].var));
}
optval_free(options[opt_idx].def_val);
}
free_operatorfunc_option();
free_tagfunc_option();
free_findfunc_option();
XFREE_CLEAR(fenc_default);
XFREE_CLEAR(p_term);
XFREE_CLEAR(p_ttytype);
}
#endif
/// Initialize the options, part two: After getting Rows and Columns.
void set_init_2(bool headless)
{
// set in set_init_1 but logging is not allowed there
ILOG("startup runtimepath/packpath value: %s", p_rtp);
// 'scroll' defaults to half the window height. The stored default is zero,
// which results in the actual value computed from the window height.
if (!(options[kOptScroll].flags & kOptFlagWasSet)) {
set_option_default(kOptScroll, OPT_LOCAL);
}
comp_col();
// 'window' is only for backwards compatibility with Vi.
// Default is Rows - 1.
if (!option_was_set(kOptWindow)) {
p_window = Rows - 1;
}
change_option_default(kOptWindow, NUMBER_OPTVAL(Rows - 1));
}
/// Initialize the options, part three: After reading the .vimrc
void set_init_3(void)
{
parse_shape_opt(SHAPE_CURSOR); // set cursor shapes from 'guicursor'
// Set 'shellpipe' and 'shellredir', depending on the 'shell' option.
// This is done after other initializations, where 'shell' might have been
// set, but only if they have not been set before.
bool do_srr = !(options[kOptShellredir].flags & kOptFlagWasSet);
bool do_sp = !(options[kOptShellpipe].flags & kOptFlagWasSet);
size_t len = 0;
char *p = (char *)invocation_path_tail(p_sh, &len);
p = xmemdupz(p, len);
bool is_csh = path_fnamecmp(p, "csh") == 0 || path_fnamecmp(p, "tcsh") == 0;
bool is_known_shell = path_fnamecmp(p, "sh") == 0 || path_fnamecmp(p, "ksh") == 0
|| path_fnamecmp(p, "mksh") == 0 || path_fnamecmp(p, "pdksh") == 0
|| path_fnamecmp(p, "zsh") == 0 || path_fnamecmp(p, "zsh-beta") == 0
|| path_fnamecmp(p, "bash") == 0 || path_fnamecmp(p, "fish") == 0
|| path_fnamecmp(p, "ash") == 0 || path_fnamecmp(p, "dash") == 0;
// Default for p_sp is "| tee", for p_srr is ">".
// For known shells it is changed here to include stderr.
if (is_csh || is_known_shell) {
if (do_sp) {
const OptVal sp =
is_csh ? STATIC_CSTR_AS_OPTVAL("|& tee") : STATIC_CSTR_AS_OPTVAL("2>&1| tee");
set_option_direct(kOptShellpipe, sp, 0, SID_NONE);
change_option_default(kOptShellpipe, optval_copy(sp));
}
if (do_srr) {
const OptVal srr = is_csh ? STATIC_CSTR_AS_OPTVAL(">&") : STATIC_CSTR_AS_OPTVAL(">%s 2>&1");
set_option_direct(kOptShellredir, srr, 0, SID_NONE);
change_option_default(kOptShellredir, optval_copy(srr));
}
}
xfree(p);
if (buf_is_empty(curbuf)) {
// Apply the first entry of 'fileformats' to the initial buffer.
if (options[kOptFileformats].flags & kOptFlagWasSet) {
set_fileformat(default_fileformat(), OPT_LOCAL);
}
}
set_title_defaults(); // 'title', 'icon'
}
/// When 'helplang' is still at its default value, set it to "lang".
/// Only the first two characters of "lang" are used.
void set_helplang_default(const char *lang)
{
if (lang == NULL) {
return;
}
const size_t lang_len = strlen(lang);
if (lang_len < 2) { // safety check
return;
}
if (options[kOptHelplang].flags & kOptFlagWasSet) {
return;
}
free_string_option(p_hlg);
p_hlg = xmemdupz(lang, lang_len);
// zh_CN becomes "cn", zh_TW becomes "tw".
if (STRNICMP(p_hlg, "zh_", 3) == 0 && lang_len >= 5) {
p_hlg[0] = (char)TOLOWER_ASC(p_hlg[3]);
p_hlg[1] = (char)TOLOWER_ASC(p_hlg[4]);
} else if (lang_len && *p_hlg == 'C') {
// any C like setting, such as C.UTF-8, becomes "en"
p_hlg[0] = 'e';
p_hlg[1] = 'n';
}
p_hlg[2] = NUL;
}
/// 'title' and 'icon' only default to true if they have not been set or reset
/// in .vimrc and we can read the old value.
/// When 'title' and 'icon' have been reset in .vimrc, we won't even check if
/// they can be reset. This reduces startup time when using X on a remote
/// machine.
void set_title_defaults(void)
{
// If GUI is (going to be) used, we can always set the window title and
// icon name. Saves a bit of time, because the X11 display server does
// not need to be contacted.
if (!(options[kOptTitle].flags & kOptFlagWasSet)) {
change_option_default(kOptTitle, BOOLEAN_OPTVAL(false));
p_title = 0;
}
if (!(options[kOptIcon].flags & kOptFlagWasSet)) {
change_option_default(kOptIcon, BOOLEAN_OPTVAL(false));
p_icon = 0;
}
}
void ex_set(exarg_T *eap)
{
int flags = 0;
if (eap->cmdidx == CMD_setlocal) {
flags = OPT_LOCAL;
} else if (eap->cmdidx == CMD_setglobal) {
flags = OPT_GLOBAL;
}
if (eap->forceit) {
flags |= OPT_ONECOLUMN;
}
do_set(eap->arg, flags);
}
/// Copy the new string value into allocated memory for the option.
/// Can't use set_option_direct(), because we need to remove the backslashes.
static char *stropt_copy_value(const char *origval, char **argp, set_op_T op,
uint32_t flags FUNC_ATTR_UNUSED)
{
char *arg = *argp;
// get a bit too much
size_t newlen = strlen(arg) + 1;
if (op != OP_NONE) {
newlen += strlen(origval) + 1;
}
char *newval = xmalloc(newlen);
char *s = newval;
// Copy the string, skip over escaped chars.
// For MS-Windows backslashes before normal file name characters
// are not removed, and keep backslash at start, for "\\machine\path",
// but do remove it for "\\\\machine\\path".
// The reverse is found in escape_option_str_cmdline().
while (*arg != NUL && !ascii_iswhite(*arg)) {
if (*arg == '\\' && arg[1] != NUL
#ifdef BACKSLASH_IN_FILENAME
&& !((flags & kOptFlagExpand)
&& vim_isfilec((uint8_t)arg[1])
&& !ascii_iswhite(arg[1])
&& (arg[1] != '\\'
|| (s == newval && arg[2] != '\\')))
#endif
) {
arg++; // remove backslash
}
int i = utfc_ptr2len(arg);
if (i > 1) {
// copy multibyte char
memmove(s, arg, (size_t)i);
arg += i;
s += i;
} else {
*s++ = *arg++;
}
}
*s = NUL;
*argp = arg;
return newval;
}
/// Expand environment variables and ~ in string option value 'newval'.
static char *stropt_expand_envvar(OptIndex opt_idx, const char *origval, char *newval, set_op_T op)
{
char *s = option_expand(opt_idx, newval);
if (s == NULL) {
return newval;
}
xfree(newval);
uint32_t newlen = (unsigned)strlen(s) + 1;
if (op != OP_NONE) {
newlen += (unsigned)strlen(origval) + 1;
}
newval = xmalloc(newlen);
STRCPY(newval, s);
return newval;
}
/// Concatenate the original and new values of a string option, adding a "," if
/// needed.
static void stropt_concat_with_comma(const char *origval, char *newval, set_op_T op, uint32_t flags)
{
int len = 0;
int comma = ((flags & kOptFlagComma) && *origval != NUL && *newval != NUL);
if (op == OP_ADDING) {
len = (int)strlen(origval);
// Strip a trailing comma, would get 2.
if (comma && len > 1
&& (flags & kOptFlagOneComma) == kOptFlagOneComma
&& origval[len - 1] == ','
&& origval[len - 2] != '\\') {
len--;
}
memmove(newval + len + comma, newval, strlen(newval) + 1);
memmove(newval, origval, (size_t)len);
} else {
len = (int)strlen(newval);
STRMOVE(newval + len + comma, origval);
}
if (comma) {
newval[len] = ',';
}
}
/// Remove a value from a string option. Copy string option value in "origval"
/// to "newval" and then remove the string "strval" of length "len".
static void stropt_remove_val(const char *origval, char *newval, uint32_t flags, const char *strval,
int len)
{
// Remove newval[] from origval[]. (Note: "len" has been set above
// and is used here).
STRCPY(newval, origval);
if (*strval) {
// may need to remove a comma
if (flags & kOptFlagComma) {
if (strval == origval) {
// include comma after string
if (strval[len] == ',') {
len++;
}
} else {
// include comma before string
strval--;
len++;
}
}
STRMOVE(newval + (strval - origval), strval + len);
}
}
/// Remove flags that appear twice in the string option value 'newval'.
static void stropt_remove_dupflags(char *newval, uint32_t flags)
{
char *s = newval;
// Remove flags that appear twice.
for (s = newval; *s;) {
// if options have kOptFlagFlagList and kOptFlagOneComma such as 'whichwrap'
if (flags & kOptFlagOneComma) {
if (*s != ',' && *(s + 1) == ','
&& vim_strchr(s + 2, (uint8_t)(*s)) != NULL) {
// Remove the duplicated value and the next comma.
STRMOVE(s, s + 2);
continue;
}
} else {
if ((!(flags & kOptFlagComma) || *s != ',')
&& vim_strchr(s + 1, (uint8_t)(*s)) != NULL) {
STRMOVE(s, s + 1);
continue;
}
}
s++;
}
}
/// Get the string value specified for a ":set" command. The following set options are supported:
/// set {opt}={val}
/// set {opt}:{val}
static char *stropt_get_newval(int nextchar, OptIndex opt_idx, char **argp, void *varp,
const char *origval, set_op_T *op_arg, uint32_t flags)
{
char *arg = *argp;
set_op_T op = *op_arg;
char *save_arg = NULL;
char *newval;
const char *s = NULL;
arg++; // jump to after the '=' or ':'
// Set 'keywordprg' to ":help" if an empty
// value was passed to :set by the user.
if (varp == &p_kp && (*arg == NUL || *arg == ' ')) {
save_arg = arg;
arg = ":help";
}
// Copy the new string into allocated memory.
newval = stropt_copy_value(origval, &arg, op, flags);
// Expand environment variables and ~.
// Don't do it when adding without inserting a comma.
if (op == OP_NONE || (flags & kOptFlagComma)) {
newval = stropt_expand_envvar(opt_idx, origval, newval, op);
}
// locate newval[] in origval[] when removing it
// and when adding to avoid duplicates
int len = 0;
if (op == OP_REMOVING || (flags & kOptFlagNoDup)) {
len = (int)strlen(newval);
s = find_dup_item(origval, newval, (size_t)len, flags);
// do not add if already there
if ((op == OP_ADDING || op == OP_PREPENDING) && s != NULL) {
op = OP_NONE;
STRCPY(newval, origval);
}
// if no duplicate, move pointer to end of original value
if (s == NULL) {
s = origval + (int)strlen(origval);
}
}
// concatenate the two strings; add a ',' if needed
if (op == OP_ADDING || op == OP_PREPENDING) {
stropt_concat_with_comma(origval, newval, op, flags);
} else if (op == OP_REMOVING) {
// Remove newval[] from origval[]. (Note: "len" has been set above
// and is used here).
stropt_remove_val(origval, newval, flags, s, len);
}
if (flags & kOptFlagFlagList) {
// Remove flags that appear twice.
stropt_remove_dupflags(newval, flags);
}
if (save_arg != NULL) {
arg = save_arg; // arg was temporarily changed, restore it
}
*argp = arg;
*op_arg = op;
return newval;
}
static set_op_T get_op(const char *arg)
{
set_op_T op = OP_NONE;
if (*arg != NUL && *(arg + 1) == '=') {
if (*arg == '+') {
op = OP_ADDING; // "+="
} else if (*arg == '^') {
op = OP_PREPENDING; // "^="
} else if (*arg == '-') {
op = OP_REMOVING; // "-="
}
}
return op;
}
static set_prefix_T get_option_prefix(char **argp)
{
if (strncmp(*argp, "no", 2) == 0) {
*argp += 2;
return PREFIX_NO;
} else if (strncmp(*argp, "inv", 3) == 0) {
*argp += 3;
return PREFIX_INV;
}
return PREFIX_NONE;
}
static int validate_opt_idx(win_T *win, OptIndex opt_idx, int opt_flags, uint32_t flags,
set_prefix_T prefix, const char **errmsg)
{
// Only bools can have a prefix of 'inv' or 'no'
if (!option_has_type(opt_idx, kOptValTypeBoolean) && prefix != PREFIX_NONE) {
*errmsg = e_invarg;
return FAIL;
}
// Skip all options that are not window-local (used when showing
// an already loaded buffer in a window).
if ((opt_flags & OPT_WINONLY) && !option_is_window_local(opt_idx)) {
return FAIL;
}
// Skip all options that are window-local (used for :vimgrep).
if ((opt_flags & OPT_NOWIN) && option_is_window_local(opt_idx)) {
return FAIL;
}
// Disallow changing some options from modelines.
if (opt_flags & OPT_MODELINE) {
if (flags & (kOptFlagSecure | kOptFlagNoML)) {
*errmsg = e_not_allowed_in_modeline;
return FAIL;
}
if ((flags & kOptFlagMLE) && !p_mle) {
*errmsg = e_not_allowed_in_modeline_when_modelineexpr_is_off;
return FAIL;
}
// In diff mode some options are overruled. This avoids that
// 'foldmethod' becomes "marker" instead of "diff" and that
// "wrap" gets set.
if (win->w_p_diff && (opt_idx == kOptFoldmethod || opt_idx == kOptWrap)) {
return FAIL;
}
}
// Disallow changing some options in the sandbox
if (sandbox != 0 && (flags & kOptFlagSecure)) {
*errmsg = e_sandbox;
return FAIL;
}
return OK;
}
/// Skip over the name of a TTY option or keycode option.
///
/// @param[in] arg Start of TTY or keycode option name.
///
/// @return NULL when option isn't a TTY or keycode option. Otherwise pointer to the char after the
/// option name.
static const char *find_tty_option_end(const char *arg)
{
if (strequal(arg, "term")) {
return arg + sizeof("term") - 1;
} else if (strequal(arg, "ttytype")) {
return arg + sizeof("ttytype") - 1;
}
const char *p = arg;
bool delimit = false; // whether to delimit <
if (arg[0] == '<') {
// look out for <t_>;>
delimit = true;
p++;
}
if (p[0] == 't' && p[1] == '_' && p[2] && p[3]) {
p += 4;
} else if (delimit) {
// Search for delimiting >.
while (*p != NUL && *p != '>') {
p++;
}
}
// Return NULL when delimiting > is not found.
if (delimit) {
if (*p != '>') {
return NULL;
}
p++;
}
return arg == p ? NULL : p;
}
/// Skip over the name of an option.
///
/// @param[in] arg Start of option name.
/// @param[out] opt_idxp Set to option index in options[] table.
///
/// @return NULL when no option name found. Otherwise pointer to the char after the option name.
const char *find_option_end(const char *arg, OptIndex *opt_idxp)
{
const char *p;
// Handle TTY and keycode options separately.
if ((p = find_tty_option_end(arg)) != NULL) {
*opt_idxp = kOptInvalid;
return p;
} else {
p = arg;
}
if (!ASCII_ISALPHA(*p)) {
*opt_idxp = kOptInvalid;
return NULL;
}
while (ASCII_ISALPHA(*p)) {
p++;
}
*opt_idxp = find_option_len(arg, (size_t)(p - arg));
return p;
}
/// Get new option value from argp. Allocated OptVal must be freed by caller.
/// Can unset local value of an option when ":set {option}<" is used.
static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T prefix, char **argp,
int nextchar, set_op_T op, uint32_t flags, void *varp, char *errbuf,
const size_t errbuflen, const char **errmsg)
FUNC_ATTR_WARN_UNUSED_RESULT
{
assert(varp != NULL);
vimoption_T *opt = &options[opt_idx];
char *arg = *argp;
// When setting the local value of a global option, the old value may be the global value.
const bool oldval_is_global = option_is_global_local(opt_idx) && (opt_flags & OPT_LOCAL);
OptVal oldval = optval_from_varp(opt_idx, oldval_is_global ? get_varp(opt) : varp);
OptVal newval = NIL_OPTVAL;
if (nextchar == '&') {
// ":set opt&": Reset to default value.
// NOTE: Use OPT_GLOBAL instead of opt_flags to ensure we don't use the unset local value for
// global-local options when OPT_LOCAL is used.
return optval_copy(get_option_default(opt_idx, OPT_GLOBAL));
} else if (nextchar == '<') {
// ":set opt<": Reset to global value.
// ":setlocal opt<": Copy global value to local value.
if (option_is_global_local(opt_idx) && !(opt_flags & OPT_LOCAL)) {
unset_option_local_value(opt_idx);
}
return get_option_value(opt_idx, OPT_GLOBAL);
}
switch (oldval.type) {
case kOptValTypeNil:
abort();
case kOptValTypeBoolean: {
TriState newval_bool;
// ":set opt!": invert
if (nextchar == '!') {
switch (oldval.data.boolean) {
case kNone:
newval_bool = kNone;
break;
case kTrue:
newval_bool = kFalse;
break;
case kFalse:
newval_bool = kTrue;
break;
}
} else {
// ":set invopt": invert
// ":set opt" or ":set noopt": set or reset
if (prefix == PREFIX_INV) {
newval_bool = *(int *)varp ^ 1;
} else {
newval_bool = prefix == PREFIX_NO ? 0 : 1;
}
}
newval = BOOLEAN_OPTVAL(newval_bool);
break;
}
case kOptValTypeNumber: {
OptInt oldval_num = oldval.data.number;
OptInt newval_num;
// Different ways to set a number option:
// <xx> accept special key codes for 'wildchar' or 'wildcharm'
// ^x accept ctrl key codes for 'wildchar' or 'wildcharm'
// c accept any non-digit for 'wildchar' or 'wildcharm'
// [-]0-9 set number
// other error
arg++;
if (((OptInt *)varp == &p_wc || (OptInt *)varp == &p_wcm)
&& (*arg == '<' || *arg == '^'
|| (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1])) && !ascii_isdigit(*arg)))) {
newval_num = string_to_key(arg);
if (newval_num == 0) {
*errmsg = e_invarg;
return newval;
}
} else if (*arg == '-' || ascii_isdigit(*arg)) {
int i;
// Allow negative, octal and hex numbers.
vim_str2nr(arg, NULL, &i, STR2NR_ALL, &newval_num, NULL, 0, true, NULL);
if (i == 0 || (arg[i] != NUL && !ascii_iswhite(arg[i]))) {
*errmsg = e_number_required_after_equal;
return newval;
}
} else {
*errmsg = e_number_required_after_equal;
return newval;
}
if (op == OP_ADDING) {
newval_num = oldval_num + newval_num;
}
if (op == OP_PREPENDING) {
newval_num = oldval_num * newval_num;
}
if (op == OP_REMOVING) {
newval_num = oldval_num - newval_num;
}
newval = NUMBER_OPTVAL(newval_num);
break;
}
case kOptValTypeString: {
const char *oldval_str = oldval.data.string.data;
// Get the new value for the option
const char *newval_str = stropt_get_newval(nextchar, opt_idx, argp, varp, oldval_str, &op,
flags);
newval = CSTR_AS_OPTVAL(newval_str);
break;
}
}
return newval;
}
static void do_one_set_option(int opt_flags, char **argp, bool *did_show, char *errbuf,
size_t errbuflen, const char **errmsg)
{
// 1: nothing, 0: "no", 2: "inv" in front of name
set_prefix_T prefix = get_option_prefix(argp);
char *arg = *argp;
// find end of name
OptIndex opt_idx;
const char *const option_end = find_option_end(arg, &opt_idx);
if (opt_idx != kOptInvalid) {
assert(option_end >= arg);
} else if (is_tty_option(arg)) { // Silently ignore TTY options.
return;
} else { // Invalid option name, skip.
*errmsg = e_unknown_option;
return;
}
// Remember character after option name.
uint8_t afterchar = (uint8_t)(*option_end);
char *p = (char *)option_end;
// Skip white space, allow ":set ai ?".
while (ascii_iswhite(*p)) {
p++;
}
set_op_T op = get_op(p);
if (op != OP_NONE) {
p++;
}
uint8_t nextchar = (uint8_t)(*p); // next non-white char after option name
// flags for current option
uint32_t flags = options[opt_idx].flags;
// pointer to variable for current option
void *varp = get_varp_scope(&(options[opt_idx]), opt_flags);
if (validate_opt_idx(curwin, opt_idx, opt_flags, flags, prefix, errmsg) == FAIL) {
return;
}
if (vim_strchr("?=:!&<", nextchar) != NULL) {
*argp = p;
if (nextchar == '&' && (*argp)[1] == 'v' && (*argp)[2] == 'i') {
if ((*argp)[3] == 'm') { // "opt&vim": set to Vim default
*argp += 3;
} else { // "opt&vi": set to Vi default
*argp += 2;
}
}
if (vim_strchr("?!&<", nextchar) != NULL
&& (*argp)[1] != NUL && !ascii_iswhite((*argp)[1])) {
*errmsg = e_trailing;
return;
}
}
// Allow '=' and ':' as MS-DOS command.com allows only one '=' character per "set" command line.
if (nextchar == '?'
|| (prefix == PREFIX_NONE && vim_strchr("=:&<", nextchar) == NULL
&& !option_has_type(opt_idx, kOptValTypeBoolean))) {
// print value
if (*did_show) {
msg_putchar('\n'); // cursor below last one
} else {
gotocmdline(true); // cursor at status line
*did_show = true; // remember that we did a line
}
msg_ext_set_kind("list_cmd");
showoneopt(&options[opt_idx], opt_flags);
if (p_verbose > 0) {
// Mention where the option was last set.
if (varp == options[opt_idx].var) {
option_last_set_msg(options[opt_idx].last_set);
} else if (option_has_scope(opt_idx, kOptScopeWin)) {
option_last_set_msg(curwin->w_p_script_ctx[option_scope_idx(opt_idx, kOptScopeWin)]);
} else if (option_has_scope(opt_idx, kOptScopeBuf)) {
option_last_set_msg(curbuf->b_p_script_ctx[option_scope_idx(opt_idx, kOptScopeBuf)]);
}
}
if (nextchar != '?' && nextchar != NUL && !ascii_iswhite(afterchar)) {
*errmsg = e_trailing;
}
return;
}
if (option_has_type(opt_idx, kOptValTypeBoolean)) {
if (vim_strchr("=:", nextchar) != NULL) {
*errmsg = e_invarg;
return;
}
if (vim_strchr("!&<", nextchar) == NULL && nextchar != NUL && !ascii_iswhite(afterchar)) {
*errmsg = e_trailing;
return;
}
} else {
if (vim_strchr("=:&<", nextchar) == NULL) {
*errmsg = e_invarg;
return;
}
}
OptVal newval = get_option_newval(opt_idx, opt_flags, prefix, argp, nextchar, op, flags, varp,
errbuf, errbuflen, errmsg);
if (newval.type == kOptValTypeNil || *errmsg != NULL) {
return;
}
*errmsg = set_option(opt_idx, newval, opt_flags, 0, false, op == OP_NONE, errbuf, errbuflen);
}
/// Parse 'arg' for option settings.
///
/// 'arg' may be IObuff, but only when no errors can be present and option
/// does not need to be expanded with option_expand().
/// "opt_flags":
/// 0 for ":set"
/// OPT_GLOBAL for ":setglobal"
/// OPT_LOCAL for ":setlocal" and a modeline
/// OPT_MODELINE for a modeline
/// OPT_WINONLY to only set window-local options
/// OPT_NOWIN to skip setting window-local options
///
/// @param arg option string (may be written to!)
///
/// @return FAIL if an error is detected, OK otherwise
int do_set(char *arg, int opt_flags)
{
bool did_show = false; // already showed one value
if (*arg == NUL) {
showoptions(false, opt_flags);
did_show = true;
} else {
while (*arg != NUL) { // loop to process all options
if (strncmp(arg, "all", 3) == 0 && !ASCII_ISALPHA(arg[3])
&& !(opt_flags & OPT_MODELINE)) {
// ":set all" show all options.
// ":set all&" set all options to their default value.
arg += 3;
if (*arg == '&') {
arg++;
// Only for :set command set global value of local options.
set_options_default(opt_flags);
didset_options();
didset_options2();
ui_refresh_options();
redraw_all_later(UPD_CLEAR);
} else {
showoptions(true, opt_flags);
did_show = true;
}
} else {
char *startarg = arg; // remember for error message
const char *errmsg = NULL;
char errbuf[ERR_BUFLEN];
do_one_set_option(opt_flags, &arg, &did_show, errbuf, sizeof(errbuf), &errmsg);
// Advance to next argument.
// - skip until a blank found, taking care of backslashes
// - skip blanks
// - skip one "=val" argument (for hidden options ":set gfn =xx")
for (int i = 0; i < 2; i++) {
arg = skiptowhite_esc(arg);
arg = skipwhite(arg);
if (*arg != '=') {
break;
}
}
if (errmsg != NULL) {
int i = vim_snprintf((char *)IObuff, IOSIZE, "%s", _(errmsg)) + 2;
if (i + (arg - startarg) < IOSIZE) {
// append the argument with the error
xstrlcpy(IObuff + i - 2, ": ", (size_t)(IOSIZE - i + 2));
assert(arg >= startarg);
memmove(IObuff + i, startarg, (size_t)(arg - startarg));
IObuff[i + (arg - startarg)] = NUL;
}
// make sure all characters are printable
trans_characters(IObuff, IOSIZE);
no_wait_return++; // wait_return() done later
emsg(IObuff); // show error highlighted
no_wait_return--;
return FAIL;
}
}
arg = skipwhite(arg);
}
}
if (silent_mode && did_show) {
// After displaying option values in silent mode.
silent_mode = false;
info_message = true; // use stdout, not stderr
msg_putchar('\n');
silent_mode = true;
info_message = false; // use stdout, not stderr
}
return OK;
}
// Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number.
// When "has_lt" is true there is a '<' before "*arg_arg".
// Returns 0 when the key is not recognized.
static int find_key_len(const char *arg_arg, size_t len, bool has_lt)
{
int key = 0;
const char *arg = arg_arg;
// Don't use get_special_key_code() for t_xx, we don't want it to call
// add_termcap_entry().
if (len >= 4 && arg[0] == 't' && arg[1] == '_') {
if (!has_lt || arg[4] == '>') {
key = TERMCAP2KEY((uint8_t)arg[2], (uint8_t)arg[3]);
}
} else if (has_lt) {
arg--; // put arg at the '<'
int modifiers = 0;
key = find_special_key(&arg, len + 1, &modifiers, FSK_KEYCODE | FSK_KEEP_X_KEY | FSK_SIMPLIFY,
NULL);
if (modifiers) { // can't handle modifiers here
key = 0;
}
}
return key;
}
/// Convert a key name or string into a key value.
/// Used for 'cedit', 'wildchar' and 'wildcharm' options.
int string_to_key(char *arg)
{
if (*arg == '<' && arg[1]) {
return find_key_len(arg + 1, strlen(arg), true);
}
if (*arg == '^' && arg[1]) {
int key = CTRL_CHR((uint8_t)arg[1]);
if (key == 0) { // ^@ is <Nul>
key = K_ZERO;
}
return key;
}
return (uint8_t)(*arg);
}
// When changing 'title', 'titlestring', 'icon' or 'iconstring', call
// maketitle() to create and display it.
// When switching the title or icon off, call ui_set_{icon,title}(NULL) to get
// the old value back.
void did_set_title(void)
{
if (starting != NO_SCREEN) {
maketitle();
}
}
/// set_options_bin - called when 'bin' changes value.
///
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
void set_options_bin(int oldval, int newval, int opt_flags)
{
// The option values that are changed when 'bin' changes are
// copied when 'bin is set and restored when 'bin' is reset.
if (newval) {
if (!oldval) { // switched on
if (!(opt_flags & OPT_GLOBAL)) {
curbuf->b_p_tw_nobin = curbuf->b_p_tw;
curbuf->b_p_wm_nobin = curbuf->b_p_wm;
curbuf->b_p_ml_nobin = curbuf->b_p_ml;
curbuf->b_p_et_nobin = curbuf->b_p_et;
}
if (!(opt_flags & OPT_LOCAL)) {
p_tw_nobin = p_tw;
p_wm_nobin = p_wm;
p_ml_nobin = p_ml;
p_et_nobin = p_et;
}
}
if (!(opt_flags & OPT_GLOBAL)) {
curbuf->b_p_tw = 0; // no automatic line wrap
curbuf->b_p_wm = 0; // no automatic line wrap
curbuf->b_p_ml = 0; // no modelines
curbuf->b_p_et = 0; // no expandtab
}
if (!(opt_flags & OPT_LOCAL)) {
p_tw = 0;
p_wm = 0;
p_ml = false;
p_et = false;
p_bin = true; // needed when called for the "-b" argument
}
} else if (oldval) { // switched off
if (!(opt_flags & OPT_GLOBAL)) {
curbuf->b_p_tw = curbuf->b_p_tw_nobin;
curbuf->b_p_wm = curbuf->b_p_wm_nobin;
curbuf->b_p_ml = curbuf->b_p_ml_nobin;
curbuf->b_p_et = curbuf->b_p_et_nobin;
}
if (!(opt_flags & OPT_LOCAL)) {
p_tw = p_tw_nobin;
p_wm = p_wm_nobin;
p_ml = p_ml_nobin;
p_et = p_et_nobin;
}
}
// Remember where the dependent option were reset
didset_options_sctx(opt_flags, p_bin_dep_opts);
}
/// Find the parameter represented by the given character (eg ', :, ", or /),
/// and return its associated value in the 'shada' string.
/// Only works for number parameters, not for 'r' or 'n'.
/// If the parameter is not specified in the string or there is no following
/// number, return -1.
int get_shada_parameter(int type)
{
char *p = find_shada_parameter(type);
if (p != NULL && ascii_isdigit(*p)) {
return atoi(p);
}
return -1;
}
/// Find the parameter represented by the given character (eg ''', ':', '"', or
/// '/') in the 'shada' option and return a pointer to the string after it.
/// Return NULL if the parameter is not specified in the string.
char *find_shada_parameter(int type)
{
for (char *p = p_shada; *p; p++) {
if (*p == type) {
return p + 1;
}
if (*p == 'n') { // 'n' is always the last one
break;
}
p = vim_strchr(p, ','); // skip until next ','
if (p == NULL) { // hit the end without finding parameter
break;
}
}
return NULL;
}
/// Expand environment variables for some string options.
/// These string options cannot be indirect!
/// If "val" is NULL expand the current value of the option.
/// Return pointer to NameBuff, or NULL when not expanded.
static char *option_expand(OptIndex opt_idx, const char *val)
{
// if option doesn't need expansion nothing to do
if (!(options[opt_idx].flags & kOptFlagExpand) || is_option_hidden(opt_idx)) {
return NULL;
}
if (val == NULL) {
val = *(char **)options[opt_idx].var;
}
// If val is longer than MAXPATHL no meaningful expansion can be done,
// expand_env() would truncate the string.
if (val == NULL || strlen(val) > MAXPATHL) {
return NULL;
}
// Expanding this with NameBuff, expand_env() must not be passed IObuff.
// Escape spaces when expanding 'tags', they are used to separate file
// names.
// For 'spellsuggest' expand after "file:".
expand_env_esc(val, NameBuff, MAXPATHL,
(char **)options[opt_idx].var == &p_tags, false,
(char **)options[opt_idx].var == &p_sps ? "file:"
: NULL);
if (strcmp(NameBuff, val) == 0) { // they are the same
return NULL;
}
return NameBuff;
}
/// After setting various option values: recompute variables that depend on
/// option values.
static void didset_options(void)
{
// initialize the table for 'iskeyword' et.al.
init_chartab();
didset_string_options();
spell_check_msm();
spell_check_sps();
compile_cap_prog(curwin->w_s);
did_set_spell_option();
// set cedit_key
did_set_cedit(NULL);
// initialize the table for 'breakat'.
did_set_breakat(NULL);
didset_window_options(curwin, true);
}
// More side effects of setting options.
static void didset_options2(void)
{
// Initialize the highlight_attr[] table.
highlight_changed();
// Parse default for 'fillchars'.
set_chars_option(curwin, curwin->w_p_fcs, kFillchars, true, NULL, 0);
// Parse default for 'listchars'.
set_chars_option(curwin, curwin->w_p_lcs, kListchars, true, NULL, 0);
// Parse default for 'wildmode'.
check_opt_wim();
xfree(curbuf->b_p_vsts_array);
tabstop_set(curbuf->b_p_vsts, &curbuf->b_p_vsts_array);
xfree(curbuf->b_p_vts_array);
tabstop_set(curbuf->b_p_vts, &curbuf->b_p_vts_array);
}
/// Check for string options that are NULL (normally only termcap options).
void check_options(void)
{
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
if ((option_has_type(opt_idx, kOptValTypeString)) && options[opt_idx].var != NULL) {
check_string_option((char **)get_varp(&(options[opt_idx])));
}
}
}
/// Check if option was set insecurely.
///
/// @param wp Window.
/// @param opt_idx Option index in options[] table.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
///
/// @return True if option was set from a modeline or in secure mode, false if it wasn't.
int was_set_insecurely(win_T *const wp, OptIndex opt_idx, int opt_flags)
{
assert(opt_idx != kOptInvalid);
uint32_t *flagp = insecure_flag(wp, opt_idx, opt_flags);
return (*flagp & kOptFlagInsecure) != 0;
}
/// Get a pointer to the flags used for the kOptFlagInsecure flag of option
/// "opt_idx". For some local options a local flags field is used.
/// NOTE: Caller must make sure that "wp" is set to the window from which
/// the option is used.
uint32_t *insecure_flag(win_T *const wp, OptIndex opt_idx, int opt_flags)
{
if (opt_flags & OPT_LOCAL) {
assert(wp != NULL);
switch (opt_idx) {
case kOptStatusline:
return &wp->w_p_stl_flags;
case kOptWinbar:
return &wp->w_p_wbr_flags;
case kOptFoldexpr:
return &wp->w_p_fde_flags;
case kOptFoldtext:
return &wp->w_p_fdt_flags;
case kOptIndentexpr:
return &wp->w_buffer->b_p_inde_flags;
case kOptFormatexpr:
return &wp->w_buffer->b_p_fex_flags;
case kOptIncludeexpr:
return &wp->w_buffer->b_p_inex_flags;
default:
break;
}
}
// Nothing special, return global flags field.
return &options[opt_idx].flags;
}
/// Redraw the window title and/or tab page text later.
void redraw_titles(void)
{
need_maketitle = true;
redraw_tabline = true;
}
/// Return true if "val" is a valid name: only consists of alphanumeric ASCII
/// characters or characters in "allowed".
bool valid_name(const char *val, const char *allowed)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
for (const char *s = val; *s != NUL; s++) {
if (!ASCII_ISALNUM(*s)
&& vim_strchr(allowed, (uint8_t)(*s)) == NULL) {
return false;
}
}
return true;
}
void check_blending(win_T *wp)
{
wp->w_grid_alloc.blending =
wp->w_p_winbl > 0 || (wp->w_floating && wp->w_config.shadow);
}
/// Handle setting `winhighlight' in window "wp"
///
/// @param winhl when NULL: use "wp->w_p_winhl"
/// @param wp when NULL: only parse "winhl"
///
/// @return whether the option value is valid.
bool parse_winhl_opt(const char *winhl, win_T *wp)
{
const char *p = empty_string_option;
if (winhl != NULL) {
p = winhl;
} else if (wp != NULL) {
p = wp->w_p_winhl;
}
if (wp != NULL && wp->w_ns_hl_winhl < 0) {
// 'winhighlight' shouldn't be used for this window.
// Only check that the value is valid.
wp = NULL;
}
if (!*p) {
if (wp != NULL && wp->w_ns_hl_winhl > 0 && wp->w_ns_hl == wp->w_ns_hl_winhl) {
wp->w_ns_hl = 0;
wp->w_hl_needs_update = true;
}
return true;
}
int ns_hl = 0;
if (wp != NULL) {
if (wp->w_ns_hl_winhl == 0) {
wp->w_ns_hl_winhl = (int)nvim_create_namespace(NULL_STRING);
} else {
// Namespace already exists. Invalidate existing items.
DecorProvider *dp = get_decor_provider(wp->w_ns_hl_winhl, true);
dp->hl_valid++;
}
wp->w_ns_hl = wp->w_ns_hl_winhl;
ns_hl = wp->w_ns_hl;
}
while (*p) {
const char *colon = strchr(p, ':');
if (!colon) {
return false;
}
size_t nlen = (size_t)(colon - p);
const char *hi = colon + 1;
const char *commap = xstrchrnul(hi, ',');
size_t len = (size_t)(commap - hi);
int hl_id = len ? syn_check_group(hi, len) : -1;
if (hl_id == 0) {
return false;
}
int hl_id_link = nlen ? syn_check_group(p, nlen) : 0;
if (hl_id_link == 0) {
return false;
}
if (wp != NULL) {
HlAttrs attrs = HLATTRS_INIT;
attrs.rgb_ae_attr |= HL_GLOBAL;
ns_hl_def(ns_hl, hl_id_link, attrs, hl_id, NULL);
}
p = *commap ? commap + 1 : "";
}
if (wp != NULL) {
wp->w_hl_needs_update = true;
}
return true;
}
/// Get the script context of global option at index opt_idx.
sctx_T *get_option_sctx(OptIndex opt_idx)
{
assert(opt_idx != kOptInvalid);
return &options[opt_idx].last_set.script_ctx;
}
/// Set the script_ctx for an option, taking care of setting the buffer- or
/// window-local value.
void set_option_sctx(OptIndex opt_idx, int opt_flags, sctx_T script_ctx)
{
bool both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0;
nlua_set_sctx(&script_ctx);
LastSet last_set = {
.script_ctx = script_ctx,
.channel_id = current_channel_id,
};
// Modeline already has the line number set.
if (!(opt_flags & OPT_MODELINE)) {
last_set.script_ctx.sc_lnum += SOURCING_LNUM;
}
// Remember where the option was set. For local options need to do that
// in the buffer or window structure.
if (both || (opt_flags & OPT_GLOBAL) || option_is_global_only(opt_idx)) {
options[opt_idx].last_set = last_set;
}
if (both || (opt_flags & OPT_LOCAL)) {
if (option_has_scope(opt_idx, kOptScopeBuf)) {
curbuf->b_p_script_ctx[option_scope_idx(opt_idx, kOptScopeBuf)] = last_set;
} else if ((option_has_scope(opt_idx, kOptScopeWin))) {
curwin->w_p_script_ctx[option_scope_idx(opt_idx, kOptScopeWin)] = last_set;
if (both) {
// also setting the "all buffers" value
curwin->w_allbuf_opt.wo_script_ctx[option_scope_idx(opt_idx, kOptScopeWin)] = last_set;
}
}
}
}
/// Apply the OptionSet autocommand.
static void apply_optionset_autocmd(OptIndex opt_idx, int opt_flags, OptVal oldval, OptVal oldval_g,
OptVal oldval_l, OptVal newval, const char *errmsg)
{
// Don't do this while starting up, failure or recursively.
if (starting || errmsg != NULL || *get_vim_var_str(VV_OPTION_TYPE) != NUL) {
return;
}
char buf_type[7];
typval_T oldval_tv = optval_as_tv(oldval, false);
typval_T oldval_g_tv = optval_as_tv(oldval_g, false);
typval_T oldval_l_tv = optval_as_tv(oldval_l, false);
typval_T newval_tv = optval_as_tv(newval, false);
vim_snprintf(buf_type, sizeof(buf_type), "%s", (opt_flags & OPT_LOCAL) ? "local" : "global");
set_vim_var_tv(VV_OPTION_NEW, &newval_tv);
set_vim_var_tv(VV_OPTION_OLD, &oldval_tv);
set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
if (opt_flags & OPT_LOCAL) {
set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1);
set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv);
}
if (opt_flags & OPT_GLOBAL) {
set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1);
set_vim_var_tv(VV_OPTION_OLDGLOBAL, &oldval_tv);
}
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) {
set_vim_var_string(VV_OPTION_COMMAND, "set", -1);
set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_l_tv);
set_vim_var_tv(VV_OPTION_OLDGLOBAL, &oldval_g_tv);
}
if (opt_flags & OPT_MODELINE) {
set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1);
set_vim_var_tv(VV_OPTION_OLDLOCAL, &oldval_tv);
}
apply_autocmds(EVENT_OPTIONSET, options[opt_idx].fullname, NULL, false, NULL);
reset_v_option_vars();
}
/// Process the updated 'arabic' option value.
static const char *did_set_arabic(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
const char *errmsg = NULL;
if (win->w_p_arab) {
// 'arabic' is set, handle various sub-settings.
if (!p_tbidi) {
// set rightleft mode
if (!win->w_p_rl) {
win->w_p_rl = true;
changed_window_setting(win);
}
// Enable Arabic shaping (major part of what Arabic requires)
if (!p_arshape) {
p_arshape = true;
redraw_all_later(UPD_NOT_VALID);
}
}
// Arabic requires a utf-8 encoding, inform the user if it's not
// set.
if (strcmp(p_enc, "utf-8") != 0) {
static char *w_arabic = N_("W17: Arabic requires UTF-8, do ':set encoding=utf-8'");
msg_source(HLF_W);
msg(_(w_arabic), HLF_W);
set_vim_var_string(VV_WARNINGMSG, _(w_arabic), -1);
}
// set 'delcombine'
p_deco = true;
// Force-set the necessary keymap for arabic.
errmsg = set_option_value(kOptKeymap, STATIC_CSTR_AS_OPTVAL("arabic"), OPT_LOCAL);
} else {
// 'arabic' is reset, handle various sub-settings.
if (!p_tbidi) {
// reset rightleft mode
if (win->w_p_rl) {
win->w_p_rl = false;
changed_window_setting(win);
}
// 'arabicshape' isn't reset, it is a global option and
// another window may still need it "on".
}
// 'delcombine' isn't reset, it is a global option and another
// window may still want it "on".
// Revert to the default keymap
win->w_buffer->b_p_iminsert = B_IMODE_NONE;
win->w_buffer->b_p_imsearch = B_IMODE_USE_INSERT;
}
return errmsg;
}
/// Process the updated 'autochdir' option value.
static const char *did_set_autochdir(optset_T *args FUNC_ATTR_UNUSED)
{
// Change directories when the 'acd' option is set now.
do_autochdir();
return NULL;
}
/// Process the updated 'binary' option value.
static const char *did_set_binary(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
// when 'bin' is set also set some other options
set_options_bin((int)args->os_oldval.boolean, buf->b_p_bin, args->os_flags);
redraw_titles();
return NULL;
}
/// Process the updated 'buflisted' option value.
static const char *did_set_buflisted(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
// when 'buflisted' changes, trigger autocommands
if (args->os_oldval.boolean != buf->b_p_bl) {
apply_autocmds(buf->b_p_bl ? EVENT_BUFADD : EVENT_BUFDELETE,
NULL, NULL, true, buf);
}
return NULL;
}
/// Process the new 'cmdheight' option value.
static const char *did_set_cmdheight(optset_T *args)
{
OptInt old_value = args->os_oldval.number;
if (p_ch > Rows - min_rows(curtab) + 1) {
p_ch = Rows - min_rows(curtab) + 1;
}
// if p_ch changed value, change the command line height
// Only compute the new window layout when startup has been
// completed. Otherwise the frame sizes may be wrong.
if ((p_ch != old_value
|| tabline_height() + global_stl_height() + topframe->fr_height != Rows - p_ch)
&& full_screen) {
command_height();
}
return NULL;
}
/// Process the updated 'diff' option value.
static const char *did_set_diff(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
// May add or remove the buffer from the list of diff buffers.
diff_buf_adjust(win);
if (foldmethodIsDiff(win)) {
foldUpdateAll(win);
}
return NULL;
}
/// Process the updated 'endoffile' or 'endofline' or 'fixendofline' or 'bomb'
/// option value.
static const char *did_set_eof_eol_fixeol_bomb(optset_T *args FUNC_ATTR_UNUSED)
{
// redraw the window title and tab page text
redraw_titles();
return NULL;
}
/// Process the updated 'equalalways' option value.
static const char *did_set_equalalways(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
if (p_ea && !args->os_oldval.boolean) {
win_equal(win, false, 0);
}
return NULL;
}
/// Process the new 'foldlevel' option value.
static const char *did_set_foldlevel(optset_T *args FUNC_ATTR_UNUSED)
{
newFoldLevel();
return NULL;
}
/// Process the new 'foldminlines' option value.
static const char *did_set_foldminlines(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
foldUpdateAll(win);
return NULL;
}
/// Process the new 'foldnestmax' option value.
static const char *did_set_foldnestmax(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
if (foldmethodIsSyntax(win) || foldmethodIsIndent(win)) {
foldUpdateAll(win);
}
return NULL;
}
/// Process the new 'helpheight' option value.
static const char *did_set_helpheight(optset_T *args)
{
// Change window height NOW
if (!ONE_WINDOW) {
if (curbuf->b_help && curwin->w_height < p_hh) {
win_setheight((int)p_hh);
}
}
return NULL;
}
/// Process the updated 'hlsearch' option value.
static const char *did_set_hlsearch(optset_T *args FUNC_ATTR_UNUSED)
{
// when 'hlsearch' is set or reset: reset no_hlsearch
set_no_hlsearch(false);
return NULL;
}
/// Process the updated 'ignorecase' option value.
static const char *did_set_ignorecase(optset_T *args FUNC_ATTR_UNUSED)
{
// when 'ignorecase' is set or reset and 'hlsearch' is set, redraw
if (p_hls) {
redraw_all_later(UPD_SOME_VALID);
}
return NULL;
}
/// Process the new 'iminset' option value.
static const char *did_set_iminsert(optset_T *args FUNC_ATTR_UNUSED)
{
showmode();
// Show/unshow value of 'keymap' in status lines.
status_redraw_curbuf();
return NULL;
}
/// Process the updated 'langnoremap' option value.
static const char *did_set_langnoremap(optset_T *args FUNC_ATTR_UNUSED)
{
// 'langnoremap' -> !'langremap'
p_lrm = !p_lnr;
return NULL;
}
/// Process the updated 'langremap' option value.
static const char *did_set_langremap(optset_T *args FUNC_ATTR_UNUSED)
{
// 'langremap' -> !'langnoremap'
p_lnr = !p_lrm;
return NULL;
}
/// Process the new 'laststatus' option value.
static const char *did_set_laststatus(optset_T *args)
{
OptInt old_value = args->os_oldval.number;
OptInt value = args->os_newval.number;
// When switching to global statusline, decrease topframe height
// Also clear the cmdline to remove the ruler if there is one
if (value == 3 && old_value != 3) {
frame_new_height(topframe, topframe->fr_height - STATUS_HEIGHT, false, false, false);
win_comp_pos();
clear_cmdline = true;
}
// When switching from global statusline, increase height of topframe by STATUS_HEIGHT
// in order to to re-add the space that was previously taken by the global statusline
if (old_value == 3 && value != 3) {
frame_new_height(topframe, topframe->fr_height + STATUS_HEIGHT, false, false, false);
win_comp_pos();
}
last_status(false); // (re)set last window status line.
return NULL;
}
/// Process the updated 'lines' or 'columns' option value.
static const char *did_set_lines_or_columns(optset_T *args)
{
// If the screen (shell) height has been changed, assume it is the
// physical screenheight.
if (p_lines != Rows || p_columns != Columns) {
// Changing the screen size is not allowed while updating the screen.
if (updating_screen) {
OptVal oldval = (OptVal){ .type = kOptValTypeNumber, .data = args->os_oldval };
set_option_varp(args->os_idx, args->os_varp, oldval, false);
} else if (full_screen) {
screen_resize((int)p_columns, (int)p_lines);
} else {
// TODO(bfredl): is this branch ever needed?
// Postpone the resizing; check the size and cmdline position for
// messages.
Rows = (int)p_lines;
Columns = (int)p_columns;
check_screensize();
int new_row = (int)(Rows - MAX(p_ch, 1));
if (cmdline_row > new_row && Rows > p_ch) {
assert(p_ch >= 0 && new_row <= INT_MAX);
cmdline_row = new_row;
}
}
if (p_window >= Rows || !option_was_set(kOptWindow)) {
p_window = Rows - 1;
}
}
// Adjust 'scrolljump' if needed.
if (p_sj >= Rows && full_screen) {
p_sj = Rows / 2;
}
return NULL;
}
/// Process the updated 'lisp' option value.
static const char *did_set_lisp(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
// When 'lisp' option changes include/exclude '-' in keyword characters.
buf_init_chartab(buf, false); // ignore errors
return NULL;
}
/// Process the updated 'modifiable' option value.
static const char *did_set_modifiable(optset_T *args FUNC_ATTR_UNUSED)
{
// when 'modifiable' is changed, redraw the window title
redraw_titles();
return NULL;
}
/// Process the updated 'modified' option value.
static const char *did_set_modified(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
if (!args->os_newval.boolean) {
save_file_ff(buf); // Buffer is unchanged
}
redraw_titles();
buf->b_modified_was_set = (int)args->os_newval.boolean;
return NULL;
}
/// Process the updated 'number' or 'relativenumber' option value.
static const char *did_set_number_relativenumber(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
if (*win->w_p_stc != NUL) {
// When 'relativenumber'/'number' is changed and 'statuscolumn' is set, reset width.
win->w_nrwidth_line_count = 0;
}
check_signcolumn(NULL, win);
return NULL;
}
/// Process the new 'numberwidth' option value.
static const char *did_set_numberwidth(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
win->w_nrwidth_line_count = 0; // trigger a redraw
return NULL;
}
/// Process the updated 'paste' option value.
static const char *did_set_paste(optset_T *args FUNC_ATTR_UNUSED)
{
static int old_p_paste = false;
static int save_sm = 0;
static int save_sta = 0;
static int save_ru = 0;
static int save_ri = 0;
if (p_paste) {
// Paste switched from off to on.
// Save the current values, so they can be restored later.
if (!old_p_paste) {
// save options for each buffer
FOR_ALL_BUFFERS(buf) {
buf->b_p_tw_nopaste = buf->b_p_tw;
buf->b_p_wm_nopaste = buf->b_p_wm;
buf->b_p_sts_nopaste = buf->b_p_sts;
buf->b_p_ai_nopaste = buf->b_p_ai;
buf->b_p_et_nopaste = buf->b_p_et;
if (buf->b_p_vsts_nopaste) {
xfree(buf->b_p_vsts_nopaste);
}
buf->b_p_vsts_nopaste = buf->b_p_vsts && buf->b_p_vsts != empty_string_option
? xstrdup(buf->b_p_vsts)
: NULL;
}
// save global options
save_sm = p_sm;
save_sta = p_sta;
save_ru = p_ru;
save_ri = p_ri;
// save global values for local buffer options
p_ai_nopaste = p_ai;
p_et_nopaste = p_et;
p_sts_nopaste = p_sts;
p_tw_nopaste = p_tw;
p_wm_nopaste = p_wm;
if (p_vsts_nopaste) {
xfree(p_vsts_nopaste);
}
p_vsts_nopaste = p_vsts && p_vsts != empty_string_option ? xstrdup(p_vsts) : NULL;
}
// Always set the option values, also when 'paste' is set when it is
// already on.
// set options for each buffer
FOR_ALL_BUFFERS(buf) {
buf->b_p_tw = 0; // textwidth is 0
buf->b_p_wm = 0; // wrapmargin is 0
buf->b_p_sts = 0; // softtabstop is 0
buf->b_p_ai = 0; // no auto-indent
buf->b_p_et = 0; // no expandtab
if (buf->b_p_vsts) {
free_string_option(buf->b_p_vsts);
}
buf->b_p_vsts = empty_string_option;
XFREE_CLEAR(buf->b_p_vsts_array);
}
// set global options
p_sm = 0; // no showmatch
p_sta = 0; // no smarttab
if (p_ru) {
status_redraw_all(); // redraw to remove the ruler
}
p_ru = 0; // no ruler
p_ri = 0; // no reverse insert
// set global values for local buffer options
p_tw = 0;
p_wm = 0;
p_sts = 0;
p_ai = 0;
p_et = 0;
if (p_vsts) {
free_string_option(p_vsts);
}
p_vsts = empty_string_option;
} else if (old_p_paste) {
// Paste switched from on to off: Restore saved values.
// restore options for each buffer
FOR_ALL_BUFFERS(buf) {
buf->b_p_tw = buf->b_p_tw_nopaste;
buf->b_p_wm = buf->b_p_wm_nopaste;
buf->b_p_sts = buf->b_p_sts_nopaste;
buf->b_p_ai = buf->b_p_ai_nopaste;
buf->b_p_et = buf->b_p_et_nopaste;
if (buf->b_p_vsts) {
free_string_option(buf->b_p_vsts);
}
buf->b_p_vsts = buf->b_p_vsts_nopaste ? xstrdup(buf->b_p_vsts_nopaste) : empty_string_option;
xfree(buf->b_p_vsts_array);
if (buf->b_p_vsts && buf->b_p_vsts != empty_string_option) {
tabstop_set(buf->b_p_vsts, &buf->b_p_vsts_array);
} else {
buf->b_p_vsts_array = NULL;
}
}
// restore global options
p_sm = save_sm;
p_sta = save_sta;
if (p_ru != save_ru) {
status_redraw_all(); // redraw to draw the ruler
}
p_ru = save_ru;
p_ri = save_ri;
// set global values for local buffer options
p_ai = p_ai_nopaste;
p_et = p_et_nopaste;
p_sts = p_sts_nopaste;
p_tw = p_tw_nopaste;
p_wm = p_wm_nopaste;
if (p_vsts) {
free_string_option(p_vsts);
}
p_vsts = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : empty_string_option;
}
old_p_paste = p_paste;
// Remember where the dependent options were reset
didset_options_sctx((OPT_LOCAL | OPT_GLOBAL), p_paste_dep_opts);
return NULL;
}
/// Process the updated 'previewwindow' option value.
static const char *did_set_previewwindow(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
if (!win->w_p_pvw) {
return NULL;
}
// There can be only one window with 'previewwindow' set.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_pvw && wp != win) {
win->w_p_pvw = false;
return e_preview_window_already_exists;
}
}
return NULL;
}
/// Process the new 'pumblend' option value.
static const char *did_set_pumblend(optset_T *args FUNC_ATTR_UNUSED)
{
hl_invalidate_blends();
pum_grid.blending = (p_pb > 0);
if (pum_drawn()) {
pum_redraw();
}
return NULL;
}
/// Process the updated 'readonly' option value.
static const char *did_set_readonly(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
// when 'readonly' is reset globally, also reset readonlymode
if (!buf->b_p_ro && (args->os_flags & OPT_LOCAL) == 0) {
readonlymode = false;
}
// when 'readonly' is set may give W10 again
if (buf->b_p_ro) {
buf->b_did_warn = false;
}
redraw_titles();
return NULL;
}
/// Process the new 'scrollback' option value.
static const char *did_set_scrollback(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
OptInt old_value = args->os_oldval.number;
OptInt value = args->os_newval.number;
if (buf->terminal && value < old_value) {
// Force the scrollback to take immediate effect only when decreasing it.
on_scrollback_option_changed(buf->terminal);
}
return NULL;
}
/// Process the updated 'scrollbind' option value.
static const char *did_set_scrollbind(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
// when 'scrollbind' is set: snapshot the current position to avoid a jump
// at the end of normal_cmd()
if (!win->w_p_scb) {
return NULL;
}
do_check_scrollbind(false);
win->w_scbind_pos = get_vtopline(win);
return NULL;
}
#ifdef BACKSLASH_IN_FILENAME
/// Process the updated 'shellslash' option value.
static const char *did_set_shellslash(optset_T *args FUNC_ATTR_UNUSED)
{
if (p_ssl) {
psepc = '/';
psepcN = '\\';
pseps[0] = '/';
} else {
psepc = '\\';
psepcN = '/';
pseps[0] = '\\';
}
// need to adjust the file name arguments and buffer names.
buflist_slash_adjust();
alist_slash_adjust();
scriptnames_slash_adjust();
return NULL;
}
#endif
/// Process the new 'shiftwidth' or the 'tabstop' option value.
static const char *did_set_shiftwidth_tabstop(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
win_T *win = (win_T *)args->os_win;
OptInt *pp = (OptInt *)args->os_varp;
if (foldmethodIsIndent(win)) {
foldUpdateAll(win);
}
// When 'shiftwidth' changes, or it's zero and 'tabstop' changes:
// parse 'cinoptions'.
if (pp == &buf->b_p_sw || buf->b_p_sw == 0) {
parse_cino(buf);
}
return NULL;
}
/// Process the new 'showtabline' option value.
static const char *did_set_showtabline(optset_T *args FUNC_ATTR_UNUSED)
{
// (re)set tab page line
win_new_screen_rows(); // recompute window positions and heights
return NULL;
}
/// Process the updated 'smoothscroll' option value.
static const char *did_set_smoothscroll(optset_T *args FUNC_ATTR_UNUSED)
{
win_T *win = (win_T *)args->os_win;
if (!win->w_p_sms) {
win->w_skipcol = 0;
}
return NULL;
}
/// Process the updated 'spell' option value.
static const char *did_set_spell(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
if (win->w_p_spell) {
return parse_spelllang(win);
}
return NULL;
}
/// Process the updated 'swapfile' option value.
static const char *did_set_swapfile(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
// when 'swf' is set, create swapfile, when reset remove swapfile
if (buf->b_p_swf && p_uc) {
ml_open_file(buf); // create the swap file
} else {
// no need to reset buf->b_may_swap, ml_open_file() will check buf->b_p_swf
mf_close_file(buf, true); // remove the swap file
}
return NULL;
}
/// Process the new 'textwidth' option value.
static const char *did_set_textwidth(optset_T *args FUNC_ATTR_UNUSED)
{
FOR_ALL_TAB_WINDOWS(tp, wp) {
check_colorcolumn(NULL, wp);
}
return NULL;
}
/// Process the updated 'title' or the 'icon' option value.
static const char *did_set_title_icon(optset_T *args FUNC_ATTR_UNUSED)
{
// when 'title' changed, may need to change the title; same for 'icon'
did_set_title();
return NULL;
}
/// Process the new 'titlelen' option value.
static const char *did_set_titlelen(optset_T *args)
{
OptInt old_value = args->os_oldval.number;
// if 'titlelen' has changed, redraw the title
if (starting != NO_SCREEN && old_value != p_titlelen) {
need_maketitle = true;
}
return NULL;
}
/// Process the updated 'undofile' option value.
static const char *did_set_undofile(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
// Only take action when the option was set.
if (!buf->b_p_udf && !p_udf) {
return NULL;
}
// When reset we do not delete the undo file, the option may be set again
// without making any changes in between.
uint8_t hash[UNDO_HASH_SIZE];
FOR_ALL_BUFFERS(bp) {
// When 'undofile' is set globally: for every buffer, otherwise
// only for the current buffer: Try to read in the undofile,
// if one exists, the buffer wasn't changed and the buffer was
// loaded
if ((buf == bp
|| (args->os_flags & OPT_GLOBAL) || args->os_flags == 0)
&& !bufIsChanged(bp) && bp->b_ml.ml_mfp != NULL) {
u_compute_hash(bp, hash);
u_read_undo(NULL, hash, bp->b_fname);
}
}
return NULL;
}
/// Process the new global 'undolevels' option value.
const char *did_set_global_undolevels(OptInt value, OptInt old_value)
{
// sync undo before 'undolevels' changes
// use the old value, otherwise u_sync() may not work properly
p_ul = old_value;
u_sync(true);
p_ul = value;
return NULL;
}
/// Process the new buffer local 'undolevels' option value.
const char *did_set_buflocal_undolevels(buf_T *buf, OptInt value, OptInt old_value)
{
// use the old value, otherwise u_sync() may not work properly
buf->b_p_ul = old_value;
u_sync(true);
buf->b_p_ul = value;
return NULL;
}
/// Process the new 'undolevels' option value.
static const char *did_set_undolevels(optset_T *args)
{
buf_T *buf = (buf_T *)args->os_buf;
OptInt *pp = (OptInt *)args->os_varp;
if (pp == &p_ul) { // global 'undolevels'
did_set_global_undolevels(args->os_newval.number, args->os_oldval.number);
} else if (pp == &buf->b_p_ul) { // buffer local 'undolevels'
did_set_buflocal_undolevels(buf, args->os_newval.number, args->os_oldval.number);
}
return NULL;
}
/// Process the new 'updatecount' option value.
static const char *did_set_updatecount(optset_T *args)
{
OptInt old_value = args->os_oldval.number;
// when 'updatecount' changes from zero to non-zero, open swap files
if (p_uc && !old_value) {
ml_open_files();
}
return NULL;
}
/// Process the new 'wildchar' / 'wildcharm' option value.
static const char *did_set_wildchar(optset_T *args)
{
OptInt c = *(OptInt *)args->os_varp;
// Don't allow key values that wouldn't work as wildchar.
if (c == Ctrl_C || c == '\n' || c == '\r' || c == K_KENTER) {
return e_invarg;
}
return NULL;
}
/// Process the new 'winblend' option value.
static const char *did_set_winblend(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
OptInt old_value = args->os_oldval.number;
OptInt value = args->os_newval.number;
if (value != old_value) {
win->w_p_winbl = MAX(MIN(win->w_p_winbl, 100), 0);
win->w_hl_needs_update = true;
check_blending(win);
}
return NULL;
}
/// Process the new 'window' option value.
static const char *did_set_window(optset_T *args FUNC_ATTR_UNUSED)
{
if (p_window < 1) {
p_window = Rows - 1;
} else if (p_window >= Rows) {
p_window = Rows - 1;
}
return NULL;
}
/// Process the new 'winheight' value.
static const char *did_set_winheight(optset_T *args)
{
// Change window height NOW
if (!ONE_WINDOW) {
if (curwin->w_height < p_wh) {
win_setheight((int)p_wh);
}
}
return NULL;
}
/// Process the new 'winwidth' option value.
static const char *did_set_winwidth(optset_T *args)
{
if (!ONE_WINDOW && curwin->w_width < p_wiw) {
win_setwidth((int)p_wiw);
}
return NULL;
}
/// Process the updated 'wrap' option value.
static const char *did_set_wrap(optset_T *args)
{
win_T *win = (win_T *)args->os_win;
// Set w_leftcol or w_skipcol to zero.
if (win->w_p_wrap) {
win->w_leftcol = 0;
} else {
win->w_skipcol = 0;
}
return NULL;
}
// When 'syntax' is set, load the syntax of that name
static void do_syntax_autocmd(buf_T *buf, bool value_changed)
{
static int syn_recursive = 0;
syn_recursive++;
// Only pass true for "force" when the value changed or not used
// recursively, to avoid endless recurrence.
apply_autocmds(EVENT_SYNTAX, buf->b_p_syn, buf->b_fname,
value_changed || syn_recursive == 1, buf);
buf->b_flags |= BF_SYN_SET;
syn_recursive--;
}
static void do_spelllang_source(win_T *win)
{
char fname[200];
char *q = win->w_s->b_p_spl;
// Skip the first name if it is "cjk".
if (strncmp(q, "cjk,", 4) == 0) {
q += 4;
}
// Source the spell/LANG.{vim,lua} in 'runtimepath'.
// They could set 'spellcapcheck' depending on the language.
// Use the first name in 'spelllang' up to '_region' or
// '.encoding'.
char *p;
for (p = q; *p != NUL; p++) {
if (!ASCII_ISALNUM(*p) && *p != '-') {
break;
}
}
if (p > q) {
vim_snprintf(fname, sizeof(fname), "spell/%.*s.*", (int)(p - q), q);
source_runtime_vim_lua(fname, DIP_ALL);
}
}
/// Check the bounds of numeric options.
///
/// @param opt_idx Index in options[] table. Must not be kOptInvalid.
/// @param[in,out] newval Pointer to new option value. Will be set to bound checked value.
/// @param[out] errbuf Buffer for error message. Cannot be NULL.
/// @param errbuflen Length of error buffer.
///
/// @return Error message, if any.
static const char *check_num_option_bounds(OptIndex opt_idx, OptInt *newval, char *errbuf,
size_t errbuflen)
FUNC_ATTR_NONNULL_ARG(3)
{
const char *errmsg = NULL;
switch (opt_idx) {
case kOptLines:
if (*newval < min_rows_for_all_tabpages() && full_screen) {
vim_snprintf(errbuf, errbuflen, _("E593: Need at least %d lines"),
min_rows_for_all_tabpages());
errmsg = errbuf;
*newval = min_rows_for_all_tabpages();
}
// True max size is defined by check_screensize().
*newval = MIN(*newval, INT_MAX);
break;
case kOptColumns:
if (*newval < MIN_COLUMNS && full_screen) {
vim_snprintf(errbuf, errbuflen, _("E594: Need at least %d columns"), MIN_COLUMNS);
errmsg = errbuf;
*newval = MIN_COLUMNS;
}
// True max size is defined by check_screensize().
*newval = MIN(*newval, INT_MAX);
break;
case kOptPumblend:
*newval = MAX(MIN(*newval, 100), 0);
break;
case kOptScrolljump:
if ((*newval < -100 || *newval >= Rows) && full_screen) {
errmsg = e_scroll;
*newval = 1;
}
break;
case kOptScroll:
if ((*newval <= 0 || (*newval > curwin->w_height_inner && curwin->w_height_inner > 0))
&& full_screen) {
if (*newval != 0) {
errmsg = e_scroll;
}
*newval = win_default_scroll(curwin);
}
break;
default:
break;
}
return errmsg;
}
/// Validate and bound check option value.
///
/// @param opt_idx Index in options[] table. Must not be kOptInvalid.
/// @param[in,out] newval Pointer to new option value. Will be set to bound checked value.
/// @param[out] errbuf Buffer for error message. Cannot be NULL.
/// @param errbuflen Length of error buffer.
///
/// @return Error message, if any.
static const char *validate_num_option(OptIndex opt_idx, OptInt *newval, char *errbuf,
size_t errbuflen)
{
OptInt value = *newval;
// Many number options assume their value is in the signed int range.
if (value < INT_MIN || value > INT_MAX) {
return e_invarg;
}
switch (opt_idx) {
case kOptHelpheight:
case kOptTitlelen:
case kOptUpdatecount:
case kOptReport:
case kOptUpdatetime:
case kOptSidescroll:
case kOptFoldlevel:
case kOptShiftwidth:
case kOptTextwidth:
case kOptWritedelay:
case kOptTimeoutlen:
if (value < 0) {
return e_positive;
}
break;
case kOptWinheight:
if (value < 1) {
return e_positive;
} else if (p_wmh > value) {
return e_winheight;
}
break;
case kOptWinminheight:
if (value < 0) {
return e_positive;
} else if (value > p_wh) {
return e_winheight;
}
break;
case kOptWinwidth:
if (value < 1) {
return e_positive;
} else if (p_wmw > value) {
return e_winwidth;
}
break;
case kOptWinminwidth:
if (value < 0) {
return e_positive;
} else if (value > p_wiw) {
return e_winwidth;
}
break;
case kOptMaxcombine:
*newval = MAX_MCO;
break;
case kOptCmdheight:
if (value < 0) {
return e_positive;
}
break;
case kOptHistory:
if (value < 0) {
return e_positive;
} else if (value > 10000) {
return e_invarg;
}
break;
case kOptPyxversion:
if (value == 0) {
*newval = 3;
} else if (value != 3) {
return e_invarg;
}
break;
case kOptRegexpengine:
if (value < 0 || value > 2) {
return e_invarg;
}
break;
case kOptScrolloff:
if (value < 0 && full_screen) {
return e_positive;
}
break;
case kOptSidescrolloff:
if (value < 0 && full_screen) {
return e_positive;
}
break;
case kOptCmdwinheight:
if (value < 1) {
return e_positive;
}
break;
case kOptConceallevel:
if (value < 0) {
return e_positive;
} else if (value > 3) {
return e_invarg;
}
break;
case kOptNumberwidth:
if (value < 1) {
return e_positive;
} else if (value > MAX_NUMBERWIDTH) {
return e_invarg;
}
break;
case kOptIminsert:
if (value < 0 || value > B_IMODE_LAST) {
return e_invarg;
}
break;
case kOptImsearch:
if (value < -1 || value > B_IMODE_LAST) {
return e_invarg;
}
break;
case kOptChannel:
return e_invarg;
case kOptScrollback:
if (value < -1 || value > SB_MAX) {
return e_invarg;
}
break;
case kOptTabstop:
if (value < 1) {
return e_positive;
} else if (value > TABSTOP_MAX) {
return e_invarg;
}
break;
default:
break;
}
return check_num_option_bounds(opt_idx, newval, errbuf, errbuflen);
}
/// Called after an option changed: check if something needs to be redrawn.
void check_redraw_for(buf_T *buf, win_T *win, uint32_t flags)
{
// Careful: kOptFlagRedrAll is a combination of other redraw flags
bool all = (flags & kOptFlagRedrAll) == kOptFlagRedrAll;
if ((flags & kOptFlagRedrStat) || all) { // mark all status lines and window bars dirty
status_redraw_all();
}
if ((flags & kOptFlagRedrTabl) || all) { // mark tablines dirty
redraw_tabline = true;
}
if ((flags & kOptFlagRedrBuf) || (flags & kOptFlagRedrWin) || all) {
if (flags & kOptFlagHLOnly) {
redraw_later(win, UPD_NOT_VALID);
} else {
changed_window_setting(win);
}
}
if (flags & kOptFlagRedrBuf) {
redraw_buf_later(buf, UPD_NOT_VALID);
}
if (all) {
redraw_all_later(UPD_NOT_VALID);
}
}
void check_redraw(uint32_t flags)
{
check_redraw_for(curbuf, curwin, flags);
}
bool is_tty_option(const char *name)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return find_tty_option_end(name) != NULL;
}
#define TCO_BUFFER_SIZE 8
/// Get value of TTY option.
///
/// @param name Name of TTY option.
///
/// @return [allocated] TTY option value. Returns NIL_OPTVAL if option isn't a TTY option.
OptVal get_tty_option(const char *name)
{
char *value = NULL;
if (strequal(name, "t_Co")) {
if (t_colors <= 1) {
value = xstrdup("");
} else {
value = xmalloc(TCO_BUFFER_SIZE);
snprintf(value, TCO_BUFFER_SIZE, "%d", t_colors);
}
} else if (strequal(name, "term")) {
value = p_term ? xstrdup(p_term) : xstrdup("nvim");
} else if (strequal(name, "ttytype")) {
value = p_ttytype ? xstrdup(p_ttytype) : xstrdup("nvim");
} else if (is_tty_option(name)) {
// XXX: All other t_* options were removed in 3baba1e7.
value = xstrdup("");
}
return value == NULL ? NIL_OPTVAL : CSTR_AS_OPTVAL(value);
}
bool set_tty_option(const char *name, char *value)
{
if (strequal(name, "term")) {
if (p_term) {
xfree(p_term);
}
p_term = value;
return true;
}
if (strequal(name, "ttytype")) {
if (p_ttytype) {
xfree(p_ttytype);
}
p_ttytype = value;
return true;
}
return false;
}
/// Find index for an option. Don't go beyond `len` length.
///
/// @param[in] name Option name.
/// @param len Option name length.
///
/// @return Option index or kOptInvalid if option was not found.
OptIndex find_option_len(const char *const name, size_t len)
FUNC_ATTR_NONNULL_ALL
{
int index = find_option_hash(name, len);
return index >= 0 ? option_hash_elems[index].opt_idx : kOptInvalid;
}
/// Find index for an option.
///
/// @param[in] name Option name.
///
/// @return Option index or kOptInvalid if option was not found.
OptIndex find_option(const char *const name)
FUNC_ATTR_NONNULL_ALL
{
return find_option_len(name, strlen(name));
}
/// Free an allocated OptVal.
void optval_free(OptVal o)
{
switch (o.type) {
case kOptValTypeNil:
case kOptValTypeBoolean:
case kOptValTypeNumber:
break;
case kOptValTypeString:
// Don't free empty string option
if (o.data.string.data != empty_string_option) {
api_free_string(o.data.string);
}
break;
}
}
/// Copy an OptVal.
OptVal optval_copy(OptVal o)
{
switch (o.type) {
case kOptValTypeNil:
case kOptValTypeBoolean:
case kOptValTypeNumber:
return o;
case kOptValTypeString:
return STRING_OPTVAL(copy_string(o.data.string, NULL));
}
UNREACHABLE;
}
/// Check if two option values are equal.
bool optval_equal(OptVal o1, OptVal o2)
{
if (o1.type != o2.type) {
return false;
}
switch (o1.type) {
case kOptValTypeNil:
return true;
case kOptValTypeBoolean:
return o1.data.boolean == o2.data.boolean;
case kOptValTypeNumber:
return o1.data.number == o2.data.number;
case kOptValTypeString:
return o1.data.string.size == o2.data.string.size
&& (o1.data.string.data == o2.data.string.data
|| strnequal(o1.data.string.data, o2.data.string.data, o1.data.string.size));
}
UNREACHABLE;
}
/// Get type of option.
static OptValType option_get_type(const OptIndex opt_idx)
{
return options[opt_idx].type;
}
/// Create OptVal from var pointer.
///
/// @param opt_idx Option index in options[] table.
/// @param[out] varp Pointer to option variable.
///
/// @return Option value stored in varp.
OptVal optval_from_varp(OptIndex opt_idx, void *varp)
FUNC_ATTR_NONNULL_ARG(2)
{
// Special case: 'modified' is b_changed, but we also want to consider it set when 'ff' or 'fenc'
// changed.
if ((int *)varp == &curbuf->b_changed) {
return BOOLEAN_OPTVAL(curbufIsChanged());
}
OptValType type = option_get_type(opt_idx);
switch (type) {
case kOptValTypeNil:
return NIL_OPTVAL;
case kOptValTypeBoolean:
return BOOLEAN_OPTVAL(TRISTATE_FROM_INT(*(int *)varp));
case kOptValTypeNumber:
return NUMBER_OPTVAL(*(OptInt *)varp);
case kOptValTypeString:
return STRING_OPTVAL(cstr_as_string(*(char **)varp));
}
UNREACHABLE;
}
/// Set option var pointer value from OptVal.
///
/// @param opt_idx Option index in options[] table.
/// @param[out] varp Pointer to option variable.
/// @param[in] value New option value.
/// @param free_oldval Free old value.
static void set_option_varp(OptIndex opt_idx, void *varp, OptVal value, bool free_oldval)
FUNC_ATTR_NONNULL_ARG(2)
{
assert(option_has_type(opt_idx, value.type));
if (free_oldval) {
optval_free(optval_from_varp(opt_idx, varp));
}
switch (value.type) {
case kOptValTypeNil:
abort();
case kOptValTypeBoolean:
*(int *)varp = value.data.boolean;
return;
case kOptValTypeNumber:
*(OptInt *)varp = value.data.number;
return;
case kOptValTypeString:
*(char **)varp = value.data.string.data;
return;
}
UNREACHABLE;
}
/// Return C-string representation of OptVal. Caller must free the returned C-string.
static char *optval_to_cstr(OptVal o)
{
switch (o.type) {
case kOptValTypeNil:
return xstrdup("");
case kOptValTypeBoolean:
return xstrdup(o.data.boolean ? "true" : "false");
case kOptValTypeNumber: {
char *buf = xmalloc(NUMBUFLEN);
snprintf(buf, NUMBUFLEN, "%" PRId64, o.data.number);
return buf;
}
case kOptValTypeString: {
char *buf = xmalloc(o.data.string.size + 3);
snprintf(buf, o.data.string.size + 3, "\"%s\"", o.data.string.data);
return buf;
}
}
UNREACHABLE;
}
/// Convert an OptVal to an API Object.
Object optval_as_object(OptVal o)
{
switch (o.type) {
case kOptValTypeNil:
return NIL;
case kOptValTypeBoolean:
switch (o.data.boolean) {
case kFalse:
case kTrue:
return BOOLEAN_OBJ(o.data.boolean);
case kNone:
return NIL;
}
UNREACHABLE;
case kOptValTypeNumber:
return INTEGER_OBJ(o.data.number);
case kOptValTypeString:
return STRING_OBJ(o.data.string);
}
UNREACHABLE;
}
/// Convert an API Object to an OptVal.
OptVal object_as_optval(Object o, bool *error)
{
switch (o.type) {
case kObjectTypeNil:
return NIL_OPTVAL;
case kObjectTypeBoolean:
return BOOLEAN_OPTVAL(o.data.boolean);
case kObjectTypeInteger:
return NUMBER_OPTVAL((OptInt)o.data.integer);
case kObjectTypeString:
return STRING_OPTVAL(o.data.string);
default:
*error = true;
return NIL_OPTVAL;
}
UNREACHABLE;
}
/// Check if option is hidden.
///
/// @param opt_idx Option index in options[] table.
///
/// @return True if option is hidden, false otherwise. Returns false if option name is invalid.
bool is_option_hidden(OptIndex opt_idx)
{
// Hidden options are always immutable and point to their default value
return opt_idx != kOptInvalid && options[opt_idx].immutable
&& options[opt_idx].var == &options[opt_idx].def_val.data;
}
/// Check if option supports a specific type.
bool option_has_type(OptIndex opt_idx, OptValType type)
{
return opt_idx != kOptInvalid && options[opt_idx].type == type;
}
/// Check if option supports a specific scope.
bool option_has_scope(OptIndex opt_idx, OptScope scope)
{
// Ensure that scope flags variable can hold all scopes.
STATIC_ASSERT(kOptScopeSize <= sizeof(OptScopeFlags) * 8,
"Option scope_flags cannot fit all option scopes");
// Ensure that the scope is valid before accessing scope_flags.
assert(scope >= kOptScopeGlobal && scope < kOptScopeSize);
// Bitshift 1 by the value of scope to get the scope's corresponding flag, and check if it's set
// in the scope_flags bit field.
return get_option(opt_idx)->scope_flags & (1 << scope);
}
/// Check if option is global-local.
static inline bool option_is_global_local(OptIndex opt_idx)
{
// Global-local options have at least two types, so their type flag cannot be a power of two.
return opt_idx != kOptInvalid && !is_power_of_two(options[opt_idx].scope_flags);
}
/// Check if option only supports global scope.
static inline bool option_is_global_only(OptIndex opt_idx)
{
// For an option to be global-only, it has to only have a single scope, which means the scope
// flags must be a power of two, and it must have the global scope.
return opt_idx != kOptInvalid && is_power_of_two(options[opt_idx].scope_flags)
&& option_has_scope(opt_idx, kOptScopeGlobal);
}
/// Check if option only supports window scope.
static inline bool option_is_window_local(OptIndex opt_idx)
{
// For an option to be window-local it has to only have a single scope, which means the scope
// flags must be a power of two, and it must have the window scope.
return opt_idx != kOptInvalid && is_power_of_two(options[opt_idx].scope_flags)
&& option_has_scope(opt_idx, kOptScopeWin);
}
/// Get option index for scope.
ssize_t option_scope_idx(OptIndex opt_idx, OptScope scope)
{
return options[opt_idx].scope_idx[scope];
}
/// Get option flags.
///
/// @param opt_idx Option index in options[] table.
///
/// @return Option flags. Returns 0 for invalid option name.
uint32_t get_option_flags(OptIndex opt_idx)
{
return opt_idx == kOptInvalid ? 0 : options[opt_idx].flags;
}
/// Gets the value for an option.
///
/// @param opt_idx Option index in options[] table.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
///
/// @return [allocated] Option value. Returns NIL_OPTVAL for invalid option index.
OptVal get_option_value(OptIndex opt_idx, int opt_flags)
{
if (opt_idx == kOptInvalid) { // option not in the options[] table.
return NIL_OPTVAL;
}
vimoption_T *opt = &options[opt_idx];
void *varp = get_varp_scope(opt, opt_flags);
return optval_copy(optval_from_varp(opt_idx, varp));
}
/// Return information for option at 'opt_idx'
vimoption_T *get_option(OptIndex opt_idx)
{
assert(opt_idx != kOptInvalid);
return &options[opt_idx];
}
/// Get option value that represents an unset local value for an option.
/// TODO(famiu): Remove this once we have a dedicated OptVal type for unset local options.
///
/// @param opt_idx Option index in options[] table.
/// @param[in] varp Pointer to option variable.
///
/// @return Option value equal to the unset value for the option.
static OptVal get_option_unset_value(OptIndex opt_idx)
{
assert(opt_idx != kOptInvalid);
vimoption_T *opt = &options[opt_idx];
// For global-local options, use the unset value of the local value.
if (option_is_global_local(opt_idx)) {
// String global-local options always use an empty string for the unset value.
if (option_has_type(opt_idx, kOptValTypeString)) {
return STATIC_CSTR_AS_OPTVAL("");
}
switch (opt_idx) {
case kOptAutoread:
return BOOLEAN_OPTVAL(kNone);
case kOptScrolloff:
case kOptSidescrolloff:
return NUMBER_OPTVAL(-1);
case kOptUndolevels:
return NUMBER_OPTVAL(NO_LOCAL_UNDOLEVEL);
default:
abort();
}
}
// For options that aren't global-local, use the global value to represent an unset local value.
return optval_from_varp(opt_idx, get_varp_scope(opt, OPT_GLOBAL));
}
/// Check if local value of global-local option is unset for current buffer / window.
/// Always returns false for options that aren't global-local.
///
/// TODO(famiu): Remove this once we have an OptVal type to indicate an unset local value.
static bool is_option_local_value_unset(OptIndex opt_idx)
{
vimoption_T *opt = get_option(opt_idx);
// Local value of option that isn't global-local is always considered set.
if (!option_is_global_local(opt_idx)) {
return false;
}
void *varp_local = get_varp_scope(opt, OPT_LOCAL);
OptVal local_value = optval_from_varp(opt_idx, varp_local);
OptVal unset_local_value = get_option_unset_value(opt_idx);
return optval_equal(local_value, unset_local_value);
}
/// Handle side-effects of setting an option.
///
/// @param opt_idx Index in options[] table. Must not be kOptInvalid.
/// @param[in] varp Option variable pointer, cannot be NULL.
/// @param old_value Old option value.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
/// @param set_sid Script ID. Special values:
/// 0: Use current script ID.
/// SID_NONE: Don't set script ID.
/// @param direct Don't process side-effects.
/// @param value_replaced Value was replaced completely.
/// @param[out] errbuf Buffer for error message.
/// @param errbuflen Length of error buffer.
///
/// @return NULL on success, an untranslated error message on error.
static const char *did_set_option(OptIndex opt_idx, void *varp, OptVal old_value, OptVal new_value,
int opt_flags, scid_T set_sid, const bool direct,
const bool value_replaced, char *errbuf, size_t errbuflen)
{
vimoption_T *opt = &options[opt_idx];
const char *errmsg = NULL;
bool restore_chartab = false;
bool value_changed = false;
bool value_checked = false;
optset_T did_set_cb_args = {
.os_varp = varp,
.os_idx = opt_idx,
.os_flags = opt_flags,
.os_oldval = old_value.data,
.os_newval = new_value.data,
.os_value_checked = false,
.os_value_changed = false,
.os_restore_chartab = false,
.os_errbuf = errbuf,
.os_errbuflen = errbuflen,
.os_buf = curbuf,
.os_win = curwin
};
if (direct) {
// Don't do any extra processing if setting directly.
}
// Disallow changing immutable options.
else if (opt->immutable && !optval_equal(old_value, new_value)) {
errmsg = e_unsupportedoption;
}
// Disallow changing some options from secure mode.
else if ((secure || sandbox != 0) && (opt->flags & kOptFlagSecure)) {
errmsg = e_secure;
}
// Check for a "normal" directory or file name in some string options.
else if (new_value.type == kOptValTypeString
&& check_illegal_path_names(*(char **)varp, opt->flags)) {
errmsg = e_invarg;
} else if (opt->opt_did_set_cb != NULL) {
// Invoke the option specific callback function to validate and apply the new value.
errmsg = opt->opt_did_set_cb(&did_set_cb_args);
// The 'filetype' and 'syntax' option callback functions may change the os_value_changed field.
value_changed = did_set_cb_args.os_value_changed;
// The 'keymap', 'filetype' and 'syntax' option callback functions may change the
// os_value_checked field.
value_checked = did_set_cb_args.os_value_checked;
// The 'isident', 'iskeyword', 'isprint' and 'isfname' options may change the character table.
// On failure, this needs to be restored.
restore_chartab = did_set_cb_args.os_restore_chartab;
}
// If option is hidden or if an error is detected, restore the previous value and don't do any
// further processing.
if (errmsg != NULL) {
set_option_varp(opt_idx, varp, old_value, true);
// When resetting some values, need to act on it.
if (restore_chartab) {
buf_init_chartab(curbuf, true);
}
return errmsg;
}
// Re-assign the new value as its value may get freed or modified by the option callback.
new_value = optval_from_varp(opt_idx, varp);
if (set_sid != SID_NONE) {
sctx_T script_ctx;
if (set_sid == 0) {
script_ctx = current_sctx;
} else {
script_ctx.sc_sid = set_sid;
script_ctx.sc_seq = 0;
script_ctx.sc_lnum = 0;
}
// Remember where the option was set.
set_option_sctx(opt_idx, opt_flags, script_ctx);
}
optval_free(old_value);
const bool scope_both = (opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0;
if (scope_both) {
if (option_is_global_local(opt_idx)) {
// Global option with local value set to use global value.
// Free the local value and clear it.
void *varp_local = get_varp_scope(opt, OPT_LOCAL);
OptVal local_unset_value = get_option_unset_value(opt_idx);
set_option_varp(opt_idx, varp_local, optval_copy(local_unset_value), true);
} else {
// May set global value for local option.
void *varp_global = get_varp_scope(opt, OPT_GLOBAL);
set_option_varp(opt_idx, varp_global, optval_copy(new_value), true);
}
}
// Don't do anything else if setting the option directly.
if (direct) {
return errmsg;
}
// Trigger the autocommand only after setting the flags.
if (varp == &curbuf->b_p_syn) {
do_syntax_autocmd(curbuf, value_changed);
} else if (varp == &curbuf->b_p_ft) {
// 'filetype' is set, trigger the FileType autocommand
// Skip this when called from a modeline
// Force autocmd when the filetype was changed
if (!(opt_flags & OPT_MODELINE) || value_changed) {
do_filetype_autocmd(curbuf, value_changed);
}
} else if (varp == &curwin->w_s->b_p_spl) {
do_spelllang_source(curwin);
}
// In case 'ruler' or 'showcmd' or 'columns' or 'ls' changed.
comp_col();
if (varp == &p_mouse) {
setmouse(); // in case 'mouse' changed
} else if ((varp == &p_flp || varp == &(curbuf->b_p_flp)) && curwin->w_briopt_list) {
// Changing Formatlistpattern when briopt includes the list setting:
// redraw
redraw_all_later(UPD_NOT_VALID);
} else if (varp == &p_wbr || varp == &(curwin->w_p_wbr)) {
// add / remove window bars for 'winbar'
set_winbar(true);
}
if (curwin->w_curswant != MAXCOL
&& (opt->flags & (kOptFlagCurswant | kOptFlagRedrAll)) != 0
&& (opt->flags & kOptFlagHLOnly) == 0) {
curwin->w_set_curswant = true;
}
check_redraw(opt->flags);
if (errmsg == NULL) {
uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
opt->flags |= kOptFlagWasSet;
// When an option is set in the sandbox, from a modeline or in secure mode set the kOptFlagInsecure
// flag. Otherwise, if a new value is stored reset the flag.
if (!value_checked && (secure || sandbox != 0 || (opt_flags & OPT_MODELINE))) {
*p |= kOptFlagInsecure;
} else if (value_replaced) {
*p &= ~(unsigned)kOptFlagInsecure;
}
}
return errmsg;
}
/// Validate the new value for an option.
///
/// @param opt_idx Index in options[] table. Must not be kOptInvalid.
/// @param newval[in,out] New option value. Might be modified.
static const char *validate_option_value(const OptIndex opt_idx, OptVal *newval, int opt_flags,
char *errbuf, size_t errbuflen)
{
const char *errmsg = NULL;
vimoption_T *opt = &options[opt_idx];
// Always allow unsetting local value of global-local option.
if (option_is_global_local(opt_idx) && (opt_flags & OPT_LOCAL)
&& optval_equal(*newval, get_option_unset_value(opt_idx))) {
return NULL;
}
if (newval->type == kOptValTypeNil) {
// Don't try to unset local value if scope is global.
// TODO(famiu): Change this to forbid changing all non-local scopes when the API scope bug is
// fixed.
if (opt_flags == OPT_GLOBAL) {
errmsg = _("Cannot unset global option value");
} else {
*newval = optval_copy(get_option_unset_value(opt_idx));
}
} else if (!option_has_type(opt_idx, newval->type)) {
char *rep = optval_to_cstr(*newval);
const char *type_str = optval_type_get_name(opt->type);
snprintf(errbuf, IOSIZE, _("Invalid value for option '%s': expected %s, got %s %s"),
opt->fullname, type_str, optval_type_get_name(newval->type), rep);
xfree(rep);
errmsg = errbuf;
} else if (newval->type == kOptValTypeNumber) {
// Validate and bound check num option values.
errmsg = validate_num_option(opt_idx, &newval->data.number, errbuf, errbuflen);
}
return errmsg;
}
/// Set the value of an option using an OptVal.
///
/// @param opt_idx Index in options[] table. Must not be kOptInvalid.
/// @param value New option value. Might get freed.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
/// @param set_sid Script ID. Special values:
/// 0: Use current script ID.
/// SID_NONE: Don't set script ID.
/// @param direct Don't process side-effects.
/// @param value_replaced Value was replaced completely.
/// @param[out] errbuf Buffer for error message.
/// @param errbuflen Length of error buffer.
///
/// @return NULL on success, an untranslated error message on error.
static const char *set_option(const OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid,
const bool direct, const bool value_replaced, char *errbuf,
size_t errbuflen)
{
assert(opt_idx != kOptInvalid);
const char *errmsg = NULL;
if (!direct) {
errmsg = validate_option_value(opt_idx, &value, opt_flags, errbuf, errbuflen);
if (errmsg != NULL) {
optval_free(value);
return errmsg;
}
}
vimoption_T *opt = &options[opt_idx];
const bool scope_local = opt_flags & OPT_LOCAL;
const bool scope_global = opt_flags & OPT_GLOBAL;
const bool scope_both = !scope_local && !scope_global;
// Whether local value of global-local option is unset.
// NOTE: When this is true, it also implies that the option is global-local.
const bool is_opt_local_unset = is_option_local_value_unset(opt_idx);
// When using ":set opt=val" for a global option with a local value the local value will be reset,
// use the global value in that case.
void *varp
= scope_both && option_is_global_local(opt_idx) ? opt->var : get_varp_scope(opt, opt_flags);
void *varp_local = get_varp_scope(opt, OPT_LOCAL);
void *varp_global = get_varp_scope(opt, OPT_GLOBAL);
OptVal old_value = optval_from_varp(opt_idx, varp);
OptVal old_global_value = optval_from_varp(opt_idx, varp_global);
// If local value of global-local option is unset, use global value as local value.
OptVal old_local_value = is_opt_local_unset
? old_global_value
: optval_from_varp(opt_idx, varp_local);
// Value that's actually being used.
// For local scope of a global-local option, it's equal to the global value if the local value is
// unset. In every other case, it is the same as old_value.
// This value is used instead of old_value when triggering the OptionSet autocommand.
OptVal used_old_value = (scope_local && is_opt_local_unset)
? optval_from_varp(opt_idx, get_varp(opt))
: old_value;
// Save the old values and the new value in case they get changed.
OptVal saved_used_value = optval_copy(used_old_value);
OptVal saved_old_global_value = optval_copy(old_global_value);
OptVal saved_old_local_value = optval_copy(old_local_value);
// New value (and varp) may become invalid if the buffer is closed by autocommands.
OptVal saved_new_value = optval_copy(value);
uint32_t *p = insecure_flag(curwin, opt_idx, opt_flags);
const int secure_saved = secure;
// When an option is set in the sandbox, from a modeline or in secure mode, then deal with side
// effects in secure mode. Also when the value was set with the kOptFlagInsecure flag and is not
// completely replaced.
if ((opt_flags & OPT_MODELINE) || sandbox != 0 || (!value_replaced && (*p & kOptFlagInsecure))) {
secure = 1;
}
// Set option through its variable pointer.
set_option_varp(opt_idx, varp, value, false);
// Process any side effects.
errmsg = did_set_option(opt_idx, varp, old_value, value, opt_flags, set_sid, direct,
value_replaced, errbuf, errbuflen);
secure = secure_saved;
if (errmsg == NULL && !direct) {
if (!starting) {
apply_optionset_autocmd(opt_idx, opt_flags, saved_used_value, saved_old_global_value,
saved_old_local_value, saved_new_value, errmsg);
}
if (opt->flags & kOptFlagUIOption) {
ui_call_option_set(cstr_as_string(opt->fullname), optval_as_object(saved_new_value));
}
}
// Free copied values as they are not needed anymore
optval_free(saved_used_value);
optval_free(saved_old_local_value);
optval_free(saved_old_global_value);
optval_free(saved_new_value);
return errmsg;
}
/// Set option value directly, without processing any side effects.
///
/// @param opt_idx Option index in options[] table.
/// @param value Option value.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
/// @param set_sid Script ID. Special values:
/// 0: Use current script ID.
/// SID_NONE: Don't set script ID.
void set_option_direct(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid)
{
static char errbuf[IOSIZE];
if (is_option_hidden(opt_idx)) {
return;
}
const char *errmsg = set_option(opt_idx, optval_copy(value), opt_flags, set_sid, true, true,
errbuf, sizeof(errbuf));
assert(errmsg == NULL);
(void)errmsg; // ignore unused warning
}
/// Set option value directly for buffer / window, without processing any side effects.
///
/// @param opt_idx Option index in options[] table.
/// @param value Option value.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
/// @param set_sid Script ID. Special values:
/// 0: Use current script ID.
/// SID_NONE: Don't set script ID.
/// @param scope Option scope. See OptScope in option.h.
/// @param[in] from Target buffer/window.
void set_option_direct_for(OptIndex opt_idx, OptVal value, int opt_flags, scid_T set_sid,
OptScope scope, void *const from)
{
buf_T *save_curbuf = curbuf;
win_T *save_curwin = curwin;
// Don't use switch_option_context(), as that calls aucmd_prepbuf(), which may have unintended
// side-effects when setting an option directly. Just change the values of curbuf and curwin if
// needed, no need to properly switch the window / buffer.
switch (scope) {
case kOptScopeGlobal:
break;
case kOptScopeWin:
curwin = (win_T *)from;
curbuf = curwin->w_buffer;
break;
case kOptScopeBuf:
curbuf = (buf_T *)from;
break;
}
set_option_direct(opt_idx, value, opt_flags, set_sid);
curwin = save_curwin;
curbuf = save_curbuf;
}
/// Set the value of an option.
///
/// @param opt_idx Index in options[] table. Must not be kOptInvalid.
/// @param[in] value Option value. If NIL_OPTVAL, the option value is cleared.
/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
///
/// @return NULL on success, an untranslated error message on error.
const char *set_option_value(const OptIndex opt_idx, const OptVal value, int opt_flags)
{
assert(opt_idx != kOptInvalid);
static char errbuf[IOSIZE];
uint32_t flags = options[opt_idx].flags;
// Disallow changing some options in the sandbox
if (sandbox > 0 && (flags & kOptFlagSecure)) {
return _(e_sandbox);
}
return set_option(opt_idx, optval_copy(value), opt_flags, 0, false, true, errbuf, sizeof(errbuf));
}
/// Unset the local value of a global-local option.
///
/// @param opt_idx Index in options[] table. Must not be kOptInvalid.
///
/// @return NULL on success, an untranslated error message on error.
static inline const char *unset_option_local_value(const OptIndex opt_idx)
{
assert(option_is_global_local(opt_idx));
return set_option_value(opt_idx, get_option_unset_value(opt_idx), OPT_LOCAL);
}
/// Set the value of an option. Supports TTY options, unlike set_option_value().
///
/// @param name Option name. Used for error messages and for setting TTY options.
/// @param opt_idx Option indx in options[] table. If kOptInvalid, `name` is used to
/// check if the option is a TTY option, and an error is shown if it's not.
/// If the option is a TTY option, the function fails silently.
/// @param value Option value. If NIL_OPTVAL, the option value is cleared.
/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
///
/// @return NULL on success, an untranslated error message on error.
const char *set_option_value_handle_tty(const char *name, OptIndex opt_idx, const OptVal value,
int opt_flags)
FUNC_ATTR_NONNULL_ARG(1)
{
static char errbuf[IOSIZE];
if (opt_idx == kOptInvalid) {
if (is_tty_option(name)) {
return NULL; // Fail silently; many old vimrcs set t_xx options.
}
snprintf(errbuf, sizeof(errbuf), _(e_unknown_option2), name);
return errbuf;
}
return set_option_value(opt_idx, value, opt_flags);
}
/// Call set_option_value() and when an error is returned, report it.
///
/// @param opt_idx Option index in options[] table.
/// @param value Option value. If NIL_OPTVAL, the option value is cleared.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
void set_option_value_give_err(const OptIndex opt_idx, OptVal value, int opt_flags)
{
const char *errmsg = set_option_value(opt_idx, value, opt_flags);
if (errmsg != NULL) {
emsg(_(errmsg));
}
}
/// Switch current context to get/set option value for window/buffer.
///
/// @param[out] ctx Current context. switchwin_T for window and aco_save_T for buffer.
/// @param scope Option scope. See OptScope in option.h.
/// @param[in] from Target buffer/window.
/// @param[out] err Error message, if any.
///
/// @return true if context was switched, false otherwise.
static bool switch_option_context(void *const ctx, OptScope scope, void *const from, Error *err)
{
switch (scope) {
case kOptScopeGlobal:
return false;
case kOptScopeWin: {
win_T *const win = (win_T *)from;
switchwin_T *const switchwin = (switchwin_T *)ctx;
if (win == curwin) {
return false;
}
if (switch_win_noblock(switchwin, win, win_find_tabpage(win), true)
== FAIL) {
restore_win_noblock(switchwin, true);
if (ERROR_SET(err)) {
return false;
}
api_set_error(err, kErrorTypeException, "Problem while switching windows");
return false;
}
return true;
}
case kOptScopeBuf: {
buf_T *const buf = (buf_T *)from;
aco_save_T *const aco = (aco_save_T *)ctx;
if (buf == curbuf) {
return false;
}
aucmd_prepbuf(aco, buf);
return true;
}
}
UNREACHABLE;
}
/// Restore context after getting/setting option for window/buffer. See switch_option_context() for
/// params.
static void restore_option_context(void *const ctx, OptScope scope)
{
switch (scope) {
case kOptScopeGlobal:
break;
case kOptScopeWin:
restore_win_noblock((switchwin_T *)ctx, true);
break;
case kOptScopeBuf:
aucmd_restbuf((aco_save_T *)ctx);
break;
}
}
/// Get option value for buffer / window.
///
/// @param opt_idx Option index in options[] table.
/// @param[out] flagsp Set to the option flags (see OptFlags) (if not NULL).
/// @param[in] scope Option scope (can be OPT_LOCAL, OPT_GLOBAL or a combination).
/// @param[out] hidden Whether option is hidden.
/// @param scope Option scope. See OptScope in option.h.
/// @param[in] from Target buffer/window.
/// @param[out] err Error message, if any.
///
/// @return Option value. Must be freed by caller.
OptVal get_option_value_for(OptIndex opt_idx, int opt_flags, const OptScope scope, void *const from,
Error *err)
{
switchwin_T switchwin;
aco_save_T aco;
void *ctx = scope == kOptScopeWin ? (void *)&switchwin
: (scope == kOptScopeBuf ? (void *)&aco : NULL);
bool switched = switch_option_context(ctx, scope, from, err);
if (ERROR_SET(err)) {
return NIL_OPTVAL;
}
OptVal retv = get_option_value(opt_idx, opt_flags);
if (switched) {
restore_option_context(ctx, scope);
}
return retv;
}
/// Set option value for buffer / window.
///
/// @param name Option name.
/// @param opt_idx Option index in options[] table.
/// @param[in] value Option value.
/// @param[in] opt_flags Flags: OPT_LOCAL, OPT_GLOBAL, or 0 (both).
/// @param scope Option scope. See OptScope in option.h.
/// @param[in] from Target buffer/window.
/// @param[out] err Error message, if any.
void set_option_value_for(const char *name, OptIndex opt_idx, OptVal value, const int opt_flags,
const OptScope scope, void *const from, Error *err)
FUNC_ATTR_NONNULL_ARG(1)
{
switchwin_T switchwin;
aco_save_T aco;
void *ctx = scope == kOptScopeWin ? (void *)&switchwin
: (scope == kOptScopeBuf ? (void *)&aco : NULL);
bool switched = switch_option_context(ctx, scope, from, err);
if (ERROR_SET(err)) {
return;
}
const char *const errmsg = set_option_value_handle_tty(name, opt_idx, value, opt_flags);
if (errmsg) {
api_set_error(err, kErrorTypeException, "%s", errmsg);
}
if (switched) {
restore_option_context(ctx, scope);
}
}
/// if 'all' == false: show changed options
/// if 'all' == true: show all normal options
///
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
static void showoptions(bool all, int opt_flags)
{
#define INC 20
#define GAP 3
vimoption_T **items = xmalloc(sizeof(vimoption_T *) * OPTION_COUNT);
msg_ext_set_kind("list_cmd");
// Highlight title
if (opt_flags & OPT_GLOBAL) {
msg_puts_title(_("\n--- Global option values ---"));
} else if (opt_flags & OPT_LOCAL) {
msg_puts_title(_("\n--- Local option values ---"));
} else {
msg_puts_title(_("\n--- Options ---"));
}
// Do the loop two times:
// 1. display the short items
// 2. display the long items (only strings and numbers)
// When "opt_flags" has OPT_ONECOLUMN do everything in run 2.
for (int run = 1; run <= 2 && !got_int; run++) {
// collect the items in items[]
int item_count = 0;
vimoption_T *opt;
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
opt = &options[opt_idx];
// apply :filter /pat/
if (message_filtered(opt->fullname)) {
continue;
}
void *varp = NULL;
if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) != 0) {
if (!option_is_global_only(opt_idx)) {
varp = get_varp_scope(opt, opt_flags);
}
} else {
varp = get_varp(opt);
}
if (varp != NULL && (all || !optval_default(opt_idx, varp))) {
int len;
if (opt_flags & OPT_ONECOLUMN) {
len = Columns;
} else if (option_has_type(opt_idx, kOptValTypeBoolean)) {
len = 1; // a toggle option fits always
} else {
option_value2string(opt, opt_flags);
len = (int)strlen(opt->fullname) + vim_strsize(NameBuff) + 1;
}
if ((len <= INC - GAP && run == 1)
|| (len > INC - GAP && run == 2)) {
items[item_count++] = opt;
}
}
}
int rows;
// display the items
if (run == 1) {
assert(Columns <= INT_MAX - GAP
&& Columns + GAP >= INT_MIN + 3
&& (Columns + GAP - 3) / INC >= INT_MIN
&& (Columns + GAP - 3) / INC <= INT_MAX);
int cols = (Columns + GAP - 3) / INC;
if (cols == 0) {
cols = 1;
}
rows = (item_count + cols - 1) / cols;
} else { // run == 2
rows = item_count;
}
for (int row = 0; row < rows && !got_int; row++) {
msg_putchar('\n'); // go to next line
if (got_int) { // 'q' typed in more
break;
}
int col = 0;
for (int i = row; i < item_count; i += rows) {
msg_col = col; // make columns
showoneopt(items[i], opt_flags);
col += INC;
}
os_breakcheck();
}
}
xfree(items);
}
/// Return true if option "p" has its default value.
static int optval_default(OptIndex opt_idx, void *varp)
{
vimoption_T *opt = &options[opt_idx];
// Hidden options always use their default value.
if (is_option_hidden(opt_idx)) {
return true;
}
OptVal current_val = optval_from_varp(opt_idx, varp);
OptVal default_val = opt->def_val;
return optval_equal(current_val, default_val);
}
/// Send update to UIs with values of UI relevant options
void ui_refresh_options(void)
{
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
uint32_t flags = options[opt_idx].flags;
if (!(flags & kOptFlagUIOption)) {
continue;
}
String name = cstr_as_string(options[opt_idx].fullname);
Object value = optval_as_object(optval_from_varp(opt_idx, options[opt_idx].var));
ui_call_option_set(name, value);
}
if (p_mouse != NULL) {
setmouse();
}
}
/// showoneopt: show the value of one option
/// must not be called with a hidden option!
///
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
static void showoneopt(vimoption_T *opt, int opt_flags)
{
int save_silent = silent_mode;
silent_mode = false;
info_message = true; // use stdout, not stderr
OptIndex opt_idx = get_opt_idx(opt);
void *varp = get_varp_scope(opt, opt_flags);
// for 'modified' we also need to check if 'ff' or 'fenc' changed.
if (option_has_type(opt_idx, kOptValTypeBoolean)
&& ((int *)varp == &curbuf->b_changed ? !curbufIsChanged() : !*(int *)varp)) {
msg_puts("no");
} else if (option_has_type(opt_idx, kOptValTypeBoolean) && *(int *)varp < 0) {
msg_puts("--");
} else {
msg_puts(" ");
}
msg_puts(opt->fullname);
if (!(option_has_type(opt_idx, kOptValTypeBoolean))) {
msg_putchar('=');
// put value string in NameBuff
option_value2string(opt, opt_flags);
msg_outtrans(NameBuff, 0, false);
}
silent_mode = save_silent;
info_message = false;
}
/// Write modified options as ":set" commands to a file.
///
/// There are three values for "opt_flags":
/// OPT_GLOBAL: Write global option values and fresh values of
/// buffer-local options (used for start of a session
/// file).
/// OPT_GLOBAL + OPT_LOCAL: Idem, add fresh values of window-local options for
/// curwin (used for a vimrc file).
/// OPT_LOCAL: Write buffer-local option values for curbuf, fresh
/// and local values for window-local options of
/// curwin. Local values are also written when at the
/// default value, because a modeline or autocommand
/// may have set them when doing ":edit file" and the
/// user has set them back at the default or fresh
/// value.
/// When "local_only" is true, don't write fresh
/// values, only local values (for ":mkview").
/// (fresh value = value used for a new buffer or window for a local option).
///
/// Return FAIL on error, OK otherwise.
int makeset(FILE *fd, int opt_flags, int local_only)
{
// Some options are never written:
// - Options that don't have a default (terminal name, columns, lines).
// - Terminal options.
// - Hidden options.
//
// Do the loop over "options[]" twice: once for options with the
// kOptFlagPriMkrc flag and once without.
for (int pri = 1; pri >= 0; pri--) {
vimoption_T *opt;
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
opt = &options[opt_idx];
if (!(opt->flags & kOptFlagNoMkrc)
&& ((pri == 1) == ((opt->flags & kOptFlagPriMkrc) != 0))) {
// skip global option when only doing locals
if (option_is_global_only(opt_idx) && !(opt_flags & OPT_GLOBAL)) {
continue;
}
// Do not store options like 'bufhidden' and 'syntax' in a vimrc
// file, they are always buffer-specific.
if ((opt_flags & OPT_GLOBAL) && (opt->flags & kOptFlagNoGlob)) {
continue;
}
void *varp = get_varp_scope(opt, opt_flags); // currently used value
// Hidden options are never written.
if (!varp) {
continue;
}
// Global values are only written when not at the default value.
if ((opt_flags & OPT_GLOBAL) && optval_default(opt_idx, varp)) {
continue;
}
if ((opt_flags & OPT_SKIPRTP)
&& (opt->var == &p_rtp || opt->var == &p_pp)) {
continue;
}
int round = 2;
void *varp_local = NULL; // fresh value
if (option_is_window_local(opt_idx)) {
// skip window-local option when only doing globals
if (!(opt_flags & OPT_LOCAL)) {
continue;
}
// When fresh value of window-local option is not at the
// default, need to write it too.
if (!(opt_flags & OPT_GLOBAL) && !local_only) {
void *varp_fresh = get_varp_scope(opt, OPT_GLOBAL); // local value
if (!optval_default(opt_idx, varp_fresh)) {
round = 1;
varp_local = varp;
varp = varp_fresh;
}
}
}
// Round 1: fresh value for window-local options.
// Round 2: other values
for (; round <= 2; varp = varp_local, round++) {
char *cmd;
if (round == 1 || (opt_flags & OPT_GLOBAL)) {
cmd = "set";
} else {
cmd = "setlocal";
}
bool do_endif = false;
// Don't set 'syntax' and 'filetype' again if the value is already right, avoids reloading
// the syntax file.
if (opt_idx == kOptSyntax || opt_idx == kOptFiletype) {
if (fprintf(fd, "if &%s != '%s'", opt->fullname,
*(char **)(varp)) < 0
|| put_eol(fd) < 0) {
return FAIL;
}
do_endif = true;
}
if (put_set(fd, cmd, opt_idx, varp) == FAIL) {
return FAIL;
}
if (do_endif) {
if (put_line(fd, "endif") == FAIL) {
return FAIL;
}
}
}
}
}
}
return OK;
}
/// Generate set commands for the local fold options only. Used when
/// 'sessionoptions' or 'viewoptions' contains "folds" but not "options".
int makefoldset(FILE *fd)
{
if (put_set(fd, "setlocal", kOptFoldmethod, &curwin->w_p_fdm) == FAIL
|| put_set(fd, "setlocal", kOptFoldexpr, &curwin->w_p_fde) == FAIL
|| put_set(fd, "setlocal", kOptFoldmarker, &curwin->w_p_fmr) == FAIL
|| put_set(fd, "setlocal", kOptFoldignore, &curwin->w_p_fdi) == FAIL
|| put_set(fd, "setlocal", kOptFoldlevel, &curwin->w_p_fdl) == FAIL
|| put_set(fd, "setlocal", kOptFoldminlines, &curwin->w_p_fml) == FAIL
|| put_set(fd, "setlocal", kOptFoldnestmax, &curwin->w_p_fdn) == FAIL
|| put_set(fd, "setlocal", kOptFoldenable, &curwin->w_p_fen) == FAIL) {
return FAIL;
}
return OK;
}
/// Print the ":set" command to set a single option to file.
///
/// @param fd File descriptor.
/// @param cmd Command name.
/// @param opt_idx Option index in options[] table.
/// @param varp Pointer to option variable.
///
/// @return FAIL on error, OK otherwise.
static int put_set(FILE *fd, char *cmd, OptIndex opt_idx, void *varp)
{
OptVal value = optval_from_varp(opt_idx, varp);
vimoption_T *opt = &options[opt_idx];
char *name = opt->fullname;
uint64_t flags = opt->flags;
if (option_is_global_local(opt_idx) && varp != opt->var
&& optval_equal(value, get_option_unset_value(opt_idx))) {
// Processing unset local value of global-local option. Do nothing.
return OK;
}
switch (value.type) {
case kOptValTypeNil:
abort();
case kOptValTypeBoolean: {
assert(value.data.boolean != kNone);
bool value_bool = TRISTATE_TO_BOOL(value.data.boolean, false);
if (fprintf(fd, "%s %s%s", cmd, value_bool ? "" : "no", name) < 0) {
return FAIL;
}
break;
}
case kOptValTypeNumber: {
if (fprintf(fd, "%s %s=", cmd, name) < 0) {
return FAIL;
}
OptInt value_num = value.data.number;
OptInt wc;
if (wc_use_keyname(varp, &wc)) {
// print 'wildchar' and 'wildcharm' as a key name
if (fputs(get_special_key_name((int)wc, 0), fd) < 0) {
return FAIL;
}
} else if (fprintf(fd, "%" PRId64, value_num) < 0) {
return FAIL;
}
break;
}
case kOptValTypeString: {
if (fprintf(fd, "%s %s=", cmd, name) < 0) {
return FAIL;
}
const char *value_str = value.data.string.data;
char *buf = NULL;
char *part = NULL;
if (value_str != NULL) {
if ((flags & kOptFlagExpand) != 0) {
size_t size = (size_t)strlen(value_str) + 1;
// replace home directory in the whole option value into "buf"
buf = xmalloc(size);
home_replace(NULL, value_str, buf, size, false);
// If the option value is longer than MAXPATHL, we need to append
// each comma separated part of the option separately, so that it
// can be expanded when read back.
if (size >= MAXPATHL && (flags & kOptFlagComma) != 0
&& vim_strchr(value_str, ',') != NULL) {
part = xmalloc(size);
// write line break to clear the option, e.g. ':set rtp='
if (put_eol(fd) == FAIL) {
goto fail;
}
char *p = buf;
while (*p != NUL) {
// for each comma separated option part, append value to
// the option, :set rtp+=value
if (fprintf(fd, "%s %s+=", cmd, name) < 0) {
goto fail;
}
copy_option_part(&p, part, size, ",");
if (put_escstr(fd, part, 2) == FAIL || put_eol(fd) == FAIL) {
goto fail;
}
}
xfree(buf);
xfree(part);
return OK;
}
if (put_escstr(fd, buf, 2) == FAIL) {
xfree(buf);
return FAIL;
}
xfree(buf);
} else if (put_escstr(fd, value_str, 2) == FAIL) {
return FAIL;
}
}
break;
fail:
xfree(buf);
xfree(part);
return FAIL;
}
}
if (put_eol(fd) < 0) {
return FAIL;
}
return OK;
}
void *get_varp_scope_from(vimoption_T *p, int opt_flags, buf_T *buf, win_T *win)
{
OptIndex opt_idx = get_opt_idx(p);
if ((opt_flags & OPT_GLOBAL) && !option_is_global_only(opt_idx)) {
if (option_is_window_local(opt_idx)) {
return GLOBAL_WO(get_varp_from(p, buf, win));
}
return p->var;
}
if ((opt_flags & OPT_LOCAL) && option_is_global_local(opt_idx)) {
switch (opt_idx) {
case kOptFormatprg:
return &(buf->b_p_fp);
case kOptFindfunc:
return &(buf->b_p_ffu);
case kOptErrorformat:
return &(buf->b_p_efm);
case kOptGrepprg:
return &(buf->b_p_gp);
case kOptMakeprg:
return &(buf->b_p_mp);
case kOptEqualprg:
return &(buf->b_p_ep);
case kOptKeywordprg:
return &(buf->b_p_kp);
case kOptPath:
return &(buf->b_p_path);
case kOptAutoread:
return &(buf->b_p_ar);
case kOptTags:
return &(buf->b_p_tags);
case kOptTagcase:
return &(buf->b_p_tc);
case kOptSidescrolloff:
return &(win->w_p_siso);
case kOptScrolloff:
return &(win->w_p_so);
case kOptDefine:
return &(buf->b_p_def);
case kOptInclude:
return &(buf->b_p_inc);
case kOptCompleteopt:
return &(buf->b_p_cot);
case kOptDictionary:
return &(buf->b_p_dict);
case kOptThesaurus:
return &(buf->b_p_tsr);
case kOptThesaurusfunc:
return &(buf->b_p_tsrfu);
case kOptTagfunc:
return &(buf->b_p_tfu);
case kOptShowbreak:
return &(win->w_p_sbr);
case kOptStatusline:
return &(win->w_p_stl);
case kOptWinbar:
return &(win->w_p_wbr);
case kOptUndolevels:
return &(buf->b_p_ul);
case kOptLispwords:
return &(buf->b_p_lw);
case kOptBackupcopy:
return &(buf->b_p_bkc);
case kOptMakeencoding:
return &(buf->b_p_menc);
case kOptFillchars:
return &(win->w_p_fcs);
case kOptListchars:
return &(win->w_p_lcs);
case kOptVirtualedit:
return &(win->w_p_ve);
default:
abort();
}
}
return get_varp_from(p, buf, win);
}
/// Get pointer to option variable, depending on local or global scope.
///
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
void *get_varp_scope(vimoption_T *p, int opt_flags)
{
return get_varp_scope_from(p, opt_flags, curbuf, curwin);
}
/// Get pointer to option variable at 'opt_idx', depending on local or global
/// scope.
void *get_option_varp_scope_from(OptIndex opt_idx, int opt_flags, buf_T *buf, win_T *win)
{
return get_varp_scope_from(&(options[opt_idx]), opt_flags, buf, win);
}
void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
{
OptIndex opt_idx = get_opt_idx(p);
// Hidden options and global-only options always use the same var pointer
if (is_option_hidden(opt_idx) || option_is_global_only(opt_idx)) {
return p->var;
}
switch (opt_idx) {
// global option with local value: use local value if it's been set
case kOptEqualprg:
return *buf->b_p_ep != NUL ? &buf->b_p_ep : p->var;
case kOptKeywordprg:
return *buf->b_p_kp != NUL ? &buf->b_p_kp : p->var;
case kOptPath:
return *buf->b_p_path != NUL ? &(buf->b_p_path) : p->var;
case kOptAutoread:
return buf->b_p_ar >= 0 ? &(buf->b_p_ar) : p->var;
case kOptTags:
return *buf->b_p_tags != NUL ? &(buf->b_p_tags) : p->var;
case kOptTagcase:
return *buf->b_p_tc != NUL ? &(buf->b_p_tc) : p->var;
case kOptSidescrolloff:
return win->w_p_siso >= 0 ? &(win->w_p_siso) : p->var;
case kOptScrolloff:
return win->w_p_so >= 0 ? &(win->w_p_so) : p->var;
case kOptBackupcopy:
return *buf->b_p_bkc != NUL ? &(buf->b_p_bkc) : p->var;
case kOptDefine:
return *buf->b_p_def != NUL ? &(buf->b_p_def) : p->var;
case kOptInclude:
return *buf->b_p_inc != NUL ? &(buf->b_p_inc) : p->var;
case kOptCompleteopt:
return *buf->b_p_cot != NUL ? &(buf->b_p_cot) : p->var;
case kOptDictionary:
return *buf->b_p_dict != NUL ? &(buf->b_p_dict) : p->var;
case kOptThesaurus:
return *buf->b_p_tsr != NUL ? &(buf->b_p_tsr) : p->var;
case kOptThesaurusfunc:
return *buf->b_p_tsrfu != NUL ? &(buf->b_p_tsrfu) : p->var;
case kOptFormatprg:
return *buf->b_p_fp != NUL ? &(buf->b_p_fp) : p->var;
case kOptFindfunc:
return *buf->b_p_ffu != NUL ? &(buf->b_p_ffu) : p->var;
case kOptErrorformat:
return *buf->b_p_efm != NUL ? &(buf->b_p_efm) : p->var;
case kOptGrepprg:
return *buf->b_p_gp != NUL ? &(buf->b_p_gp) : p->var;
case kOptMakeprg:
return *buf->b_p_mp != NUL ? &(buf->b_p_mp) : p->var;
case kOptShowbreak:
return *win->w_p_sbr != NUL ? &(win->w_p_sbr) : p->var;
case kOptStatusline:
return *win->w_p_stl != NUL ? &(win->w_p_stl) : p->var;
case kOptWinbar:
return *win->w_p_wbr != NUL ? &(win->w_p_wbr) : p->var;
case kOptUndolevels:
return buf->b_p_ul != NO_LOCAL_UNDOLEVEL ? &(buf->b_p_ul) : p->var;
case kOptLispwords:
return *buf->b_p_lw != NUL ? &(buf->b_p_lw) : p->var;
case kOptMakeencoding:
return *buf->b_p_menc != NUL ? &(buf->b_p_menc) : p->var;
case kOptFillchars:
return *win->w_p_fcs != NUL ? &(win->w_p_fcs) : p->var;
case kOptListchars:
return *win->w_p_lcs != NUL ? &(win->w_p_lcs) : p->var;
case kOptVirtualedit:
return *win->w_p_ve != NUL ? &win->w_p_ve : p->var;
case kOptArabic:
return &(win->w_p_arab);
case kOptList:
return &(win->w_p_list);
case kOptSpell:
return &(win->w_p_spell);
case kOptCursorcolumn:
return &(win->w_p_cuc);
case kOptCursorline:
return &(win->w_p_cul);
case kOptCursorlineopt:
return &(win->w_p_culopt);
case kOptColorcolumn:
return &(win->w_p_cc);
case kOptDiff:
return &(win->w_p_diff);
case kOptFoldcolumn:
return &(win->w_p_fdc);
case kOptFoldenable:
return &(win->w_p_fen);
case kOptFoldignore:
return &(win->w_p_fdi);
case kOptFoldlevel:
return &(win->w_p_fdl);
case kOptFoldmethod:
return &(win->w_p_fdm);
case kOptFoldminlines:
return &(win->w_p_fml);
case kOptFoldnestmax:
return &(win->w_p_fdn);
case kOptFoldexpr:
return &(win->w_p_fde);
case kOptFoldtext:
return &(win->w_p_fdt);
case kOptFoldmarker:
return &(win->w_p_fmr);
case kOptNumber:
return &(win->w_p_nu);
case kOptRelativenumber:
return &(win->w_p_rnu);
case kOptNumberwidth:
return &(win->w_p_nuw);
case kOptWinfixbuf:
return &(win->w_p_wfb);
case kOptWinfixheight:
return &(win->w_p_wfh);
case kOptWinfixwidth:
return &(win->w_p_wfw);
case kOptPreviewwindow:
return &(win->w_p_pvw);
case kOptRightleft:
return &(win->w_p_rl);
case kOptRightleftcmd:
return &(win->w_p_rlc);
case kOptScroll:
return &(win->w_p_scr);
case kOptSmoothscroll:
return &(win->w_p_sms);
case kOptWrap:
return &(win->w_p_wrap);
case kOptLinebreak:
return &(win->w_p_lbr);
case kOptBreakindent:
return &(win->w_p_bri);
case kOptBreakindentopt:
return &(win->w_p_briopt);
case kOptScrollbind:
return &(win->w_p_scb);
case kOptCursorbind:
return &(win->w_p_crb);
case kOptConcealcursor:
return &(win->w_p_cocu);
case kOptConceallevel:
return &(win->w_p_cole);
case kOptAutoindent:
return &(buf->b_p_ai);
case kOptBinary:
return &(buf->b_p_bin);
case kOptBomb:
return &(buf->b_p_bomb);
case kOptBufhidden:
return &(buf->b_p_bh);
case kOptBuftype:
return &(buf->b_p_bt);
case kOptBuflisted:
return &(buf->b_p_bl);
case kOptChannel:
return &(buf->b_p_channel);
case kOptCopyindent:
return &(buf->b_p_ci);
case kOptCindent:
return &(buf->b_p_cin);
case kOptCinkeys:
return &(buf->b_p_cink);
case kOptCinoptions:
return &(buf->b_p_cino);
case kOptCinscopedecls:
return &(buf->b_p_cinsd);
case kOptCinwords:
return &(buf->b_p_cinw);
case kOptComments:
return &(buf->b_p_com);
case kOptCommentstring:
return &(buf->b_p_cms);
case kOptComplete:
return &(buf->b_p_cpt);
#ifdef BACKSLASH_IN_FILENAME
case kOptCompleteslash:
return &(buf->b_p_csl);
#endif
case kOptCompletefunc:
return &(buf->b_p_cfu);
case kOptOmnifunc:
return &(buf->b_p_ofu);
case kOptEndoffile:
return &(buf->b_p_eof);
case kOptEndofline:
return &(buf->b_p_eol);
case kOptFixendofline:
return &(buf->b_p_fixeol);
case kOptExpandtab:
return &(buf->b_p_et);
case kOptFileencoding:
return &(buf->b_p_fenc);
case kOptFileformat:
return &(buf->b_p_ff);
case kOptFiletype:
return &(buf->b_p_ft);
case kOptFormatoptions:
return &(buf->b_p_fo);
case kOptFormatlistpat:
return &(buf->b_p_flp);
case kOptIminsert:
return &(buf->b_p_iminsert);
case kOptImsearch:
return &(buf->b_p_imsearch);
case kOptInfercase:
return &(buf->b_p_inf);
case kOptIskeyword:
return &(buf->b_p_isk);
case kOptIncludeexpr:
return &(buf->b_p_inex);
case kOptIndentexpr:
return &(buf->b_p_inde);
case kOptIndentkeys:
return &(buf->b_p_indk);
case kOptFormatexpr:
return &(buf->b_p_fex);
case kOptLisp:
return &(buf->b_p_lisp);
case kOptLispoptions:
return &(buf->b_p_lop);
case kOptModeline:
return &(buf->b_p_ml);
case kOptMatchpairs:
return &(buf->b_p_mps);
case kOptModifiable:
return &(buf->b_p_ma);
case kOptModified:
return &(buf->b_changed);
case kOptNrformats:
return &(buf->b_p_nf);
case kOptPreserveindent:
return &(buf->b_p_pi);
case kOptQuoteescape:
return &(buf->b_p_qe);
case kOptReadonly:
return &(buf->b_p_ro);
case kOptScrollback:
return &(buf->b_p_scbk);
case kOptSmartindent:
return &(buf->b_p_si);
case kOptSofttabstop:
return &(buf->b_p_sts);
case kOptSuffixesadd:
return &(buf->b_p_sua);
case kOptSwapfile:
return &(buf->b_p_swf);
case kOptSynmaxcol:
return &(buf->b_p_smc);
case kOptSyntax:
return &(buf->b_p_syn);
case kOptSpellcapcheck:
return &(win->w_s->b_p_spc);
case kOptSpellfile:
return &(win->w_s->b_p_spf);
case kOptSpelllang:
return &(win->w_s->b_p_spl);
case kOptSpelloptions:
return &(win->w_s->b_p_spo);
case kOptShiftwidth:
return &(buf->b_p_sw);
case kOptTagfunc:
return &(buf->b_p_tfu);
case kOptTabstop:
return &(buf->b_p_ts);
case kOptTextwidth:
return &(buf->b_p_tw);
case kOptUndofile:
return &(buf->b_p_udf);
case kOptWrapmargin:
return &(buf->b_p_wm);
case kOptVarsofttabstop:
return &(buf->b_p_vsts);
case kOptVartabstop:
return &(buf->b_p_vts);
case kOptKeymap:
return &(buf->b_p_keymap);
case kOptSigncolumn:
return &(win->w_p_scl);
case kOptWinhighlight:
return &(win->w_p_winhl);
case kOptWinblend:
return &(win->w_p_winbl);
case kOptStatuscolumn:
return &(win->w_p_stc);
default:
iemsg(_("E356: get_varp ERROR"));
}
// always return a valid pointer to avoid a crash!
return &(buf->b_p_wm);
}
/// Get option index from option pointer
static inline OptIndex get_opt_idx(vimoption_T *opt)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
return (OptIndex)(opt - options);
}
/// Get pointer to option variable.
static inline void *get_varp(vimoption_T *p)
{
return get_varp_from(p, curbuf, curwin);
}
/// Get the value of 'equalprg', either the buffer-local one or the global one.
char *get_equalprg(void)
{
if (*curbuf->b_p_ep == NUL) {
return p_ep;
}
return curbuf->b_p_ep;
}
/// Get the value of 'findfunc', either the buffer-local one or the global one.
char *get_findfunc(void)
{
if (*curbuf->b_p_ffu == NUL) {
return p_ffu;
}
return curbuf->b_p_ffu;
}
/// Copy options from one window to another.
/// Used when splitting a window.
void win_copy_options(win_T *wp_from, win_T *wp_to)
{
copy_winopt(&wp_from->w_onebuf_opt, &wp_to->w_onebuf_opt);
copy_winopt(&wp_from->w_allbuf_opt, &wp_to->w_allbuf_opt);
didset_window_options(wp_to, true);
}
static char *copy_option_val(const char *val)
{
if (val == empty_string_option) {
return empty_string_option; // no need to allocate memory
}
return xstrdup(val);
}
/// Copy the options from one winopt_T to another.
/// Doesn't free the old option values in "to", use clear_winopt() for that.
/// The 'scroll' option is not copied, because it depends on the window height.
/// The 'previewwindow' option is reset, there can be only one preview window.
void copy_winopt(winopt_T *from, winopt_T *to)
{
to->wo_arab = from->wo_arab;
to->wo_list = from->wo_list;
to->wo_lcs = copy_option_val(from->wo_lcs);
to->wo_fcs = copy_option_val(from->wo_fcs);
to->wo_nu = from->wo_nu;
to->wo_rnu = from->wo_rnu;
to->wo_ve = copy_option_val(from->wo_ve);
to->wo_ve_flags = from->wo_ve_flags;
to->wo_nuw = from->wo_nuw;
to->wo_rl = from->wo_rl;
to->wo_rlc = copy_option_val(from->wo_rlc);
to->wo_sbr = copy_option_val(from->wo_sbr);
to->wo_stl = copy_option_val(from->wo_stl);
to->wo_wbr = copy_option_val(from->wo_wbr);
to->wo_wrap = from->wo_wrap;
to->wo_wrap_save = from->wo_wrap_save;
to->wo_lbr = from->wo_lbr;
to->wo_bri = from->wo_bri;
to->wo_briopt = copy_option_val(from->wo_briopt);
to->wo_scb = from->wo_scb;
to->wo_scb_save = from->wo_scb_save;
to->wo_sms = from->wo_sms;
to->wo_crb = from->wo_crb;
to->wo_crb_save = from->wo_crb_save;
to->wo_siso = from->wo_siso;
to->wo_so = from->wo_so;
to->wo_spell = from->wo_spell;
to->wo_cuc = from->wo_cuc;
to->wo_cul = from->wo_cul;
to->wo_culopt = copy_option_val(from->wo_culopt);
to->wo_cc = copy_option_val(from->wo_cc);
to->wo_diff = from->wo_diff;
to->wo_diff_saved = from->wo_diff_saved;
to->wo_cocu = copy_option_val(from->wo_cocu);
to->wo_cole = from->wo_cole;
to->wo_fdc = copy_option_val(from->wo_fdc);
to->wo_fdc_save = from->wo_diff_saved ? xstrdup(from->wo_fdc_save) : empty_string_option;
to->wo_fen = from->wo_fen;
to->wo_fen_save = from->wo_fen_save;
to->wo_fdi = copy_option_val(from->wo_fdi);
to->wo_fml = from->wo_fml;
to->wo_fdl = from->wo_fdl;
to->wo_fdl_save = from->wo_fdl_save;
to->wo_fdm = copy_option_val(from->wo_fdm);
to->wo_fdm_save = from->wo_diff_saved ? xstrdup(from->wo_fdm_save) : empty_string_option;
to->wo_fdn = from->wo_fdn;
to->wo_fde = copy_option_val(from->wo_fde);
to->wo_fdt = copy_option_val(from->wo_fdt);
to->wo_fmr = copy_option_val(from->wo_fmr);
to->wo_scl = copy_option_val(from->wo_scl);
to->wo_winhl = copy_option_val(from->wo_winhl);
to->wo_winbl = from->wo_winbl;
to->wo_stc = copy_option_val(from->wo_stc);
// Copy the script context so that we know were the value was last set.
memmove(to->wo_script_ctx, from->wo_script_ctx, sizeof(to->wo_script_ctx));
check_winopt(to); // don't want NULL pointers
}
/// Check string options in a window for a NULL value.
void check_win_options(win_T *win)
{
check_winopt(&win->w_onebuf_opt);
check_winopt(&win->w_allbuf_opt);
}
/// Check for NULL pointers in a winopt_T and replace them with empty_string_option.
static void check_winopt(winopt_T *wop)
{
check_string_option(&wop->wo_fdc);
check_string_option(&wop->wo_fdc_save);
check_string_option(&wop->wo_fdi);
check_string_option(&wop->wo_fdm);
check_string_option(&wop->wo_fdm_save);
check_string_option(&wop->wo_fde);
check_string_option(&wop->wo_fdt);
check_string_option(&wop->wo_fmr);
check_string_option(&wop->wo_scl);
check_string_option(&wop->wo_rlc);
check_string_option(&wop->wo_sbr);
check_string_option(&wop->wo_stl);
check_string_option(&wop->wo_culopt);
check_string_option(&wop->wo_cc);
check_string_option(&wop->wo_cocu);
check_string_option(&wop->wo_briopt);
check_string_option(&wop->wo_winhl);
check_string_option(&wop->wo_lcs);
check_string_option(&wop->wo_fcs);
check_string_option(&wop->wo_ve);
check_string_option(&wop->wo_wbr);
check_string_option(&wop->wo_stc);
}
/// Free the allocated memory inside a winopt_T.
void clear_winopt(winopt_T *wop)
{
clear_string_option(&wop->wo_fdc);
clear_string_option(&wop->wo_fdc_save);
clear_string_option(&wop->wo_fdi);
clear_string_option(&wop->wo_fdm);
clear_string_option(&wop->wo_fdm_save);
clear_string_option(&wop->wo_fde);
clear_string_option(&wop->wo_fdt);
clear_string_option(&wop->wo_fmr);
clear_string_option(&wop->wo_scl);
clear_string_option(&wop->wo_rlc);
clear_string_option(&wop->wo_sbr);
clear_string_option(&wop->wo_stl);
clear_string_option(&wop->wo_culopt);
clear_string_option(&wop->wo_cc);
clear_string_option(&wop->wo_cocu);
clear_string_option(&wop->wo_briopt);
clear_string_option(&wop->wo_winhl);
clear_string_option(&wop->wo_lcs);
clear_string_option(&wop->wo_fcs);
clear_string_option(&wop->wo_ve);
clear_string_option(&wop->wo_wbr);
clear_string_option(&wop->wo_stc);
}
void didset_window_options(win_T *wp, bool valid_cursor)
{
// Set w_leftcol or w_skipcol to zero.
if (wp->w_p_wrap) {
wp->w_leftcol = 0;
} else {
wp->w_skipcol = 0;
}
check_colorcolumn(NULL, wp);
briopt_check(NULL, wp);
fill_culopt_flags(NULL, wp);
set_chars_option(wp, wp->w_p_fcs, kFillchars, true, NULL, 0);
set_chars_option(wp, wp->w_p_lcs, kListchars, true, NULL, 0);
parse_winhl_opt(NULL, wp); // sets w_hl_needs_update also for w_p_winbl
check_blending(wp);
set_winbar_win(wp, false, valid_cursor);
check_signcolumn(NULL, wp);
wp->w_grid_alloc.blending = wp->w_p_winbl > 0;
}
#define COPY_OPT_SCTX(buf, bv) buf->b_p_script_ctx[bv] = options[buf_opt_idx[bv]].last_set
/// Copy global option values to local options for one buffer.
/// Used when creating a new buffer and sometimes when entering a buffer.
/// flags:
/// BCO_ENTER We will enter the buffer "buf".
/// BCO_ALWAYS Always copy the options, but only set b_p_initialized when
/// appropriate.
/// BCO_NOHELP Don't copy the values to a help buffer.
void buf_copy_options(buf_T *buf, int flags)
{
bool should_copy = true;
char *save_p_isk = NULL; // init for GCC
bool did_isk = false;
// Skip this when the option defaults have not been set yet. Happens when
// main() allocates the first buffer.
if (p_cpo != NULL) {
//
// Always copy when entering and 'cpo' contains 'S'.
// Don't copy when already initialized.
// Don't copy when 'cpo' contains 's' and not entering.
// 'S' BCO_ENTER initialized 's' should_copy
// yes yes X X true
// yes no yes X false
// no X yes X false
// X no no yes false
// X no no no true
// no yes no X true
///
if ((vim_strchr(p_cpo, CPO_BUFOPTGLOB) == NULL || !(flags & BCO_ENTER))
&& (buf->b_p_initialized
|| (!(flags & BCO_ENTER)
&& vim_strchr(p_cpo, CPO_BUFOPT) != NULL))) {
should_copy = false;
}
if (should_copy || (flags & BCO_ALWAYS)) {
CLEAR_FIELD(buf->b_p_script_ctx);
// Don't copy the options specific to a help buffer when
// BCO_NOHELP is given or the options were initialized already
// (jumping back to a help file with CTRL-T or CTRL-O)
bool dont_do_help = ((flags & BCO_NOHELP) && buf->b_help) || buf->b_p_initialized;
if (dont_do_help) { // don't free b_p_isk
save_p_isk = buf->b_p_isk;
buf->b_p_isk = NULL;
}
// Always free the allocated strings. If not already initialized,
// reset 'readonly' and copy 'fileformat'.
if (!buf->b_p_initialized) {
free_buf_options(buf, true);
buf->b_p_ro = false; // don't copy readonly
buf->b_p_fenc = xstrdup(p_fenc);
switch (*p_ffs) {
case 'm':
buf->b_p_ff = xstrdup("mac");
break;
case 'd':
buf->b_p_ff = xstrdup("dos");
break;
case 'u':
buf->b_p_ff = xstrdup("unix");
break;
default:
buf->b_p_ff = xstrdup(p_ff);
break;
}
buf->b_p_bh = empty_string_option;
buf->b_p_bt = empty_string_option;
} else {
free_buf_options(buf, false);
}
buf->b_p_ai = p_ai;
COPY_OPT_SCTX(buf, kBufOptAutoindent);
buf->b_p_ai_nopaste = p_ai_nopaste;
buf->b_p_sw = p_sw;
COPY_OPT_SCTX(buf, kBufOptShiftwidth);
buf->b_p_scbk = p_scbk;
COPY_OPT_SCTX(buf, kBufOptScrollback);
buf->b_p_tw = p_tw;
COPY_OPT_SCTX(buf, kBufOptTextwidth);
buf->b_p_tw_nopaste = p_tw_nopaste;
buf->b_p_tw_nobin = p_tw_nobin;
buf->b_p_wm = p_wm;
COPY_OPT_SCTX(buf, kBufOptWrapmargin);
buf->b_p_wm_nopaste = p_wm_nopaste;
buf->b_p_wm_nobin = p_wm_nobin;
buf->b_p_bin = p_bin;
COPY_OPT_SCTX(buf, kBufOptBinary);
buf->b_p_bomb = p_bomb;
COPY_OPT_SCTX(buf, kBufOptBomb);
buf->b_p_et = p_et;
COPY_OPT_SCTX(buf, kBufOptExpandtab);
buf->b_p_fixeol = p_fixeol;
COPY_OPT_SCTX(buf, kBufOptFixendofline);
buf->b_p_et_nobin = p_et_nobin;
buf->b_p_et_nopaste = p_et_nopaste;
buf->b_p_ml = p_ml;
COPY_OPT_SCTX(buf, kBufOptModeline);
buf->b_p_ml_nobin = p_ml_nobin;
buf->b_p_inf = p_inf;
COPY_OPT_SCTX(buf, kBufOptInfercase);
if (cmdmod.cmod_flags & CMOD_NOSWAPFILE) {
buf->b_p_swf = false;
} else {
buf->b_p_swf = p_swf;
COPY_OPT_SCTX(buf, kBufOptSwapfile);
}
buf->b_p_cpt = xstrdup(p_cpt);
COPY_OPT_SCTX(buf, kBufOptComplete);
#ifdef BACKSLASH_IN_FILENAME
buf->b_p_csl = xstrdup(p_csl);
COPY_OPT_SCTX(buf, kBufOptCompleteslash);
#endif
buf->b_p_cfu = xstrdup(p_cfu);
COPY_OPT_SCTX(buf, kBufOptCompletefunc);
set_buflocal_cfu_callback(buf);
buf->b_p_ofu = xstrdup(p_ofu);
COPY_OPT_SCTX(buf, kBufOptOmnifunc);
set_buflocal_ofu_callback(buf);
buf->b_p_tfu = xstrdup(p_tfu);
COPY_OPT_SCTX(buf, kBufOptTagfunc);
set_buflocal_tfu_callback(buf);
buf->b_p_sts = p_sts;
COPY_OPT_SCTX(buf, kBufOptSofttabstop);
buf->b_p_sts_nopaste = p_sts_nopaste;
buf->b_p_vsts = xstrdup(p_vsts);
COPY_OPT_SCTX(buf, kBufOptVarsofttabstop);
if (p_vsts && p_vsts != empty_string_option) {
tabstop_set(p_vsts, &buf->b_p_vsts_array);
} else {
buf->b_p_vsts_array = NULL;
}
buf->b_p_vsts_nopaste = p_vsts_nopaste ? xstrdup(p_vsts_nopaste) : NULL;
buf->b_p_com = xstrdup(p_com);
COPY_OPT_SCTX(buf, kBufOptComments);
buf->b_p_cms = xstrdup(p_cms);
COPY_OPT_SCTX(buf, kBufOptCommentstring);
buf->b_p_fo = xstrdup(p_fo);
COPY_OPT_SCTX(buf, kBufOptFormatoptions);
buf->b_p_flp = xstrdup(p_flp);
COPY_OPT_SCTX(buf, kBufOptFormatlistpat);
buf->b_p_nf = xstrdup(p_nf);
COPY_OPT_SCTX(buf, kBufOptNrformats);
buf->b_p_mps = xstrdup(p_mps);
COPY_OPT_SCTX(buf, kBufOptMatchpairs);
buf->b_p_si = p_si;
COPY_OPT_SCTX(buf, kBufOptSmartindent);
buf->b_p_channel = 0;
buf->b_p_ci = p_ci;
COPY_OPT_SCTX(buf, kBufOptCopyindent);
buf->b_p_cin = p_cin;
COPY_OPT_SCTX(buf, kBufOptCindent);
buf->b_p_cink = xstrdup(p_cink);
COPY_OPT_SCTX(buf, kBufOptCinkeys);
buf->b_p_cino = xstrdup(p_cino);
COPY_OPT_SCTX(buf, kBufOptCinoptions);
buf->b_p_cinsd = xstrdup(p_cinsd);
COPY_OPT_SCTX(buf, kBufOptCinscopedecls);
buf->b_p_lop = xstrdup(p_lop);
COPY_OPT_SCTX(buf, kBufOptLispoptions);
// Don't copy 'filetype', it must be detected
buf->b_p_ft = empty_string_option;
buf->b_p_pi = p_pi;
COPY_OPT_SCTX(buf, kBufOptPreserveindent);
buf->b_p_cinw = xstrdup(p_cinw);
COPY_OPT_SCTX(buf, kBufOptCinwords);
buf->b_p_lisp = p_lisp;
COPY_OPT_SCTX(buf, kBufOptLisp);
// Don't copy 'syntax', it must be set
buf->b_p_syn = empty_string_option;
buf->b_p_smc = p_smc;
COPY_OPT_SCTX(buf, kBufOptSynmaxcol);
buf->b_s.b_syn_isk = empty_string_option;
buf->b_s.b_p_spc = xstrdup(p_spc);
COPY_OPT_SCTX(buf, kBufOptSpellcapcheck);
compile_cap_prog(&buf->b_s);
buf->b_s.b_p_spf = xstrdup(p_spf);
COPY_OPT_SCTX(buf, kBufOptSpellfile);
buf->b_s.b_p_spl = xstrdup(p_spl);
COPY_OPT_SCTX(buf, kBufOptSpelllang);
buf->b_s.b_p_spo = xstrdup(p_spo);
COPY_OPT_SCTX(buf, kBufOptSpelloptions);
buf->b_s.b_p_spo_flags = spo_flags;
buf->b_p_inde = xstrdup(p_inde);
COPY_OPT_SCTX(buf, kBufOptIndentexpr);
buf->b_p_indk = xstrdup(p_indk);
COPY_OPT_SCTX(buf, kBufOptIndentkeys);
buf->b_p_fp = empty_string_option;
buf->b_p_fex = xstrdup(p_fex);
COPY_OPT_SCTX(buf, kBufOptFormatexpr);
buf->b_p_sua = xstrdup(p_sua);
COPY_OPT_SCTX(buf, kBufOptSuffixesadd);
buf->b_p_keymap = xstrdup(p_keymap);
COPY_OPT_SCTX(buf, kBufOptKeymap);
buf->b_kmap_state |= KEYMAP_INIT;
// This isn't really an option, but copying the langmap and IME
// state from the current buffer is better than resetting it.
buf->b_p_iminsert = p_iminsert;
COPY_OPT_SCTX(buf, kBufOptIminsert);
buf->b_p_imsearch = p_imsearch;
COPY_OPT_SCTX(buf, kBufOptImsearch);
// options that are normally global but also have a local value
// are not copied, start using the global value
buf->b_p_ar = -1;
buf->b_p_ul = NO_LOCAL_UNDOLEVEL;
buf->b_p_bkc = empty_string_option;
buf->b_bkc_flags = 0;
buf->b_p_gp = empty_string_option;
buf->b_p_mp = empty_string_option;
buf->b_p_efm = empty_string_option;
buf->b_p_ep = empty_string_option;
buf->b_p_ffu = empty_string_option;
buf->b_p_kp = empty_string_option;
buf->b_p_path = empty_string_option;
buf->b_p_tags = empty_string_option;
buf->b_p_tc = empty_string_option;
buf->b_tc_flags = 0;
buf->b_p_def = empty_string_option;
buf->b_p_inc = empty_string_option;
buf->b_p_inex = xstrdup(p_inex);
COPY_OPT_SCTX(buf, kBufOptIncludeexpr);
buf->b_p_cot = empty_string_option;
buf->b_cot_flags = 0;
buf->b_p_dict = empty_string_option;
buf->b_p_tsr = empty_string_option;
buf->b_p_tsrfu = empty_string_option;
buf->b_p_qe = xstrdup(p_qe);
COPY_OPT_SCTX(buf, kBufOptQuoteescape);
buf->b_p_udf = p_udf;
COPY_OPT_SCTX(buf, kBufOptUndofile);
buf->b_p_lw = empty_string_option;
buf->b_p_menc = empty_string_option;
// Don't copy the options set by ex_help(), use the saved values,
// when going from a help buffer to a non-help buffer.
// Don't touch these at all when BCO_NOHELP is used and going from
// or to a help buffer.
if (dont_do_help) {
buf->b_p_isk = save_p_isk;
if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) {
tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
}
} else {
buf->b_p_isk = xstrdup(p_isk);
COPY_OPT_SCTX(buf, kBufOptIskeyword);
did_isk = true;
buf->b_p_ts = p_ts;
COPY_OPT_SCTX(buf, kBufOptTabstop);
buf->b_p_vts = xstrdup(p_vts);
COPY_OPT_SCTX(buf, kBufOptVartabstop);
if (p_vts && p_vts != empty_string_option && !buf->b_p_vts_array) {
tabstop_set(p_vts, &buf->b_p_vts_array);
} else {
buf->b_p_vts_array = NULL;
}
buf->b_help = false;
if (buf->b_p_bt[0] == 'h') {
clear_string_option(&buf->b_p_bt);
}
buf->b_p_ma = p_ma;
COPY_OPT_SCTX(buf, kBufOptModifiable);
}
}
// When the options should be copied (ignoring BCO_ALWAYS), set the
// flag that indicates that the options have been initialized.
if (should_copy) {
buf->b_p_initialized = true;
}
}
check_buf_options(buf); // make sure we don't have NULLs
if (did_isk) {
buf_init_chartab(buf, false);
}
}
/// Reset the 'modifiable' option and its default value.
void reset_modifiable(void)
{
curbuf->b_p_ma = false;
p_ma = false;
change_option_default(kOptModifiable, BOOLEAN_OPTVAL(false));
}
/// Set the global value for 'iminsert' to the local value.
void set_iminsert_global(buf_T *buf)
{
p_iminsert = buf->b_p_iminsert;
}
/// Set the global value for 'imsearch' to the local value.
void set_imsearch_global(buf_T *buf)
{
p_imsearch = buf->b_p_imsearch;
}
static OptIndex expand_option_idx = kOptInvalid;
static int expand_option_start_col = 0;
static char expand_option_name[5] = { 't', '_', NUL, NUL, NUL };
static int expand_option_flags = 0;
static bool expand_option_append = false;
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
{
expand_option_flags = opt_flags;
xp->xp_context = EXPAND_SETTINGS;
if (*arg == NUL) {
xp->xp_pattern = arg;
return;
}
char *const argend = arg + strlen(arg);
char *p = argend - 1;
if (*p == ' ' && *(p - 1) != '\\') {
xp->xp_pattern = p + 1;
return;
}
while (p > arg) {
char *s = p;
// count number of backslashes before ' ' or ','
if (*p == ' ' || *p == ',') {
while (s > arg && *(s - 1) == '\\') {
s--;
}
}
// break at a space with an even number of backslashes
if (*p == ' ' && ((p - s) & 1) == 0) {
p++;
break;
}
p--;
}
if (strncmp(p, "no", 2) == 0) {
xp->xp_context = EXPAND_BOOL_SETTINGS;
xp->xp_prefix = XP_PREFIX_NO;
p += 2;
} else if (strncmp(p, "inv", 3) == 0) {
xp->xp_context = EXPAND_BOOL_SETTINGS;
xp->xp_prefix = XP_PREFIX_INV;
p += 3;
}
xp->xp_pattern = p;
arg = p;
char nextchar;
uint32_t flags = 0;
OptIndex opt_idx = 0;
bool is_term_option = false;
if (*arg == '<') {
while (*p != '>') {
if (*p++ == NUL) { // expand terminal option name
return;
}
}
int key = get_special_key_code(arg + 1);
if (key == 0) { // unknown name
xp->xp_context = EXPAND_NOTHING;
return;
}
nextchar = *++p;
is_term_option = true;
expand_option_name[2] = (char)(uint8_t)KEY2TERMCAP0(key);
expand_option_name[3] = (char)(uint8_t)KEY2TERMCAP1(key);
} else {
if (p[0] == 't' && p[1] == '_') {
p += 2;
if (*p != NUL) {
p++;
}
if (*p == NUL) {
return; // expand option name
}
nextchar = *++p;
is_term_option = true;
expand_option_name[2] = p[-2];
expand_option_name[3] = p[-1];
} else {
// Allow * wildcard.
while (ASCII_ISALNUM(*p) || *p == '_' || *p == '*') {
p++;
}
if (*p == NUL) {
return;
}
nextchar = *p;
opt_idx = find_option_len(arg, (size_t)(p - arg));
if (opt_idx == kOptInvalid || is_option_hidden(opt_idx)) {
xp->xp_context = EXPAND_NOTHING;
return;
}
flags = options[opt_idx].flags;
if (option_has_type(opt_idx, kOptValTypeBoolean)) {
xp->xp_context = EXPAND_NOTHING;
return;
}
}
}
// handle "-=" and "+="
expand_option_append = false;
bool expand_option_subtract = false;
if ((nextchar == '-' || nextchar == '+' || nextchar == '^') && p[1] == '=') {
if (nextchar == '-') {
expand_option_subtract = true;
}
if (nextchar == '+' || nextchar == '^') {
expand_option_append = true;
}
p++;
nextchar = '=';
}
if ((nextchar != '=' && nextchar != ':')
|| xp->xp_context == EXPAND_BOOL_SETTINGS) {
xp->xp_context = EXPAND_UNSUCCESSFUL;
return;
}
// Below are for handling expanding a specific option's value after the '=' or ':'
if (is_term_option) {
expand_option_idx = kOptInvalid;
} else {
expand_option_idx = opt_idx;
}
xp->xp_pattern = p + 1;
expand_option_start_col = (int)(p + 1 - xp->xp_line);
// Certain options currently have special case handling to reuse the
// expansion logic with other commands.
if (options[opt_idx].var == &p_syn) {
xp->xp_context = EXPAND_OWNSYNTAX;
return;
}
if (options[opt_idx].var == &p_ft) {
xp->xp_context = EXPAND_FILETYPE;
return;
}
if (options[opt_idx].var == &p_keymap) {
xp->xp_context = EXPAND_KEYMAP;
return;
}
// Now pick. If the option has a custom expander, use that. Otherwise, just
// fill with the existing option value.
if (expand_option_subtract) {
xp->xp_context = EXPAND_SETTING_SUBTRACT;
return;
} else if (expand_option_idx != kOptInvalid && options[expand_option_idx].opt_expand_cb != NULL) {
xp->xp_context = EXPAND_STRING_SETTING;
} else if (*xp->xp_pattern == NUL) {
xp->xp_context = EXPAND_OLD_SETTING;
return;
} else {
xp->xp_context = EXPAND_NOTHING;
}
if (is_term_option || option_has_type(opt_idx, kOptValTypeNumber)) {
return;
}
// Only string options below
// Options that have kOptFlagExpand are considered to all use file/dir expansion.
if (flags & kOptFlagExpand) {
p = options[opt_idx].var;
if (p == (char *)&p_bdir
|| p == (char *)&p_dir
|| p == (char *)&p_path
|| p == (char *)&p_pp
|| p == (char *)&p_rtp
|| p == (char *)&p_cdpath
|| p == (char *)&p_vdir) {
xp->xp_context = EXPAND_DIRECTORIES;
if (p == (char *)&p_path || p == (char *)&p_cdpath) {
xp->xp_backslash = XP_BS_THREE;
} else {
xp->xp_backslash = XP_BS_ONE;
}
} else {
xp->xp_context = EXPAND_FILES;
// for 'tags' need three backslashes for a space
if (p == (char *)&p_tags) {
xp->xp_backslash = XP_BS_THREE;
} else {
xp->xp_backslash = XP_BS_ONE;
}
}
if (flags & kOptFlagComma) {
xp->xp_backslash |= XP_BS_COMMA;
}
}
// For an option that is a list of file names, or comma/colon-separated
// values, split it by the delimiter and find the start of the current
// pattern, while accounting for backslash-escaped space/commas/colons.
// Triple-backslashed escaped file names (e.g. 'path') can also be
// delimited by space.
if ((flags & kOptFlagExpand) || (flags & kOptFlagComma) || (flags & kOptFlagColon)) {
for (p = argend - 1; p > xp->xp_pattern; p--) {
// count number of backslashes before ' ' or ','
if (*p == ' ' || *p == ',' || (*p == ':' && (flags & kOptFlagColon))) {
char *s = p;
while (s > xp->xp_pattern && *(s - 1) == '\\') {
s--;
}
if ((*p == ' ' && ((xp->xp_backslash & XP_BS_THREE) && (p - s) < 3))
#if defined(BACKSLASH_IN_FILENAME)
|| (*p == ',' && (flags & kOptFlagComma) && (p - s) < 1)
#else
|| (*p == ',' && (flags & kOptFlagComma) && (p - s) < 2)
#endif
|| (*p == ':' && (flags & kOptFlagColon))) {
xp->xp_pattern = p + 1;
break;
}
}
}
}
// An option that is a list of single-character flags should always start
// at the end as we don't complete words.
if (flags & kOptFlagFlagList) {
xp->xp_pattern = argend;
}
// Some options can either be using file/dir expansions, or custom value
// expansion depending on what the user typed. Unfortunately we have to
// manually handle it here to make sure we have the correct xp_context set.
// for 'spellsuggest' start at "file:"
if (options[opt_idx].var == &p_sps) {
if (strncmp(xp->xp_pattern, "file:", 5) == 0) {
xp->xp_pattern += 5;
return;
} else if (options[expand_option_idx].opt_expand_cb != NULL) {
xp->xp_context = EXPAND_STRING_SETTING;
}
}
}
/// Returns true if "str" either matches "regmatch" or fuzzy matches "pat".
///
/// If "test_only" is true and "fuzzy" is false and if "str" matches the regular
/// expression "regmatch", then returns true. Otherwise returns false.
///
/// If "test_only" is false and "fuzzy" is false and if "str" matches the
/// regular expression "regmatch", then stores the match in matches[idx] and
/// returns true.
///
/// If "test_only" is true and "fuzzy" is true and if "str" fuzzy matches
/// "fuzzystr", then returns true. Otherwise returns false.
///
/// If "test_only" is false and "fuzzy" is true and if "str" fuzzy matches
/// "fuzzystr", then stores the match details in fuzmatch[idx] and returns true.
static bool match_str(char *const str, regmatch_T *const regmatch, char **const matches,
const int idx, const bool test_only, const bool fuzzy,
const char *const fuzzystr, fuzmatch_str_T *const fuzmatch)
{
if (!fuzzy) {
if (vim_regexec(regmatch, str, 0)) {
if (!test_only) {
matches[idx] = xstrdup(str);
}
return true;
}
} else {
const int score = fuzzy_match_str(str, fuzzystr);
if (score != 0) {
if (!test_only) {
fuzmatch[idx].idx = idx;
fuzmatch[idx].str = xstrdup(str);
fuzmatch[idx].score = score;
}
return true;
}
}
return false;
}
int ExpandSettings(expand_T *xp, regmatch_T *regmatch, char *fuzzystr, int *numMatches,
char ***matches, const bool can_fuzzy)
{
int num_normal = 0; // Nr of matching non-term-code settings
int count = 0;
static char *(names[]) = { "all" };
int ic = regmatch->rm_ic; // remember the ignore-case flag
fuzmatch_str_T *fuzmatch = NULL;
const bool fuzzy = can_fuzzy && cmdline_fuzzy_complete(fuzzystr);
// do this loop twice:
// loop == 0: count the number of matching options
// loop == 1: copy the matching options into allocated memory
for (int loop = 0; loop <= 1; loop++) {
regmatch->rm_ic = ic;
if (xp->xp_context != EXPAND_BOOL_SETTINGS) {
for (int match = 0; match < (int)ARRAY_SIZE(names);
match++) {
if (match_str(names[match], regmatch, *matches,
count, (loop == 0), fuzzy, fuzzystr, fuzmatch)) {
if (loop == 0) {
num_normal++;
} else {
count++;
}
}
}
}
char *str;
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
str = options[opt_idx].fullname;
if (is_option_hidden(opt_idx)) {
continue;
}
if (xp->xp_context == EXPAND_BOOL_SETTINGS
&& !(option_has_type(opt_idx, kOptValTypeBoolean))) {
continue;
}
if (match_str(str, regmatch, *matches, count, (loop == 0),
fuzzy, fuzzystr, fuzmatch)) {
if (loop == 0) {
num_normal++;
} else {
count++;
}
} else if (!fuzzy && options[opt_idx].shortname != NULL
&& vim_regexec(regmatch, options[opt_idx].shortname, 0)) {
// Compare against the abbreviated option name (for regular
// expression match). Fuzzy matching (previous if) already
// matches against both the expanded and abbreviated names.
if (loop == 0) {
num_normal++;
} else {
(*matches)[count++] = xstrdup(str);
}
}
}
if (loop == 0) {
if (num_normal > 0) {
*numMatches = num_normal;
} else {
return OK;
}
if (!fuzzy) {
*matches = xmalloc((size_t)(*numMatches) * sizeof(char *));
} else {
fuzmatch = xmalloc((size_t)(*numMatches) * sizeof(fuzmatch_str_T));
}
}
}
if (fuzzy) {
fuzzymatches_to_strmatches(fuzmatch, matches, count, false);
}
return OK;
}
/// Escape an option value that can be used on the command-line with :set.
/// Caller needs to free the returned string, unless NULL is returned.
static char *escape_option_str_cmdline(char *var)
{
// A backslash is required before some characters. This is the reverse of
// what happens in do_set().
char *buf = vim_strsave_escaped(var, escape_chars);
#ifdef BACKSLASH_IN_FILENAME
// For MS-Windows et al. we don't double backslashes at the start and
// before a file name character.
// The reverse is found at stropt_copy_value().
for (var = buf; *var != NUL; MB_PTR_ADV(var)) {
if (var[0] == '\\' && var[1] == '\\'
&& expand_option_idx != kOptInvalid
&& (options[expand_option_idx].flags & kOptFlagExpand)
&& vim_isfilec((uint8_t)var[2])
&& (var[2] != '\\' || (var == buf && var[4] != '\\'))) {
STRMOVE(var, var + 1);
}
}
#endif
return buf;
}
/// Expansion handler for :set= when we just want to fill in with the existing value.
int ExpandOldSetting(int *numMatches, char ***matches)
{
char *var = NULL;
*numMatches = 0;
*matches = xmalloc(sizeof(char *));
// For a terminal key code expand_option_idx is kOptInvalid.
if (expand_option_idx == kOptInvalid) {
expand_option_idx = find_option(expand_option_name);
}
if (expand_option_idx != kOptInvalid) {
// Put string of option value in NameBuff.
option_value2string(&options[expand_option_idx], expand_option_flags);
var = NameBuff;
} else {
var = "";
}
char *buf = escape_option_str_cmdline(var);
(*matches)[0] = buf;
*numMatches = 1;
return OK;
}
/// Expansion handler for :set=/:set+= when the option has a custom expansion handler.
int ExpandStringSetting(expand_T *xp, regmatch_T *regmatch, int *numMatches, char ***matches)
{
if (expand_option_idx == kOptInvalid || options[expand_option_idx].opt_expand_cb == NULL) {
// Not supposed to reach this. This function is only for options with
// custom expansion callbacks.
return FAIL;
}
optexpand_T args = {
.oe_varp = get_varp_scope(&options[expand_option_idx], expand_option_flags),
.oe_idx = expand_option_idx,
.oe_append = expand_option_append,
.oe_regmatch = regmatch,
.oe_xp = xp,
.oe_set_arg = xp->xp_line + expand_option_start_col,
};
args.oe_include_orig_val = !expand_option_append && (*args.oe_set_arg == NUL);
// Retrieve the existing value, but escape it as a reverse of setting it.
// We technically only need to do this when oe_append or
// oe_include_orig_val is true.
option_value2string(&options[expand_option_idx], expand_option_flags);
char *var = NameBuff;
char *buf = escape_option_str_cmdline(var);
args.oe_opt_value = buf;
int num_ret = options[expand_option_idx].opt_expand_cb(&args, numMatches, matches);
xfree(buf);
return num_ret;
}
/// Expansion handler for :set-=
int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, char ***matches)
{
if (expand_option_idx == kOptInvalid) {
// term option
return ExpandOldSetting(numMatches, matches);
}
char *option_val = *(char **)get_option_varp_scope_from(expand_option_idx,
expand_option_flags,
curbuf, curwin);
uint32_t option_flags = options[expand_option_idx].flags;
if (option_has_type(expand_option_idx, kOptValTypeNumber)) {
return ExpandOldSetting(numMatches, matches);
} else if (option_flags & kOptFlagComma) {
// Split the option by comma, then present each option to the user if
// it matches the pattern.
// This condition needs to go first, because 'whichwrap' has both
// kOptFlagComma and kOptFlagFlagList.
if (*option_val == NUL) {
return FAIL;
}
// Make a copy as we need to inject null characters destructively.
char *option_copy = xstrdup(option_val);
char *next_val = option_copy;
garray_T ga;
ga_init(&ga, sizeof(char *), 10);
do {
char *item = next_val;
char *comma = vim_strchr(next_val, ',');
while (comma != NULL && comma != next_val && *(comma - 1) == '\\') {
// "\," is interpreted as a literal comma rather than option
// separator when reading options in copy_option_part(). Skip
// it.
comma = vim_strchr(comma + 1, ',');
}
if (comma != NULL) {
*comma = NUL; // null-terminate this value, required by later functions
next_val = comma + 1;
} else {
next_val = NULL;
}
if (*item == NUL) {
// empty value, don't add to list
continue;
}
if (!vim_regexec(regmatch, item, 0)) {
continue;
}
char *buf = escape_option_str_cmdline(item);
GA_APPEND(char *, &ga, buf);
} while (next_val != NULL);
xfree(option_copy);
*matches = ga.ga_data;
*numMatches = ga.ga_len;
return OK;
} else if (option_flags & kOptFlagFlagList) {
// Only present the flags that are set on the option as the other flags
// are not meaningful to do set-= on.
if (*xp->xp_pattern != NUL) {
// Don't suggest anything if cmdline is non-empty. Vim's set-=
// behavior requires consecutive strings and it's usually
// unintuitive to users if they try to subtract multiple flags at
// once.
return FAIL;
}
size_t num_flags = strlen(option_val);
if (num_flags == 0) {
return FAIL;
}
*matches = xmalloc(sizeof(char *) * (num_flags + 1));
int count = 0;
(*matches)[count++] = xmemdupz(option_val, num_flags);
if (num_flags > 1) {
// If more than one flags, split the flags up and expose each
// character as individual choice.
for (char *flag = option_val; *flag != NUL; flag++) {
(*matches)[count++] = xmemdupz(flag, 1);
}
}
*numMatches = count;
return OK;
}
return ExpandOldSetting(numMatches, matches);
}
/// Get the value for the numeric or string option///opp in a nice format into
/// NameBuff[]. Must not be called with a hidden option!
///
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
///
/// TODO(famiu): Replace this with optval_to_cstr() if possible.
static void option_value2string(vimoption_T *opt, int opt_flags)
{
void *varp = get_varp_scope(opt, opt_flags);
assert(varp != NULL);
if (option_has_type(get_opt_idx(opt), kOptValTypeNumber)) {
OptInt wc = 0;
if (wc_use_keyname(varp, &wc)) {
xstrlcpy(NameBuff, get_special_key_name((int)wc, 0), sizeof(NameBuff));
} else if (wc != 0) {
xstrlcpy(NameBuff, transchar((int)wc), sizeof(NameBuff));
} else {
snprintf(NameBuff,
sizeof(NameBuff),
"%" PRId64,
(int64_t)(*(OptInt *)varp));
}
} else { // string
varp = *(char **)(varp);
if (opt->flags & kOptFlagExpand) {
home_replace(NULL, varp, NameBuff, MAXPATHL, false);
} else {
xstrlcpy(NameBuff, varp, MAXPATHL);
}
}
}
/// Return true if "varp" points to 'wildchar' or 'wildcharm' and it can be
/// printed as a keyname.
/// "*wcp" is set to the value of the option if it's 'wildchar' or 'wildcharm'.
static int wc_use_keyname(const void *varp, OptInt *wcp)
{
if (((OptInt *)varp == &p_wc) || ((OptInt *)varp == &p_wcm)) {
*wcp = *(OptInt *)varp;
if (IS_SPECIAL(*wcp) || find_special_key_in_table((int)(*wcp)) >= 0) {
return true;
}
}
return false;
}
/// @returns true if "x" is present in 'shortmess' option, or
/// 'shortmess' contains 'a' and "x" is present in SHM_ALL_ABBREVIATIONS.
bool shortmess(int x)
{
return (p_shm != NULL
&& (vim_strchr(p_shm, x) != NULL
|| (vim_strchr(p_shm, 'a') != NULL
&& vim_strchr(SHM_ALL_ABBREVIATIONS, x) != NULL)));
}
/// vimrc_found() - Called when a vimrc or "VIMINIT" has been found.
///
/// Set the values for options that didn't get set yet to the defaults.
/// When "fname" is not NULL, use it to set $"envname" when it wasn't set yet.
void vimrc_found(char *fname, char *envname)
{
if (fname != NULL && envname != NULL) {
char *p = vim_getenv(envname);
if (p == NULL) {
// Set $MYVIMRC to the first vimrc file found.
p = FullName_save(fname, false);
if (p != NULL) {
os_setenv(envname, p, 1);
xfree(p);
}
} else {
xfree(p);
}
}
}
/// Check whether global option has been set.
///
/// @param[in] name Option name.
///
/// @return True if option was set.
bool option_was_set(OptIndex opt_idx)
{
assert(opt_idx != kOptInvalid);
return options[opt_idx].flags & kOptFlagWasSet;
}
/// Reset the flag indicating option "name" was set.
///
/// @param[in] name Option name.
void reset_option_was_set(OptIndex opt_idx)
{
assert(opt_idx != kOptInvalid);
options[opt_idx].flags &= ~(unsigned)kOptFlagWasSet;
}
/// fill_culopt_flags() -- called when 'culopt' changes value
int fill_culopt_flags(char *val, win_T *wp)
{
char *p;
uint8_t culopt_flags_new = 0;
if (val == NULL) {
p = wp->w_p_culopt;
} else {
p = val;
}
while (*p != NUL) {
// Note: Keep this in sync with opt_culopt_values.
if (strncmp(p, "line", 4) == 0) {
p += 4;
culopt_flags_new |= kOptCuloptFlagLine;
} else if (strncmp(p, "both", 4) == 0) {
p += 4;
culopt_flags_new |= kOptCuloptFlagLine | kOptCuloptFlagNumber;
} else if (strncmp(p, "number", 6) == 0) {
p += 6;
culopt_flags_new |= kOptCuloptFlagNumber;
} else if (strncmp(p, "screenline", 10) == 0) {
p += 10;
culopt_flags_new |= kOptCuloptFlagScreenline;
}
if (*p != ',' && *p != NUL) {
return FAIL;
}
if (*p == ',') {
p++;
}
}
// Can't have both "line" and "screenline".
if ((culopt_flags_new & kOptCuloptFlagLine) && (culopt_flags_new & kOptCuloptFlagScreenline)) {
return FAIL;
}
wp->w_p_culopt_flags = culopt_flags_new;
return OK;
}
/// Get the value of 'magic' taking "magic_overruled" into account.
bool magic_isset(void)
{
switch (magic_overruled) {
case OPTION_MAGIC_ON:
return true;
case OPTION_MAGIC_OFF:
return false;
case OPTION_MAGIC_NOT_SET:
break;
}
return p_magic;
}
/// Set the callback function value for an option that accepts a function name,
/// lambda, et al. (e.g. 'operatorfunc', 'tagfunc', etc.)
/// @return OK if the option is successfully set to a function, otherwise FAIL
int option_set_callback_func(char *optval, Callback *optcb)
{
if (optval == NULL || *optval == NUL) {
callback_free(optcb);
return OK;
}
typval_T *tv;
if (*optval == '{'
|| (strncmp(optval, "function(", 9) == 0)
|| (strncmp(optval, "funcref(", 8) == 0)) {
// Lambda expression or a funcref
tv = eval_expr(optval, NULL);
if (tv == NULL) {
return FAIL;
}
} else {
// treat everything else as a function name string
tv = xcalloc(1, sizeof(*tv));
tv->v_type = VAR_STRING;
tv->vval.v_string = xstrdup(optval);
}
Callback cb;
if (!callback_from_typval(&cb, tv) || cb.type == kCallbackNone) {
tv_free(tv);
return FAIL;
}
callback_free(optcb);
*optcb = cb;
tv_free(tv);
return OK;
}
static void didset_options_sctx(int opt_flags, int *buf)
{
for (int i = 0;; i++) {
if (buf[i] == kOptInvalid) {
break;
}
set_option_sctx(buf[i], opt_flags, current_sctx);
}
}
/// Check if backspacing over something is allowed.
/// @param what BS_INDENT, BS_EOL, BS_START, or BS_NOSTOP
bool can_bs(int what)
{
if (what == BS_START && bt_prompt(curbuf)) {
return false;
}
// support for number values was removed but we keep '2' since it is used in
// legacy tests
if (*p_bs == '2') {
return what != BS_NOSTOP;
}
return vim_strchr(p_bs, what) != NULL;
}
/// Get the local or global value of 'backupcopy' flags.
///
/// @param buf The buffer.
unsigned get_bkc_flags(buf_T *buf)
{
return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags;
}
/// Get the local or global value of 'formatlistpat'.
///
/// @param buf The buffer.
char *get_flp_value(buf_T *buf)
{
if (buf->b_p_flp == NULL || *buf->b_p_flp == NUL) {
return p_flp;
}
return buf->b_p_flp;
}
/// Get the local or global value of 'virtualedit' flags.
unsigned get_ve_flags(win_T *wp)
{
return (wp->w_ve_flags ? wp->w_ve_flags : ve_flags)
& ~(unsigned)(kOptVeFlagNone | kOptVeFlagNoneU);
}
/// Get the local or global value of 'showbreak'.
///
/// @param win If not NULL, the window to get the local option from; global
/// otherwise.
char *get_showbreak_value(win_T *const win)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (win->w_p_sbr == NULL || *win->w_p_sbr == NUL) {
return p_sbr;
}
if (strcmp(win->w_p_sbr, "NONE") == 0) {
return empty_string_option;
}
return win->w_p_sbr;
}
/// Return the current end-of-line type: EOL_DOS, EOL_UNIX or EOL_MAC.
int get_fileformat(const buf_T *buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
int c = (unsigned char)(*buf->b_p_ff);
if (buf->b_p_bin || c == 'u') {
return EOL_UNIX;
}
if (c == 'm') {
return EOL_MAC;
}
return EOL_DOS;
}
/// Like get_fileformat(), but override 'fileformat' with "p" for "++opt=val"
/// argument.
///
/// @param eap can be NULL!
int get_fileformat_force(const buf_T *buf, const exarg_T *eap)
FUNC_ATTR_NONNULL_ARG(1)
{
int c;
if (eap != NULL && eap->force_ff != 0) {
c = eap->force_ff;
} else {
if ((eap != NULL && eap->force_bin != 0)
? (eap->force_bin == FORCE_BIN) : buf->b_p_bin) {
return EOL_UNIX;
}
c = (unsigned char)(*buf->b_p_ff);
}
if (c == 'u') {
return EOL_UNIX;
}
if (c == 'm') {
return EOL_MAC;
}
return EOL_DOS;
}
/// Return the default fileformat from 'fileformats'.
int default_fileformat(void)
{
switch (*p_ffs) {
case 'm':
return EOL_MAC;
case 'd':
return EOL_DOS;
}
return EOL_UNIX;
}
/// Set the current end-of-line type to EOL_UNIX, EOL_MAC, or EOL_DOS.
///
/// Sets 'fileformat'.
///
/// @param eol_style End-of-line style.
/// @param opt_flags Option flags (can be OPT_LOCAL, OPT_GLOBAL or a combination).
void set_fileformat(int eol_style, int opt_flags)
{
char *p = NULL;
switch (eol_style) {
case EOL_UNIX:
p = "unix";
break;
case EOL_MAC:
p = "mac";
break;
case EOL_DOS:
p = "dos";
break;
}
// p is NULL if "eol_style" is EOL_UNKNOWN.
if (p != NULL) {
set_option_direct(kOptFileformat, CSTR_AS_OPTVAL(p), opt_flags, 0);
}
// This may cause the buffer to become (un)modified.
redraw_buf_status_later(curbuf);
redraw_tabline = true;
need_maketitle = true; // Set window title later.
}
/// Skip to next part of an option argument: skip space and comma
char *skip_to_option_part(const char *p)
{
if (*p == ',') {
p++;
}
while (*p == ' ') {
p++;
}
return (char *)p;
}
/// Isolate one part of a string option separated by `sep_chars`.
///
/// @param[in,out] option advanced to the next part
/// @param[in,out] buf copy of the isolated part
/// @param[in] maxlen length of `buf`
/// @param[in] sep_chars chars that separate the option parts
///
/// @return length of `*option`
size_t copy_option_part(char **option, char *buf, size_t maxlen, char *sep_chars)
{
size_t len = 0;
char *p = *option;
// skip '.' at start of option part, for 'suffixes'
if (*p == '.') {
buf[len++] = *p++;
}
while (*p != NUL && vim_strchr(sep_chars, (uint8_t)(*p)) == NULL) {
// Skip backslash before a separator character and space.
if (p[0] == '\\' && vim_strchr(sep_chars, (uint8_t)p[1]) != NULL) {
p++;
}
if (len < maxlen - 1) {
buf[len++] = *p;
}
p++;
}
buf[len] = NUL;
if (*p != NUL && *p != ',') { // skip non-standard separator
p++;
}
p = skip_to_option_part(p); // p points to next file name
*option = p;
return len;
}
/// Return true when 'shell' has "csh" in the tail.
int csh_like_shell(void)
{
return strstr(path_tail(p_sh), "csh") != NULL;
}
/// Return true when 'shell' has "fish" in the tail.
bool fish_like_shell(void)
{
return strstr(path_tail(p_sh), "fish") != NULL;
}
/// Get window or buffer local options
dict_T *get_winbuf_options(const int bufopt)
FUNC_ATTR_WARN_UNUSED_RESULT
{
dict_T *const d = tv_dict_alloc();
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
vimoption_T *opt = &options[opt_idx];
if ((bufopt && (option_has_scope(opt_idx, kOptScopeBuf)))
|| (!bufopt && (option_has_scope(opt_idx, kOptScopeWin)))) {
void *varp = get_varp(opt);
if (varp != NULL) {
typval_T opt_tv = optval_as_tv(optval_from_varp(opt_idx, varp), true);
tv_dict_add_tv(d, opt->fullname, strlen(opt->fullname), &opt_tv);
}
}
}
return d;
}
/// Return the effective 'scrolloff' value for the current window, using the
/// global value when appropriate.
int get_scrolloff_value(win_T *wp)
{
// Disallow scrolloff in terminal-mode. #11915
if (State & MODE_TERMINAL) {
return 0;
}
return (int)(wp->w_p_so < 0 ? p_so : wp->w_p_so);
}
/// Return the effective 'sidescrolloff' value for the current window, using the
/// global value when appropriate.
int get_sidescrolloff_value(win_T *wp)
{
return (int)(wp->w_p_siso < 0 ? p_siso : wp->w_p_siso);
}
Dict get_vimoption(String name, int opt_flags, buf_T *buf, win_T *win, Arena *arena, Error *err)
{
OptIndex opt_idx = find_option_len(name.data, name.size);
VALIDATE_S(opt_idx != kOptInvalid, "option (not found)", name.data, {
return (Dict)ARRAY_DICT_INIT;
});
return vimoption2dict(&options[opt_idx], opt_flags, buf, win, arena);
}
Dict get_all_vimoptions(Arena *arena)
{
Dict retval = arena_dict(arena, kOptCount);
for (OptIndex opt_idx = 0; opt_idx < kOptCount; opt_idx++) {
Dict opt_dict = vimoption2dict(&options[opt_idx], OPT_GLOBAL, curbuf, curwin, arena);
PUT_C(retval, options[opt_idx].fullname, DICT_OBJ(opt_dict));
}
return retval;
}
static Dict vimoption2dict(vimoption_T *opt, int opt_flags, buf_T *buf, win_T *win, Arena *arena)
{
OptIndex opt_idx = get_opt_idx(opt);
Dict dict = arena_dict(arena, 13);
PUT_C(dict, "name", CSTR_AS_OBJ(opt->fullname));
PUT_C(dict, "shortname", CSTR_AS_OBJ(opt->shortname));
const char *scope;
if (option_has_scope(opt_idx, kOptScopeBuf)) {
scope = "buf";
} else if (option_has_scope(opt_idx, kOptScopeWin)) {
scope = "win";
} else {
scope = "global";
}
PUT_C(dict, "scope", CSTR_AS_OBJ(scope));
// welcome to the jungle
PUT_C(dict, "global_local", BOOLEAN_OBJ(option_is_global_local(opt_idx)));
PUT_C(dict, "commalist", BOOLEAN_OBJ(opt->flags & kOptFlagComma));
PUT_C(dict, "flaglist", BOOLEAN_OBJ(opt->flags & kOptFlagFlagList));
PUT_C(dict, "was_set", BOOLEAN_OBJ(opt->flags & kOptFlagWasSet));
LastSet last_set = { .channel_id = 0 };
if (opt_flags == OPT_GLOBAL) {
last_set = opt->last_set;
} else {
// Scope is either OPT_LOCAL or a fallback mode was requested.
if (option_has_scope(opt_idx, kOptScopeBuf)) {
last_set = buf->b_p_script_ctx[opt->scope_idx[kOptScopeBuf]];
}
if (option_has_scope(opt_idx, kOptScopeWin)) {
last_set = win->w_p_script_ctx[opt->scope_idx[kOptScopeWin]];
}
if (opt_flags != OPT_LOCAL && last_set.script_ctx.sc_sid == 0) {
last_set = opt->last_set;
}
}
PUT_C(dict, "last_set_sid", INTEGER_OBJ(last_set.script_ctx.sc_sid));
PUT_C(dict, "last_set_linenr", INTEGER_OBJ(last_set.script_ctx.sc_lnum));
PUT_C(dict, "last_set_chan", INTEGER_OBJ((int64_t)last_set.channel_id));
PUT_C(dict, "type", CSTR_AS_OBJ(optval_type_get_name(option_get_type(get_opt_idx(opt)))));
PUT_C(dict, "default", optval_as_object(opt->def_val));
PUT_C(dict, "allows_duplicates", BOOLEAN_OBJ(!(opt->flags & kOptFlagNoDup)));
return dict;
}