diff options
| -rw-r--r-- | runtime/doc/eval.txt | 21 | ||||
| -rw-r--r-- | src/nvim/eval.c | 205 | ||||
| -rw-r--r-- | src/nvim/eval.h | 3 | ||||
| -rw-r--r-- | src/nvim/option.c | 148 | ||||
| -rw-r--r-- | src/nvim/testdir/test_autocmd.vim | 522 | ||||
| -rw-r--r-- | test/functional/legacy/autocmd_option_spec.lua | 420 | 
6 files changed, 1131 insertions, 188 deletions
| diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 58d5d30146..89688b36a7 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -2042,10 +2042,29 @@ v:option_new    New value of the option. Valid while executing an |OptionSet|  		autocommand.  						    *v:option_old*  v:option_old    Old value of the option. Valid while executing an |OptionSet| -		autocommand. +		autocommand. Depending on the command used for setting and the +		kind of option this is either the local old value or the +		global old value. +						    *v:option_oldlocal* +v:option_oldlocal +		Old local value of the option. Valid while executing an +		|OptionSet| autocommand. +						    *v:option_oldglobal* +v:option_oldglobal +		Old global value of the option. Valid while executing an +		|OptionSet| autocommand.  						    *v:option_type*  v:option_type   Scope of the set command. Valid while executing an  		|OptionSet| autocommand. Can be either "global" or "local" +						    *v:option_command* +v:option_command +		Command used to set the option. Valid while executing an +		|OptionSet| autocommand. +			value		option was set via   ~ +			"setlocal"	|:setlocal| or ":let l:xxx" +			"setglobal"	|:setglobal| or ":let g:xxx" +			"set"		|:set| or |:let| +			"modeline"	|modeline|  					*v:operator* *operator-variable*  v:operator	The last operator given in Normal mode.  This is a single  		character except for commands starting with <g> or <z>, diff --git a/src/nvim/eval.c b/src/nvim/eval.c index d3d0121632..a09ad7924d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -134,13 +134,15 @@ typedef struct {      .vv_flags = flags, \    } +#define VIMVAR_KEY_LEN 16  // Maximum length of the key of v:variables +  // Array to hold the value of v: variables.  // The value is in a dictitem, so that it can also be used in the v: scope.  // The reason to use this table anyway is for very quick access to the  // variables with the VV_ defines.  static struct vimvar {    char *vv_name;  ///< Name of the variable, without v:. -  TV_DICTITEM_STRUCT(17) vv_di;  ///< Value and name for key (max 16 chars). +  TV_DICTITEM_STRUCT(VIMVAR_KEY_LEN + 1) vv_di;  ///< Value and name for key (max 16 chars).    char vv_flags;  ///< Flags: #VV_COMPAT, #VV_RO, #VV_RO_SBX.  } vimvars[] =  { @@ -150,100 +152,103 @@ static struct vimvar {    // VV_SEND_SERVER "servername"    // VV_REG "register"    // VV_OP "operator" -  VV(VV_COUNT,          "count",            VAR_NUMBER, VV_RO), -  VV(VV_COUNT1,         "count1",           VAR_NUMBER, VV_RO), -  VV(VV_PREVCOUNT,      "prevcount",        VAR_NUMBER, VV_RO), -  VV(VV_ERRMSG,         "errmsg",           VAR_STRING, 0), -  VV(VV_WARNINGMSG,     "warningmsg",       VAR_STRING, 0), -  VV(VV_STATUSMSG,      "statusmsg",        VAR_STRING, 0), -  VV(VV_SHELL_ERROR,    "shell_error",      VAR_NUMBER, VV_RO), -  VV(VV_THIS_SESSION,   "this_session",     VAR_STRING, 0), -  VV(VV_VERSION,        "version",          VAR_NUMBER, VV_COMPAT+VV_RO), -  VV(VV_LNUM,           "lnum",             VAR_NUMBER, VV_RO_SBX), -  VV(VV_TERMRESPONSE,   "termresponse",     VAR_STRING, VV_RO), -  VV(VV_FNAME,          "fname",            VAR_STRING, VV_RO), -  VV(VV_LANG,           "lang",             VAR_STRING, VV_RO), -  VV(VV_LC_TIME,        "lc_time",          VAR_STRING, VV_RO), -  VV(VV_CTYPE,          "ctype",            VAR_STRING, VV_RO), -  VV(VV_CC_FROM,        "charconvert_from", VAR_STRING, VV_RO), -  VV(VV_CC_TO,          "charconvert_to",   VAR_STRING, VV_RO), -  VV(VV_FNAME_IN,       "fname_in",         VAR_STRING, VV_RO), -  VV(VV_FNAME_OUT,      "fname_out",        VAR_STRING, VV_RO), -  VV(VV_FNAME_NEW,      "fname_new",        VAR_STRING, VV_RO), -  VV(VV_FNAME_DIFF,     "fname_diff",       VAR_STRING, VV_RO), -  VV(VV_CMDARG,         "cmdarg",           VAR_STRING, VV_RO), -  VV(VV_FOLDSTART,      "foldstart",        VAR_NUMBER, VV_RO_SBX), -  VV(VV_FOLDEND,        "foldend",          VAR_NUMBER, VV_RO_SBX), -  VV(VV_FOLDDASHES,     "folddashes",       VAR_STRING, VV_RO_SBX), -  VV(VV_FOLDLEVEL,      "foldlevel",        VAR_NUMBER, VV_RO_SBX), -  VV(VV_PROGNAME,       "progname",         VAR_STRING, VV_RO), -  VV(VV_SEND_SERVER,    "servername",       VAR_STRING, VV_RO), -  VV(VV_DYING,          "dying",            VAR_NUMBER, VV_RO), -  VV(VV_EXCEPTION,      "exception",        VAR_STRING, VV_RO), -  VV(VV_THROWPOINT,     "throwpoint",       VAR_STRING, VV_RO), -  VV(VV_REG,            "register",         VAR_STRING, VV_RO), -  VV(VV_CMDBANG,        "cmdbang",          VAR_NUMBER, VV_RO), -  VV(VV_INSERTMODE,     "insertmode",       VAR_STRING, VV_RO), -  VV(VV_VAL,            "val",              VAR_UNKNOWN, VV_RO), -  VV(VV_KEY,            "key",              VAR_UNKNOWN, VV_RO), -  VV(VV_PROFILING,      "profiling",        VAR_NUMBER, VV_RO), -  VV(VV_FCS_REASON,     "fcs_reason",       VAR_STRING, VV_RO), -  VV(VV_FCS_CHOICE,     "fcs_choice",       VAR_STRING, 0), -  VV(VV_BEVAL_BUFNR,    "beval_bufnr",      VAR_NUMBER, VV_RO), -  VV(VV_BEVAL_WINNR,    "beval_winnr",      VAR_NUMBER, VV_RO), -  VV(VV_BEVAL_WINID,    "beval_winid",      VAR_NUMBER, VV_RO), -  VV(VV_BEVAL_LNUM,     "beval_lnum",       VAR_NUMBER, VV_RO), -  VV(VV_BEVAL_COL,      "beval_col",        VAR_NUMBER, VV_RO), -  VV(VV_BEVAL_TEXT,     "beval_text",       VAR_STRING, VV_RO), -  VV(VV_SCROLLSTART,    "scrollstart",      VAR_STRING, 0), -  VV(VV_SWAPNAME,       "swapname",         VAR_STRING, VV_RO), -  VV(VV_SWAPCHOICE,     "swapchoice",       VAR_STRING, 0), -  VV(VV_SWAPCOMMAND,    "swapcommand",      VAR_STRING, VV_RO), -  VV(VV_CHAR,           "char",             VAR_STRING, 0), -  VV(VV_MOUSE_WIN,      "mouse_win",        VAR_NUMBER, 0), -  VV(VV_MOUSE_WINID,    "mouse_winid",      VAR_NUMBER, 0), -  VV(VV_MOUSE_LNUM,     "mouse_lnum",       VAR_NUMBER, 0), -  VV(VV_MOUSE_COL,      "mouse_col",        VAR_NUMBER, 0), -  VV(VV_OP,             "operator",         VAR_STRING, VV_RO), -  VV(VV_SEARCHFORWARD,  "searchforward",    VAR_NUMBER, 0), -  VV(VV_HLSEARCH,       "hlsearch",         VAR_NUMBER, 0), -  VV(VV_OLDFILES,       "oldfiles",         VAR_LIST, 0), -  VV(VV_WINDOWID,       "windowid",         VAR_NUMBER, VV_RO_SBX), -  VV(VV_PROGPATH,       "progpath",         VAR_STRING, VV_RO), -  VV(VV_COMPLETED_ITEM, "completed_item",   VAR_DICT, VV_RO), -  VV(VV_OPTION_NEW,     "option_new",       VAR_STRING, VV_RO), -  VV(VV_OPTION_OLD,     "option_old",       VAR_STRING, VV_RO), -  VV(VV_OPTION_TYPE,    "option_type",      VAR_STRING, VV_RO), -  VV(VV_ERRORS,         "errors",           VAR_LIST, 0), -  VV(VV_FALSE,          "false",            VAR_BOOL, VV_RO), -  VV(VV_TRUE,           "true",             VAR_BOOL, VV_RO), -  VV(VV_NULL,           "null",             VAR_SPECIAL, VV_RO), -  VV(VV_NUMBERMAX,      "numbermax",        VAR_NUMBER, VV_RO), -  VV(VV_NUMBERMIN,      "numbermin",        VAR_NUMBER, VV_RO), -  VV(VV_NUMBERSIZE,     "numbersize",       VAR_NUMBER, VV_RO), -  VV(VV_VIM_DID_ENTER,  "vim_did_enter",    VAR_NUMBER, VV_RO), -  VV(VV_TESTING,        "testing",          VAR_NUMBER, 0), -  VV(VV_TYPE_NUMBER,    "t_number",         VAR_NUMBER, VV_RO), -  VV(VV_TYPE_STRING,    "t_string",         VAR_NUMBER, VV_RO), -  VV(VV_TYPE_FUNC,      "t_func",           VAR_NUMBER, VV_RO), -  VV(VV_TYPE_LIST,      "t_list",           VAR_NUMBER, VV_RO), -  VV(VV_TYPE_DICT,      "t_dict",           VAR_NUMBER, VV_RO), -  VV(VV_TYPE_FLOAT,     "t_float",          VAR_NUMBER, VV_RO), -  VV(VV_TYPE_BOOL,      "t_bool",           VAR_NUMBER, VV_RO), -  VV(VV_TYPE_BLOB,      "t_blob",           VAR_NUMBER, VV_RO), -  VV(VV_EVENT,          "event",            VAR_DICT, VV_RO), -  VV(VV_ECHOSPACE,      "echospace",        VAR_NUMBER, VV_RO), -  VV(VV_ARGV,           "argv",             VAR_LIST, VV_RO), -  VV(VV_COLLATE,        "collate",          VAR_STRING, VV_RO), -  VV(VV_EXITING,        "exiting",          VAR_NUMBER, VV_RO), +  VV(VV_COUNT,            "count",            VAR_NUMBER, VV_RO), +  VV(VV_COUNT1,           "count1",           VAR_NUMBER, VV_RO), +  VV(VV_PREVCOUNT,        "prevcount",        VAR_NUMBER, VV_RO), +  VV(VV_ERRMSG,           "errmsg",           VAR_STRING, 0), +  VV(VV_WARNINGMSG,       "warningmsg",       VAR_STRING, 0), +  VV(VV_STATUSMSG,        "statusmsg",        VAR_STRING, 0), +  VV(VV_SHELL_ERROR,      "shell_error",      VAR_NUMBER, VV_RO), +  VV(VV_THIS_SESSION,     "this_session",     VAR_STRING, 0), +  VV(VV_VERSION,          "version",          VAR_NUMBER, VV_COMPAT+VV_RO), +  VV(VV_LNUM,             "lnum",             VAR_NUMBER, VV_RO_SBX), +  VV(VV_TERMRESPONSE,     "termresponse",     VAR_STRING, VV_RO), +  VV(VV_FNAME,            "fname",            VAR_STRING, VV_RO), +  VV(VV_LANG,             "lang",             VAR_STRING, VV_RO), +  VV(VV_LC_TIME,          "lc_time",          VAR_STRING, VV_RO), +  VV(VV_CTYPE,            "ctype",            VAR_STRING, VV_RO), +  VV(VV_CC_FROM,          "charconvert_from", VAR_STRING, VV_RO), +  VV(VV_CC_TO,            "charconvert_to",   VAR_STRING, VV_RO), +  VV(VV_FNAME_IN,         "fname_in",         VAR_STRING, VV_RO), +  VV(VV_FNAME_OUT,        "fname_out",        VAR_STRING, VV_RO), +  VV(VV_FNAME_NEW,        "fname_new",        VAR_STRING, VV_RO), +  VV(VV_FNAME_DIFF,       "fname_diff",       VAR_STRING, VV_RO), +  VV(VV_CMDARG,           "cmdarg",           VAR_STRING, VV_RO), +  VV(VV_FOLDSTART,        "foldstart",        VAR_NUMBER, VV_RO_SBX), +  VV(VV_FOLDEND,          "foldend",          VAR_NUMBER, VV_RO_SBX), +  VV(VV_FOLDDASHES,       "folddashes",       VAR_STRING, VV_RO_SBX), +  VV(VV_FOLDLEVEL,        "foldlevel",        VAR_NUMBER, VV_RO_SBX), +  VV(VV_PROGNAME,         "progname",         VAR_STRING, VV_RO), +  VV(VV_SEND_SERVER,      "servername",       VAR_STRING, VV_RO), +  VV(VV_DYING,            "dying",            VAR_NUMBER, VV_RO), +  VV(VV_EXCEPTION,        "exception",        VAR_STRING, VV_RO), +  VV(VV_THROWPOINT,       "throwpoint",       VAR_STRING, VV_RO), +  VV(VV_REG,              "register",         VAR_STRING, VV_RO), +  VV(VV_CMDBANG,          "cmdbang",          VAR_NUMBER, VV_RO), +  VV(VV_INSERTMODE,       "insertmode",       VAR_STRING, VV_RO), +  VV(VV_VAL,              "val",              VAR_UNKNOWN, VV_RO), +  VV(VV_KEY,              "key",              VAR_UNKNOWN, VV_RO), +  VV(VV_PROFILING,        "profiling",        VAR_NUMBER, VV_RO), +  VV(VV_FCS_REASON,       "fcs_reason",       VAR_STRING, VV_RO), +  VV(VV_FCS_CHOICE,       "fcs_choice",       VAR_STRING, 0), +  VV(VV_BEVAL_BUFNR,      "beval_bufnr",      VAR_NUMBER, VV_RO), +  VV(VV_BEVAL_WINNR,      "beval_winnr",      VAR_NUMBER, VV_RO), +  VV(VV_BEVAL_WINID,      "beval_winid",      VAR_NUMBER, VV_RO), +  VV(VV_BEVAL_LNUM,       "beval_lnum",       VAR_NUMBER, VV_RO), +  VV(VV_BEVAL_COL,        "beval_col",        VAR_NUMBER, VV_RO), +  VV(VV_BEVAL_TEXT,       "beval_text",       VAR_STRING, VV_RO), +  VV(VV_SCROLLSTART,      "scrollstart",      VAR_STRING, 0), +  VV(VV_SWAPNAME,         "swapname",         VAR_STRING, VV_RO), +  VV(VV_SWAPCHOICE,       "swapchoice",       VAR_STRING, 0), +  VV(VV_SWAPCOMMAND,      "swapcommand",      VAR_STRING, VV_RO), +  VV(VV_CHAR,             "char",             VAR_STRING, 0), +  VV(VV_MOUSE_WIN,        "mouse_win",        VAR_NUMBER, 0), +  VV(VV_MOUSE_WINID,      "mouse_winid",      VAR_NUMBER, 0), +  VV(VV_MOUSE_LNUM,       "mouse_lnum",       VAR_NUMBER, 0), +  VV(VV_MOUSE_COL,        "mouse_col",        VAR_NUMBER, 0), +  VV(VV_OP,               "operator",         VAR_STRING, VV_RO), +  VV(VV_SEARCHFORWARD,    "searchforward",    VAR_NUMBER, 0), +  VV(VV_HLSEARCH,         "hlsearch",         VAR_NUMBER, 0), +  VV(VV_OLDFILES,         "oldfiles",         VAR_LIST, 0), +  VV(VV_WINDOWID,         "windowid",         VAR_NUMBER, VV_RO_SBX), +  VV(VV_PROGPATH,         "progpath",         VAR_STRING, VV_RO), +  VV(VV_COMPLETED_ITEM,   "completed_item",   VAR_DICT, VV_RO), +  VV(VV_OPTION_NEW,       "option_new",       VAR_STRING, VV_RO), +  VV(VV_OPTION_OLD,       "option_old",       VAR_STRING, VV_RO), +  VV(VV_OPTION_OLDLOCAL,  "option_oldlocal",  VAR_STRING, VV_RO), +  VV(VV_OPTION_OLDGLOBAL, "option_oldglobal", VAR_STRING, VV_RO), +  VV(VV_OPTION_COMMAND,   "option_command",   VAR_STRING, VV_RO), +  VV(VV_OPTION_TYPE,      "option_type",      VAR_STRING, VV_RO), +  VV(VV_ERRORS,           "errors",           VAR_LIST, 0), +  VV(VV_FALSE,            "false",            VAR_BOOL, VV_RO), +  VV(VV_TRUE,             "true",             VAR_BOOL, VV_RO), +  VV(VV_NULL,             "null",             VAR_SPECIAL, VV_RO), +  VV(VV_NUMBERMAX,        "numbermax",        VAR_NUMBER, VV_RO), +  VV(VV_NUMBERMIN,        "numbermin",        VAR_NUMBER, VV_RO), +  VV(VV_NUMBERSIZE,       "numbersize",       VAR_NUMBER, VV_RO), +  VV(VV_VIM_DID_ENTER,    "vim_did_enter",    VAR_NUMBER, VV_RO), +  VV(VV_TESTING,          "testing",          VAR_NUMBER, 0), +  VV(VV_TYPE_NUMBER,      "t_number",         VAR_NUMBER, VV_RO), +  VV(VV_TYPE_STRING,      "t_string",         VAR_NUMBER, VV_RO), +  VV(VV_TYPE_FUNC,        "t_func",           VAR_NUMBER, VV_RO), +  VV(VV_TYPE_LIST,        "t_list",           VAR_NUMBER, VV_RO), +  VV(VV_TYPE_DICT,        "t_dict",           VAR_NUMBER, VV_RO), +  VV(VV_TYPE_FLOAT,       "t_float",          VAR_NUMBER, VV_RO), +  VV(VV_TYPE_BOOL,        "t_bool",           VAR_NUMBER, VV_RO), +  VV(VV_TYPE_BLOB,        "t_blob",           VAR_NUMBER, VV_RO), +  VV(VV_EVENT,            "event",            VAR_DICT, VV_RO), +  VV(VV_ECHOSPACE,        "echospace",        VAR_NUMBER, VV_RO), +  VV(VV_ARGV,             "argv",             VAR_LIST, VV_RO), +  VV(VV_COLLATE,          "collate",          VAR_STRING, VV_RO), +  VV(VV_EXITING,          "exiting",          VAR_NUMBER, VV_RO),    // Neovim -  VV(VV_STDERR,         "stderr",           VAR_NUMBER, VV_RO), -  VV(VV_MSGPACK_TYPES,  "msgpack_types",    VAR_DICT, VV_RO), -  VV(VV__NULL_STRING,   "_null_string",     VAR_STRING, VV_RO), -  VV(VV__NULL_LIST,     "_null_list",       VAR_LIST, VV_RO), -  VV(VV__NULL_DICT,     "_null_dict",       VAR_DICT, VV_RO), -  VV(VV__NULL_BLOB,     "_null_blob",       VAR_BLOB, VV_RO), -  VV(VV_LUA,            "lua",              VAR_PARTIAL, VV_RO), +  VV(VV_STDERR,           "stderr",           VAR_NUMBER, VV_RO), +  VV(VV_MSGPACK_TYPES,    "msgpack_types",    VAR_DICT, VV_RO), +  VV(VV__NULL_STRING,     "_null_string",     VAR_STRING, VV_RO), +  VV(VV__NULL_LIST,       "_null_list",       VAR_LIST, VV_RO), +  VV(VV__NULL_DICT,       "_null_dict",       VAR_DICT, VV_RO), +  VV(VV__NULL_BLOB,       "_null_blob",       VAR_BLOB, VV_RO), +  VV(VV_LUA,              "lua",              VAR_PARTIAL, VV_RO),  };  #undef VV @@ -344,7 +349,7 @@ void eval_init(void)    for (size_t i = 0; i < ARRAY_SIZE(vimvars); i++) {      p = &vimvars[i]; -    assert(STRLEN(p->vv_name) <= 16); +    assert(STRLEN(p->vv_name) <= VIMVAR_KEY_LEN);      STRCPY(p->vv_di.di_key, p->vv_name);      if (p->vv_flags & VV_RO) {        p->vv_di.di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; @@ -10446,11 +10451,15 @@ void option_last_set_msg(LastSet last_set)    }  } -// reset v:option_new, v:option_old and v:option_type +// reset v:option_new, v:option_old, v:option_oldlocal, v:option_oldglobal, +// v:option_type, and v:option_command.  void reset_v_option_vars(void)  { -  set_vim_var_string(VV_OPTION_NEW,  NULL, -1); -  set_vim_var_string(VV_OPTION_OLD,  NULL, -1); +  set_vim_var_string(VV_OPTION_NEW, NULL, -1); +  set_vim_var_string(VV_OPTION_OLD, NULL, -1); +  set_vim_var_string(VV_OPTION_OLDLOCAL, NULL, -1); +  set_vim_var_string(VV_OPTION_OLDGLOBAL, NULL, -1); +  set_vim_var_string(VV_OPTION_COMMAND, NULL, -1);    set_vim_var_string(VV_OPTION_TYPE, NULL, -1);  } diff --git a/src/nvim/eval.h b/src/nvim/eval.h index d34348a274..986dda2408 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -138,6 +138,9 @@ typedef enum {    VV_COMPLETED_ITEM,    VV_OPTION_NEW,    VV_OPTION_OLD, +  VV_OPTION_OLDLOCAL, +  VV_OPTION_OLDGLOBAL, +  VV_OPTION_COMMAND,    VV_OPTION_TYPE,    VV_ERRORS,    VV_FALSE, diff --git a/src/nvim/option.c b/src/nvim/option.c index 45e2032b35..dbe90920cd 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -1301,7 +1301,11 @@ int do_set(char_u *arg, int opt_flags)              char_u *oldval = NULL;         // previous value if *varp              char_u *newval;              char_u *origval = NULL; +            char_u *origval_l = NULL; +            char_u *origval_g = NULL;              char *saved_origval = NULL; +            char *saved_origval_l = NULL; +            char *saved_origval_g = NULL;              char *saved_newval = NULL;              unsigned newlen;              int comma; @@ -1319,10 +1323,21 @@ int do_set(char_u *arg, int opt_flags)              // new value is valid.              oldval = *(char_u **)varp; +            if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { +              origval_l = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); +              origval_g = *(char_u **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); + +              // A global-local string option might have an empty +              // option as value to indicate that the global +              // value should be used. +              if (((int)options[opt_idx].indir & PV_BOTH) && origval_l == empty_option) { +                origval_l = origval_g; +              } +            } +              // When setting the local value of a global              // option, the old value may be the global value. -            if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags -                                                            & OPT_LOCAL)) { +            if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags & OPT_LOCAL)) {                origval = *(char_u **)get_varp(&options[opt_idx]);              } else {                origval = oldval; @@ -1388,6 +1403,12 @@ int do_set(char_u *arg, int opt_flags)                  if (origval == oldval) {                    origval = *(char_u **)varp;                  } +                if (origval_l == oldval) { +                  origval_l = *(char_u **)varp; +                } +                if (origval_g == oldval) { +                  origval_g = *(char_u **)varp; +                }                  oldval = *(char_u **)varp;                }                /* @@ -1596,6 +1617,8 @@ int do_set(char_u *arg, int opt_flags)              // origval may be freed by              // did_set_string_option(), make a copy.              saved_origval = (origval != NULL) ? xstrdup((char *)origval) : 0; +            saved_origval_l = (origval_l != NULL) ? xstrdup((char *)origval_l) : 0; +            saved_origval_g = (origval_g != NULL) ? xstrdup((char *)origval_g) : 0;              // newval (and varp) may become invalid if the              // buffer is closed by autocommands. @@ -1630,8 +1653,8 @@ int do_set(char_u *arg, int opt_flags)              if (errmsg == NULL) {                if (!starting) { -                trigger_optionsset_string(opt_idx, opt_flags, saved_origval, -                                          saved_newval); +                trigger_optionsset_string(opt_idx, opt_flags, saved_origval, saved_origval_l, +                                          saved_origval_g, saved_newval);                }                if (options[opt_idx].flags & P_UI_OPTION) {                  ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -1639,6 +1662,8 @@ int do_set(char_u *arg, int opt_flags)                }              }              xfree(saved_origval); +            xfree(saved_origval_l); +            xfree(saved_origval_g);              xfree(saved_newval);              // If error detected, print the error message. @@ -2233,9 +2258,19 @@ static char *set_string_option(const int opt_idx, const char *const value, const            ? OPT_GLOBAL : OPT_LOCAL)            : opt_flags));    char *const oldval = *varp; +  char *oldval_l = NULL; +  char *oldval_g = NULL; + +  if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { +    oldval_l = *(char **)get_varp_scope(&(options[opt_idx]), OPT_LOCAL); +    oldval_g = *(char **)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); +  } +    *varp = s;    char *const saved_oldval = xstrdup(oldval); +  char *const saved_oldval_l = (oldval_l != NULL) ? xstrdup((char *)oldval_l) : 0; +  char *const saved_oldval_g = (oldval_g != NULL) ? xstrdup((char *)oldval_g) : 0;    char *const saved_newval = xstrdup(s);    int value_checked = false; @@ -2249,7 +2284,8 @@ static char *set_string_option(const int opt_idx, const char *const value, const    // call autocommand after handling side effects    if (r == NULL) {      if (!starting) { -      trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_newval); +      trigger_optionsset_string(opt_idx, opt_flags, saved_oldval, saved_oldval_l, saved_oldval_g, +                                saved_newval);      }      if (options[opt_idx].flags & P_UI_OPTION) {        ui_call_option_set(cstr_as_string(options[opt_idx].fullname), @@ -2257,6 +2293,8 @@ static char *set_string_option(const int opt_idx, const char *const value, const      }    }    xfree(saved_oldval); +  xfree(saved_oldval_l); +  xfree(saved_oldval_g);    xfree(saved_newval);    return r; @@ -3851,6 +3889,7 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va                               const int opt_flags)  {    int old_value = *(int *)varp; +  int old_global_value = 0;    // Disallow changing some options from secure mode    if ((secure || sandbox != 0) @@ -3858,6 +3897,13 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va      return (char *)e_secure;    } +  // Save the global value before changing anything. This is needed as for +  // a global-only option setting the "local value" in fact sets the global +  // value (since there is only one value). +  if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { +    old_global_value = *(int *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); +  } +    *(int *)varp = value;             // set the new value    // Remember where the option was set.    set_option_sctx_idx(opt_idx, opt_flags, current_sctx); @@ -4134,20 +4180,35 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, const int va    // Don't do this while starting up or recursively.    if (!starting && *get_vim_var_str(VV_OPTION_TYPE) == NUL) {      char buf_old[2]; +    char buf_old_global[2];      char buf_new[2];      char buf_type[7]; -    vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%d", -                 old_value ? true: false); -    vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%d", -                 value ? true: false); +    vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%d", old_value ? true : false); +    vim_snprintf(buf_old_global, ARRAY_SIZE(buf_old_global), "%d", old_global_value ? true : false); +    vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%d", value ? true : false);      vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s",                   (opt_flags & OPT_LOCAL) ? "local" : "global");      set_vim_var_string(VV_OPTION_NEW, buf_new, -1);      set_vim_var_string(VV_OPTION_OLD, buf_old, -1);      set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); -    apply_autocmds(EVENT_OPTIONSET, -                   (char_u *)options[opt_idx].fullname, -                   NULL, false, NULL); +    if (opt_flags & OPT_LOCAL) { +      set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); +      set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); +    } +    if (opt_flags & OPT_GLOBAL) { +      set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); +      set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); +    } +    if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { +      set_vim_var_string(VV_OPTION_COMMAND, "set", -1); +      set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); +      set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); +    } +    if (opt_flags & OPT_MODELINE) { +      set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); +      set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); +    } +    apply_autocmds(EVENT_OPTIONSET, (char_u *)options[opt_idx].fullname, NULL, false, NULL);      reset_v_option_vars();    } @@ -4181,7 +4242,8 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,  {    char *errmsg = NULL;    long old_value = *(long *)varp; -  long old_Rows = Rows;                 // remember old Rows +  long old_global_value = 0;  // only used when setting a local and global option +  long old_Rows = Rows;       // remember old Rows    long *pp = (long *)varp;    // Disallow changing some options from secure mode. @@ -4190,6 +4252,13 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,      return e_secure;    } +  // Save the global value before changing anything. This is needed as for +  // a global-only option setting the "local value" infact sets the global +  // value (since there is only one value). +  if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { +    old_global_value = *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL); +  } +    // Many number options assume their value is in the signed int range.    if (value < INT_MIN || value > INT_MAX) {      return e_invarg; @@ -4534,19 +4603,36 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,    // Don't do this while starting up, failure or recursively.    if (!starting && errmsg == NULL && *get_vim_var_str(VV_OPTION_TYPE) == NUL) {      char buf_old[NUMBUFLEN]; +    char buf_old_global[NUMBUFLEN];      char buf_new[NUMBUFLEN];      char buf_type[7];      vim_snprintf(buf_old, ARRAY_SIZE(buf_old), "%ld", old_value); +    vim_snprintf(buf_old_global, ARRAY_SIZE(buf_old_global), "%ld", old_global_value);      vim_snprintf(buf_new, ARRAY_SIZE(buf_new), "%ld", value);      vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s",                   (opt_flags & OPT_LOCAL) ? "local" : "global");      set_vim_var_string(VV_OPTION_NEW, buf_new, -1);      set_vim_var_string(VV_OPTION_OLD, buf_old, -1);      set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); -    apply_autocmds(EVENT_OPTIONSET, -                   (char_u *)options[opt_idx].fullname, -                   NULL, false, NULL); +    if (opt_flags & OPT_LOCAL) { +      set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); +      set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); +    } +    if (opt_flags & OPT_GLOBAL) { +      set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); +      set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old, -1); +    } +    if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { +      set_vim_var_string(VV_OPTION_COMMAND, "set", -1); +      set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); +      set_vim_var_string(VV_OPTION_OLDGLOBAL, buf_old_global, -1); +    } +    if (opt_flags & OPT_MODELINE) { +      set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); +      set_vim_var_string(VV_OPTION_OLDLOCAL, buf_old, -1); +    } +    apply_autocmds(EVENT_OPTIONSET, (char_u *)options[opt_idx].fullname, NULL, false, NULL);      reset_v_option_vars();    } @@ -4565,7 +4651,15 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char *errbuf,    return (char *)errmsg;  } -static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *newval) +/// Trigger the OptionSet autocommand. +/// "opt_idx"   is the index of the option being set. +/// "opt_flags" can be OPT_LOCAL etc. +/// "oldval"    the old value +/// "oldval_l"  the old local value (only non-NULL if global and local value are set) +/// "oldval_g"  the old global value (only non-NULL if global and local value are set) +/// "newval"    the new value +static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval, char *oldval_l, +                                      char *oldval_g, char *newval)  {    // Don't do this recursively.    if (oldval != NULL @@ -4578,8 +4672,24 @@ static void trigger_optionsset_string(int opt_idx, int opt_flags, char *oldval,      set_vim_var_string(VV_OPTION_OLD, oldval, -1);      set_vim_var_string(VV_OPTION_NEW, newval, -1);      set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); -    apply_autocmds(EVENT_OPTIONSET, -                   (char_u *)options[opt_idx].fullname, NULL, false, NULL); +    if (opt_flags & OPT_LOCAL) { +      set_vim_var_string(VV_OPTION_COMMAND, "setlocal", -1); +      set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); +    } +    if (opt_flags & OPT_GLOBAL) { +      set_vim_var_string(VV_OPTION_COMMAND, "setglobal", -1); +      set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval, -1); +    } +    if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { +      set_vim_var_string(VV_OPTION_COMMAND, "set", -1); +      set_vim_var_string(VV_OPTION_OLDLOCAL, oldval_l, -1); +      set_vim_var_string(VV_OPTION_OLDGLOBAL, oldval_g, -1); +    } +    if (opt_flags & OPT_MODELINE) { +      set_vim_var_string(VV_OPTION_COMMAND, "modeline", -1); +      set_vim_var_string(VV_OPTION_OLDLOCAL, oldval, -1); +    } +    apply_autocmds(EVENT_OPTIONSET, (char_u *)options[opt_idx].fullname, NULL, false, NULL);      reset_v_option_vars();    }  } diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 0c8b8a45d9..7835556a47 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -580,9 +580,10 @@ func Test_empty_doau()  endfunc  func s:AutoCommandOptionSet(match) +  let template = "Option: <%s>, OldVal: <%s>, OldValLocal: <%s>, OldValGlobal: <%s>, NewVal: <%s>, Scope: <%s>, Command: <%s>\n"    let item     = remove(g:options, 0) -  let expected = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", item[0], item[1], item[2], item[3]) -  let actual   = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", a:match, v:option_old, v:option_new, v:option_type) +  let expected = printf(template, item[0], item[1], item[2], item[3], item[4], item[5], item[6]) +  let actual   = printf(template, a:match, v:option_old, v:option_oldlocal, v:option_oldglobal, v:option_new, v:option_type, v:option_command)    let g:opt    = [expected, actual]    "call assert_equal(expected, actual)  endfunc @@ -596,130 +597,593 @@ func Test_OptionSet()    au OptionSet * :call s:AutoCommandOptionSet(expand("<amatch>"))    " 1: Setting number option" -  let g:options = [['number', 0, 1, 'global']] +  let g:options = [['number', 0, 0, 0, 1, 'global', 'set']]    set nu    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 2: Setting local number option" -  let g:options = [['number', 1, 0, 'local']] +  let g:options = [['number', 1, 1, '', 0, 'local', 'setlocal']]    setlocal nonu    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 3: Setting global number option" -  let g:options = [['number', 1, 0, 'global']] +  let g:options = [['number', 1, '', 1, 0, 'global', 'setglobal']]    setglobal nonu    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 4: Setting local autoindent option" -  let g:options = [['autoindent', 0, 1, 'local']] +  let g:options = [['autoindent', 0, 0, '', 1, 'local', 'setlocal']]    setlocal ai    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 5: Setting global autoindent option" -  let g:options = [['autoindent', 0, 1, 'global']] +  let g:options = [['autoindent', 0, '', 0, 1, 'global', 'setglobal']]    setglobal ai    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 6: Setting global autoindent option" -  let g:options = [['autoindent', 1, 0, 'global']] +  let g:options = [['autoindent', 1, 1, 1, 0, 'global', 'set']] +  set ai! +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 6a: Setting global autoindent option" +  let g:options = [['autoindent', 1, 1, 0, 0, 'global', 'set']] +  noa setlocal ai +  noa setglobal noai    set ai!    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " Should not print anything, use :noa    " 7: don't trigger OptionSet" -  let g:options = [['invalid', 1, 1, 'invalid']] +  let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']]    noa set nonu -  call assert_equal([['invalid', 1, 1, 'invalid']], g:options) +  call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 8: Setting several global list and number option" -  let g:options = [['list', 0, 1, 'global'], ['number', 0, 1, 'global']] +  let g:options = [['list', 0, 0, 0, 1, 'global', 'set'], ['number', 0, 0, 0, 1, 'global', 'set']]    set list nu    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 9: don't trigger OptionSet" -  let g:options = [['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']] +  let g:options = [['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']]    noa set nolist nonu -  call assert_equal([['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']], g:options) +  call assert_equal([['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid'], ['invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid', 'invalid']], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 10: Setting global acd" -  let g:options = [['autochdir', 0, 1, 'local']] +  let g:options = [['autochdir', 0, 0, '', 1, 'local', 'setlocal']]    setlocal acd    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 11: Setting global autoread (also sets local value)" -  let g:options = [['autoread', 0, 1, 'global']] +  let g:options = [['autoread', 0, 0, 0, 1, 'global', 'set']]    set ar    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 12: Setting local autoread" -  let g:options = [['autoread', 1, 1, 'local']] +  let g:options = [['autoread', 1, 1, '', 1, 'local', 'setlocal']]    setlocal ar    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 13: Setting global autoread" -  let g:options = [['autoread', 1, 0, 'global']] +  let g:options = [['autoread', 1, '', 1, 0, 'global', 'setglobal']]    setglobal invar    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 14: Setting option backspace through :let" -  let g:options = [['backspace', '', 'eol,indent,start', 'global']] +  let g:options = [['backspace', '', '', '', 'eol,indent,start', 'global', 'set']]    let &bs = "eol,indent,start"    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 15: Setting option backspace through setbufvar()" -  let g:options = [['backup', 0, 1, 'local']] +  let g:options = [['backup', 0, 0, '', 1, 'local', 'setlocal']]    " try twice, first time, shouldn't trigger because option name is invalid,    " second time, it should trigger -  call assert_fails("call setbufvar(1, '&l:bk', 1)", "E355") +  let bnum = bufnr('%') +  call assert_fails("call setbufvar(bnum, '&l:bk', 1)", 'E355:')    " should trigger, use correct option name -  call setbufvar(1, '&backup', 1) +  call setbufvar(bnum, '&backup', 1)    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 16: Setting number option using setwinvar" -  let g:options = [['number', 0, 1, 'local']] +  let g:options = [['number', 0, 0, '', 1, 'local', 'setlocal']]    call setwinvar(0, '&number', 1)    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1])    " 17: Setting key option, shouldn't trigger" -  let g:options = [['key', 'invalid', 'invalid1', 'invalid']] +  let g:options = [['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']]    setlocal key=blah    setlocal key= -  call assert_equal([['key', 'invalid', 'invalid1', 'invalid']], g:options) +  call assert_equal([['key', 'invalid', 'invalid1', 'invalid2', 'invalid3', 'invalid4', 'invalid5']], g:options)    call assert_equal(g:opt[0], g:opt[1]) -  " 18: Setting string option" + +  " 18a: Setting string global option" +  let oldval = &backupext +  let g:options = [['backupext', oldval, oldval, oldval, 'foo', 'global', 'set']] +  set backupext=foo +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 18b: Resetting string global option" +  let g:options = [['backupext', 'foo', 'foo', 'foo', oldval, 'global', 'set']] +  set backupext& +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 18c: Setting global string global option" +  let g:options = [['backupext', oldval, '', oldval, 'bar', 'global', 'setglobal']] +  setglobal backupext=bar +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 18d: Setting local string global option" +  " As this is a global option this sets the global value even though +  " :setlocal is used! +  noa set backupext& " Reset global and local value (without triggering autocmd) +  let g:options = [['backupext', oldval, oldval, '', 'baz', 'local', 'setlocal']] +  setlocal backupext=baz +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 18e: Setting again string global option" +  noa setglobal backupext=ext_global " Reset global and local value (without triggering autocmd) +  noa setlocal backupext=ext_local " Sets the global(!) value! +  let g:options = [['backupext', 'ext_local', 'ext_local', 'ext_local', 'fuu', 'global', 'set']] +  set backupext=fuu +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 19a: Setting string local-global (to buffer) option"    let oldval = &tags -  let g:options = [['tags', oldval, 'tagpath', 'global']] +  let g:options = [['tags', oldval, oldval, oldval, 'tagpath', 'global', 'set']]    set tags=tagpath    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1]) -  " 1l: Resetting string option" -  let g:options = [['tags', 'tagpath', oldval, 'global']] +  " 19b: Resetting string local-global (to buffer) option" +  let g:options = [['tags', 'tagpath', 'tagpath', 'tagpath', oldval, 'global', 'set']]    set tags&    call assert_equal([], g:options)    call assert_equal(g:opt[0], g:opt[1]) +  " 19c: Setting global string local-global (to buffer) option " +  let g:options = [['tags', oldval, '', oldval, 'tagpath1', 'global', 'setglobal']] +  setglobal tags=tagpath1 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 19d: Setting local string local-global (to buffer) option" +  let g:options = [['tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal']] +  setlocal tags=tagpath2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 19e: Setting again string local-global (to buffer) option" +  " Note: v:option_old is the old global value for local-global string options +  " but the old local value for all other kinds of options. +  noa setglobal tags=tag_global " Reset global and local value (without triggering autocmd) +  noa setlocal tags=tag_local +  let g:options = [['tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set']] +  set tags=tagpath +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 19f: Setting string local-global (to buffer) option to an empty string" +  " Note: v:option_old is the old global value for local-global string options +  " but the old local value for all other kinds of options. +  noa set tags=tag_global " Reset global and local value (without triggering autocmd) +  noa setlocal tags= " empty string +  let g:options = [['tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set']] +  set tags=tagpath +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 20a: Setting string local (to buffer) option" +  let oldval = &spelllang +  let g:options = [['spelllang', oldval, oldval, oldval, 'elvish,klingon', 'global', 'set']] +  set spelllang=elvish,klingon +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 20b: Resetting string local (to buffer) option" +  let g:options = [['spelllang', 'elvish,klingon', 'elvish,klingon', 'elvish,klingon', oldval, 'global', 'set']] +  set spelllang& +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 20c: Setting global string local (to buffer) option" +  let g:options = [['spelllang', oldval, '', oldval, 'elvish', 'global', 'setglobal']] +  setglobal spelllang=elvish +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 20d: Setting local string local (to buffer) option" +  noa set spelllang& " Reset global and local value (without triggering autocmd) +  let g:options = [['spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal']] +  setlocal spelllang=klingon +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 20e: Setting again string local (to buffer) option" +  " Note: v:option_old is the old global value for local-global string options +  " but the old local value for all other kinds of options. +  noa setglobal spelllang=spellglobal " Reset global and local value (without triggering autocmd) +  noa setlocal spelllang=spelllocal +  let g:options = [['spelllang', 'spelllocal', 'spelllocal', 'spellglobal', 'foo', 'global', 'set']] +  set spelllang=foo +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 21a: Setting string local-global (to window) option" +  let oldval = &statusline +  let g:options = [['statusline', oldval, oldval, oldval, 'foo', 'global', 'set']] +  set statusline=foo +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 21b: Resetting string local-global (to window) option" +  " Note: v:option_old is the old global value for local-global string options +  " but the old local value for all other kinds of options. +  let g:options = [['statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set']] +  set statusline& +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 21c: Setting global string local-global (to window) option" +  let g:options = [['statusline', oldval, '', oldval, 'bar', 'global', 'setglobal']] +  setglobal statusline=bar +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 21d: Setting local string local-global (to window) option" +  noa set statusline& " Reset global and local value (without triggering autocmd) +  let g:options = [['statusline', oldval, oldval, '', 'baz', 'local', 'setlocal']] +  setlocal statusline=baz +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 21e: Setting again string local-global (to window) option" +  " Note: v:option_old is the old global value for local-global string options +  " but the old local value for all other kinds of options. +  noa setglobal statusline=bar " Reset global and local value (without triggering autocmd) +  noa setlocal statusline=baz +  let g:options = [['statusline', 'bar', 'baz', 'bar', 'foo', 'global', 'set']] +  set statusline=foo +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 22a: Setting string local (to window) option" +  let oldval = &foldignore +  let g:options = [['foldignore', oldval, oldval, oldval, 'fo', 'global', 'set']] +  set foldignore=fo +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 22b: Resetting string local (to window) option" +  let g:options = [['foldignore', 'fo', 'fo', 'fo', oldval, 'global', 'set']] +  set foldignore& +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 22c: Setting global string local (to window) option" +  let g:options = [['foldignore', oldval, '', oldval, 'bar', 'global', 'setglobal']] +  setglobal foldignore=bar +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 22d: Setting local string local (to window) option" +  noa set foldignore& " Reset global and local value (without triggering autocmd) +  let g:options = [['foldignore', oldval, oldval, '', 'baz', 'local', 'setlocal']] +  setlocal foldignore=baz +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 22e: Setting again string local (to window) option" +  noa setglobal foldignore=glob " Reset global and local value (without triggering autocmd) +  noa setlocal foldignore=loc +  let g:options = [['foldignore', 'loc', 'loc', 'glob', 'fo', 'global', 'set']] +  set foldignore=fo +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 23a: Setting global number local option" +  noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd) +  noa setlocal cmdheight=1 " Sets the global(!) value! +  let g:options = [['cmdheight', '1', '', '1', '2', 'global', 'setglobal']] +  setglobal cmdheight=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 23b: Setting local number global option" +  noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd) +  noa setlocal cmdheight=1 " Sets the global(!) value! +  let g:options = [['cmdheight', '1', '1', '', '2', 'local', 'setlocal']] +  setlocal cmdheight=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 23c: Setting again number global option" +  noa setglobal cmdheight=8 " Reset global and local value (without triggering autocmd) +  noa setlocal cmdheight=1 " Sets the global(!) value! +  let g:options = [['cmdheight', '1', '1', '1', '2', 'global', 'set']] +  set cmdheight=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 23d: Setting again number global option" +  noa set cmdheight=8 " Reset global and local value (without triggering autocmd) +  let g:options = [['cmdheight', '8', '8', '8', '2', 'global', 'set']] +  set cmdheight=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 24a: Setting global number global-local (to buffer) option" +  noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd) +  noa setlocal undolevels=1 +  let g:options = [['undolevels', '8', '', '8', '2', 'global', 'setglobal']] +  setglobal undolevels=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 24b: Setting local number global-local (to buffer) option" +  noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd) +  noa setlocal undolevels=1 +  let g:options = [['undolevels', '1', '1', '', '2', 'local', 'setlocal']] +  setlocal undolevels=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 24c: Setting again number global-local (to buffer) option" +  noa setglobal undolevels=8 " Reset global and local value (without triggering autocmd) +  noa setlocal undolevels=1 +  let g:options = [['undolevels', '1', '1', '8', '2', 'global', 'set']] +  set undolevels=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 24d: Setting again global number global-local (to buffer) option" +  noa set undolevels=8 " Reset global and local value (without triggering autocmd) +  let g:options = [['undolevels', '8', '8', '8', '2', 'global', 'set']] +  set undolevels=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 25a: Setting global number local (to buffer) option" +  noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd) +  noa setlocal wrapmargin=1 +  let g:options = [['wrapmargin', '8', '', '8', '2', 'global', 'setglobal']] +  setglobal wrapmargin=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 25b: Setting local number local (to buffer) option" +  noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd) +  noa setlocal wrapmargin=1 +  let g:options = [['wrapmargin', '1', '1', '', '2', 'local', 'setlocal']] +  setlocal wrapmargin=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 25c: Setting again number local (to buffer) option" +  noa setglobal wrapmargin=8 " Reset global and local value (without triggering autocmd) +  noa setlocal wrapmargin=1 +  let g:options = [['wrapmargin', '1', '1', '8', '2', 'global', 'set']] +  set wrapmargin=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 25d: Setting again global number local (to buffer) option" +  noa set wrapmargin=8 " Reset global and local value (without triggering autocmd) +  let g:options = [['wrapmargin', '8', '8', '8', '2', 'global', 'set']] +  set wrapmargin=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 26: Setting number global-local (to window) option. +  " Such option does currently not exist. + + +  " 27a: Setting global number local (to window) option" +  noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd) +  noa setlocal foldcolumn=1 +  let g:options = [['foldcolumn', '8', '', '8', '2', 'global', 'setglobal']] +  setglobal foldcolumn=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 27b: Setting local number local (to window) option" +  noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd) +  noa setlocal foldcolumn=1 +  let g:options = [['foldcolumn', '1', '1', '', '2', 'local', 'setlocal']] +  setlocal foldcolumn=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 27c: Setting again number local (to window) option" +  noa setglobal foldcolumn=8 " Reset global and local value (without triggering autocmd) +  noa setlocal foldcolumn=1 +  let g:options = [['foldcolumn', '1', '1', '8', '2', 'global', 'set']] +  set foldcolumn=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 27d: Ssettin again global number local (to window) option" +  noa set foldcolumn=8 " Reset global and local value (without triggering autocmd) +  let g:options = [['foldcolumn', '8', '8', '8', '2', 'global', 'set']] +  set foldcolumn=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 28a: Setting global boolean global option" +  noa setglobal nowrapscan " Reset global and local value (without triggering autocmd) +  noa setlocal wrapscan " Sets the global(!) value! +  let g:options = [['wrapscan', '1', '', '1', '0', 'global', 'setglobal']] +  setglobal nowrapscan +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 28b: Setting local boolean global option" +  noa setglobal nowrapscan " Reset global and local value (without triggering autocmd) +  noa setlocal wrapscan " Sets the global(!) value! +  let g:options = [['wrapscan', '1', '1', '', '0', 'local', 'setlocal']] +  setlocal nowrapscan +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 28c: Setting again boolean global option" +  noa setglobal nowrapscan " Reset global and local value (without triggering autocmd) +  noa setlocal wrapscan " Sets the global(!) value! +  let g:options = [['wrapscan', '1', '1', '1', '0', 'global', 'set']] +  set nowrapscan +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 28d: Setting again global boolean global option" +  noa set nowrapscan " Reset global and local value (without triggering autocmd) +  let g:options = [['wrapscan', '0', '0', '0', '1', 'global', 'set']] +  set wrapscan +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 29a: Setting global boolean global-local (to buffer) option" +  noa setglobal noautoread " Reset global and local value (without triggering autocmd) +  noa setlocal autoread +  let g:options = [['autoread', '0', '', '0', '1', 'global', 'setglobal']] +  setglobal autoread +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 29b: Setting local boolean global-local (to buffer) option" +  noa setglobal noautoread " Reset global and local value (without triggering autocmd) +  noa setlocal autoread +  let g:options = [['autoread', '1', '1', '', '0', 'local', 'setlocal']] +  setlocal noautoread +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 29c: Setting again boolean global-local (to buffer) option" +  noa setglobal noautoread " Reset global and local value (without triggering autocmd) +  noa setlocal autoread +  let g:options = [['autoread', '1', '1', '0', '1', 'global', 'set']] +  set autoread +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 29d: Setting again global boolean global-local (to buffer) option" +  noa set noautoread " Reset global and local value (without triggering autocmd) +  let g:options = [['autoread', '0', '0', '0', '1', 'global', 'set']] +  set autoread +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 30a: Setting global boolean local (to buffer) option" +  noa setglobal nocindent " Reset global and local value (without triggering autocmd) +  noa setlocal cindent +  let g:options = [['cindent', '0', '', '0', '1', 'global', 'setglobal']] +  setglobal cindent +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 30b: Setting local boolean local (to buffer) option" +  noa setglobal nocindent " Reset global and local value (without triggering autocmd) +  noa setlocal cindent +  let g:options = [['cindent', '1', '1', '', '0', 'local', 'setlocal']] +  setlocal nocindent +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 30c: Setting again boolean local (to buffer) option" +  noa setglobal nocindent " Reset global and local value (without triggering autocmd) +  noa setlocal cindent +  let g:options = [['cindent', '1', '1', '0', '1', 'global', 'set']] +  set cindent +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 30d: Setting again global boolean local (to buffer) option" +  noa set nocindent " Reset global and local value (without triggering autocmd) +  let g:options = [['cindent', '0', '0', '0', '1', 'global', 'set']] +  set cindent +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 31: Setting boolean global-local (to window) option +  " Currently no such option exists. + + +  " 32a: Setting global boolean local (to window) option" +  noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd) +  noa setlocal cursorcolumn +  let g:options = [['cursorcolumn', '0', '', '0', '1', 'global', 'setglobal']] +  setglobal cursorcolumn +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 32b: Setting local boolean local (to window) option" +  noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd) +  noa setlocal cursorcolumn +  let g:options = [['cursorcolumn', '1', '1', '', '0', 'local', 'setlocal']] +  setlocal nocursorcolumn +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 32c: Setting again boolean local (to window) option" +  noa setglobal nocursorcolumn " Reset global and local value (without triggering autocmd) +  noa setlocal cursorcolumn +  let g:options = [['cursorcolumn', '1', '1', '0', '1', 'global', 'set']] +  set cursorcolumn +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +  " 32d: Setting again global boolean local (to window) option" +  noa set nocursorcolumn " Reset global and local value (without triggering autocmd) +  let g:options = [['cursorcolumn', '0', '0', '0', '1', 'global', 'set']] +  set cursorcolumn +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + + +  " 33: Test autocommands when an option value is converted internally. +  noa set backspace=1 " Reset global and local value (without triggering autocmd) +  let g:options = [['backspace', 'indent,eol', 'indent,eol', 'indent,eol', '2', 'global', 'set']] +  set backspace=2 +  call assert_equal([], g:options) +  call assert_equal(g:opt[0], g:opt[1]) + +    " Cleanup    au! OptionSet    " set tags& -  for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'tags'] +  for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'backupext', 'tags', 'spelllang', 'statusline', 'foldignore', 'cmdheight', 'undolevels', 'wrapmargin', 'foldcolumn', 'wrapscan', 'autoread', 'cindent', 'cursorcolumn']      exe printf(":set %s&vim", opt)    endfor    call test_override('starting', 0) diff --git a/test/functional/legacy/autocmd_option_spec.lua b/test/functional/legacy/autocmd_option_spec.lua index 1914818215..5e586d3a6a 100644 --- a/test/functional/legacy/autocmd_option_spec.lua +++ b/test/functional/legacy/autocmd_option_spec.lua @@ -1,6 +1,6 @@  local helpers = require('test.functional.helpers')(after_each)  local nvim = helpers.meths -local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq +local clear, eq, neq, eval = helpers.clear, helpers.eq, helpers.neq, helpers.eval  local curbuf, buf = helpers.curbuf, helpers.bufmeths  local curwin = helpers.curwin  local exec_capture = helpers.exec_capture @@ -10,11 +10,14 @@ local function declare_hook_function()    source([[      fu! AutoCommand(match, bufnr, winnr)        let l:acc = { -      \   'option' : a:match, -      \   'oldval' : v:option_old, -      \   'newval' : v:option_new, -      \   'scope'  : v:option_type, -      \   'attr'   : { +      \   'option'   : a:match, +      \   'oldval'   : v:option_old, +      \   'oldval_l' : v:option_oldlocal, +      \   'oldval_g' : v:option_oldglobal, +      \   'newval'   : v:option_new, +      \   'scope'    : v:option_type, +      \   'cmd'      : v:option_command, +      \   'attr'     : {        \     'bufnr' : a:bufnr,        \     'winnr' : a:winnr,        \   } @@ -42,13 +45,16 @@ local function get_result()    return ret  end -local function expected_table(option, oldval, newval, scope, attr) +local function expected_table(option, oldval, oldval_l, oldval_g, newval, scope, cmd, attr)    return { -    option = option, -    oldval = tostring(oldval), -    newval = tostring(newval), -    scope  = scope, -    attr   = attr, +    option   = option, +    oldval   = tostring(oldval), +    oldval_l = tostring(oldval_l), +    oldval_g = tostring(oldval_g), +    newval   = tostring(newval), +    scope    = scope, +    cmd      = cmd, +    attr     = attr,    }  end @@ -66,7 +72,7 @@ local function expected_combination(...)    end    for i, v in ipairs(args) do -    local attr = v[5] +    local attr = v[8]      if not attr then        -- remove attr entries        ret[i].attr = nil @@ -112,7 +118,7 @@ local function get_new_window_number()  end  describe('au OptionSet', function() -  describe('with any opton (*)', function() +  describe('with any option (*)', function()      before_each(function()        clear() @@ -123,44 +129,44 @@ describe('au OptionSet', function()      it('should be called in setting number option', function()        command('set nu') -      expected_combination({'number', 0, 1, 'global'}) +      expected_combination({'number', 0, 0, 0, 1, 'global', 'set'})        command('setlocal nonu') -      expected_combination({'number', 1, 0, 'local'}) +      expected_combination({'number', 1, 1, '', 0, 'local', 'setlocal'})        command('setglobal nonu') -      expected_combination({'number', 1, 0, 'global'}) +      expected_combination({'number', 1, '', 1, 0, 'global', 'setglobal'})      end)      it('should be called in setting autoindent option',function()        command('setlocal ai') -      expected_combination({'autoindent', 0, 1, 'local'}) +      expected_combination({'autoindent', 0, 0, '', 1, 'local', 'setlocal'})        command('setglobal ai') -      expected_combination({'autoindent', 0, 1, 'global'}) +      expected_combination({'autoindent', 0, '', 0, 1, 'global', 'setglobal'})        command('set noai') -      expected_combination({'autoindent', 1, 0, 'global'}) +      expected_combination({'autoindent', 1, 1, 1, 0, 'global', 'set'})      end)      it('should be called in inverting global autoindent option',function()        command('set ai!') -      expected_combination({'autoindent', 0, 1, 'global'}) +      expected_combination({'autoindent', 0, 0, 0, 1, 'global', 'set'})      end)      it('should be called in being unset local autoindent option',function()        command('setlocal ai') -      expected_combination({'autoindent', 0, 1, 'local'}) +      expected_combination({'autoindent', 0, 0, '', 1, 'local', 'setlocal'})        command('setlocal ai<') -      expected_combination({'autoindent', 1, 0, 'local'}) +      expected_combination({'autoindent', 1, 1, '', 0, 'local', 'setlocal'})      end)      it('should be called in setting global list and number option at the same time',function()        command('set list nu')        expected_combination( -        {'list', 0, 1, 'global'}, -        {'number', 0, 1, 'global'} +        {'list', 0, 0, 0, 1, 'global', 'set'}, +        {'number', 0, 0, 0, 1, 'global', 'set'}        )      end) @@ -171,25 +177,27 @@ describe('au OptionSet', function()      it('should be called in setting local acd', function()        command('setlocal acd') -      expected_combination({'autochdir', 0, 1, 'local'}) +      expected_combination({'autochdir', 0, 0, '', 1, 'local', 'setlocal'})      end)      it('should be called in setting autoread', function()        command('set noar') -      expected_combination({'autoread', 1, 0, 'global'}) +      expected_combination({'autoread', 1, 1, 1, 0, 'global', 'set'})        command('setlocal ar') -      expected_combination({'autoread', 0, 1, 'local'}) +      expected_combination({'autoread', 0, 0, '', 1, 'local', 'setlocal'})      end)      it('should be called in inverting global autoread', function()        command('setglobal invar') -      expected_combination({'autoread', 1, 0, 'global'}) +      expected_combination({'autoread', 1, '', 1, 0, 'global', 'setglobal'})      end)      it('should be called in setting backspace option through :let', function() +      local oldval = eval('&backspace') +        command('let &bs=""') -      expected_combination({'backspace', 'indent,eol,start', '', 'global'}) +      expected_combination({'backspace', oldval, oldval, oldval, '', 'global', 'set'})      end)      describe('being set by setbufvar()', function() @@ -200,7 +208,7 @@ describe('au OptionSet', function()        it('should trigger using correct option name', function()          command('call setbufvar(1, "&backup", 1)') -        expected_combination({'backup', 0, 1, 'local'}) +        expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'})        end)        it('should trigger if the current buffer is different from the targetted buffer', function() @@ -208,9 +216,339 @@ describe('au OptionSet', function()          local new_bufnr = buf.get_number(new_buffer)          command('call setbufvar(' .. new_bufnr .. ', "&buftype", "nofile")') -        expected_combination({'buftype', '', 'nofile', 'local', {bufnr = new_bufnr}}) +        expected_combination({'buftype', '', '', '', 'nofile', 'local', 'setlocal', {bufnr = new_bufnr}})        end)      end) + +    it('with string global option', function() +      local oldval = eval('&backupext') + +      command('set backupext=foo') +      expected_combination({'backupext', oldval, oldval, oldval, 'foo', 'global', 'set'}) + +      command('set backupext&') +      expected_combination({'backupext', 'foo', 'foo', 'foo', oldval, 'global', 'set'}) + +      command('setglobal backupext=bar') +      expected_combination({'backupext', oldval, '', oldval, 'bar', 'global', 'setglobal'}) + +      command('noa set backupext&') +      -- As this is a global option this sets the global value even though :setlocal is used! +      command('setlocal backupext=baz') +      expected_combination({'backupext', oldval, oldval, '', 'baz', 'local', 'setlocal'}) + +      command('noa setglobal backupext=ext_global') +      command('noa setlocal backupext=ext_local') -- Sets the global(!) value +      command('set backupext=foo') +      expected_combination({ +        'backupext', 'ext_local', 'ext_local', 'ext_local', 'foo', 'global', 'set' +      }) +    end) + +    it('with string global-local (to buffer) option', function() +      local oldval = eval('&tags') + +      command('set tags=tagpath') +      expected_combination({'tags', oldval, oldval, oldval, 'tagpath', 'global', 'set'}) + +      command('set tags&') +      expected_combination({'tags', 'tagpath', 'tagpath', 'tagpath', oldval, 'global', 'set'}) + +      command('setglobal tags=tagpath1') +      expected_combination({'tags', oldval, '', oldval, 'tagpath1', 'global', 'setglobal'}) + +      command('setlocal tags=tagpath2') +      expected_combination({'tags', 'tagpath1', 'tagpath1', '', 'tagpath2', 'local', 'setlocal'}) + +      -- Note: v:option_old is the old global value for global-local string options +      -- but the old local value for all other kinds of options. +      command('noa setglobal tags=tag_global') +      command('noa setlocal tags=tag_local') +      command('set tags=tagpath') +      expected_combination({ +        'tags', 'tag_global', 'tag_local', 'tag_global', 'tagpath', 'global', 'set' +      }) + +      -- Note: v:option_old is the old global value for global-local string options +      -- but the old local value for all other kinds of options. +      command('noa set tags=tag_global') +      command('noa setlocal tags=') +      command('set tags=tagpath') +      expected_combination({'tags', 'tag_global', '', 'tag_global', 'tagpath', 'global', 'set'}) +    end) + +    it('with string local (to buffer) option', function() +      local oldval = eval('&spelllang') + +      command('set spelllang=elvish,klingon') +      expected_combination({'spelllang', oldval, oldval, oldval, 'elvish,klingon', 'global', 'set'}) + +      command('set spelllang&') +      expected_combination({ +        'spelllang', 'elvish,klingon', 'elvish,klingon', 'elvish,klingon', oldval, 'global', 'set' +      }) + +      command('setglobal spelllang=elvish') +      expected_combination({'spelllang', oldval, '', oldval, 'elvish', 'global', 'setglobal'}) + +      command('noa set spelllang&') +      command('setlocal spelllang=klingon') +      expected_combination({'spelllang', oldval, oldval, '', 'klingon', 'local', 'setlocal'}) + +      -- Note: v:option_old is the old global value for global-local string options +      -- but the old local value for all other kinds of options. +      command('noa setglobal spelllang=spellglobal') +      command('noa setlocal spelllang=spelllocal') +      command('set spelllang=foo') +      expected_combination({ +        'spelllang', 'spelllocal', 'spelllocal', 'spellglobal', 'foo', 'global', 'set' +      }) +    end) + +    it('with string global-local (to window) option', function() +      local oldval = eval('&statusline') + +      command('set statusline=foo') +      expected_combination({'statusline', oldval, oldval, '', 'foo', 'global', 'set'}) + +      -- Note: v:option_old is the old global value for global-local string options +      -- but the old local value for all other kinds of options. +      command('set statusline&') +      expected_combination({'statusline', 'foo', 'foo', 'foo', oldval, 'global', 'set'}) + +      command('setglobal statusline=bar') +      expected_combination({'statusline', oldval, '', oldval, 'bar', 'global', 'setglobal'}) + +      command('noa set statusline&') +      command('setlocal statusline=baz') +      expected_combination({'statusline', oldval, oldval, '', 'baz', 'local', 'setlocal'}) + +      -- Note: v:option_old is the old global value for global-local string options +      -- but the old local value for all other kinds of options. +      command('noa setglobal statusline=bar') +      command('noa setlocal statusline=baz') +      command('set statusline=foo') +      expected_combination({'statusline', 'bar', 'baz', 'bar', 'foo', 'global', 'set'}) +    end) + +    it('with string local (to window) option', function() +      local oldval = eval('&foldignore') + +      command('set foldignore=fo') +      expected_combination({'foldignore', oldval, oldval, oldval, 'fo', 'global', 'set'}) + +      command('set foldignore&') +      expected_combination({'foldignore', 'fo', 'fo', 'fo', oldval, 'global', 'set'}) + +      command('setglobal foldignore=bar') +      expected_combination({'foldignore', oldval, '', oldval, 'bar', 'global', 'setglobal'}) + +      command('noa set foldignore&') +      command('setlocal foldignore=baz') +      expected_combination({'foldignore', oldval, oldval, '', 'baz', 'local', 'setlocal'}) + +      command('noa setglobal foldignore=glob') +      command('noa setlocal foldignore=loc') +      command('set foldignore=fo') +      expected_combination({'foldignore', 'loc', 'loc', 'glob', 'fo', 'global', 'set'}) +    end) + +    it('with number global option', function() +      command('noa setglobal cmdheight=8') +      command('noa setlocal cmdheight=1') -- Sets the global(!) value +      command('setglobal cmdheight=2') +      expected_combination({'cmdheight', 1, '', 1, 2, 'global', 'setglobal'}) + +      command('noa setglobal cmdheight=8') +      command('noa setlocal cmdheight=1') -- Sets the global(!) value +      command('setlocal cmdheight=2') +      expected_combination({'cmdheight', 1, 1, '', 2, 'local', 'setlocal'}) + +      command('noa setglobal cmdheight=8') +      command('noa setlocal cmdheight=1') -- Sets the global(!) value +      command('set cmdheight=2') +      expected_combination({'cmdheight', 1, 1, 1, 2, 'global', 'set'}) + +      command('noa set cmdheight=8') +      command('set cmdheight=2') +      expected_combination({'cmdheight', 8, 8, 8, 2, 'global', 'set'}) +    end) + +    it('with number global-local (to buffer) option', function() +      command('noa setglobal undolevels=8') +      command('noa setlocal undolevels=1') +      command('setglobal undolevels=2') +      expected_combination({'undolevels', 8, '', 8, 2, 'global', 'setglobal'}) + +      command('noa setglobal undolevels=8') +      command('noa setlocal undolevels=1') +      command('setlocal undolevels=2') +      expected_combination({'undolevels', 1, 1, '', 2, 'local', 'setlocal'}) + +      command('noa setglobal undolevels=8') +      command('noa setlocal undolevels=1') +      command('set undolevels=2') +      expected_combination({'undolevels', 1, 1, 8, 2, 'global', 'set'}) + +      command('noa set undolevels=8') +      command('set undolevels=2') +      expected_combination({'undolevels', 8, 8, 8, 2, 'global', 'set'}) +    end) + +    it('with number local (to buffer) option', function() +      command('noa setglobal wrapmargin=8') +      command('noa setlocal wrapmargin=1') +      command('setglobal wrapmargin=2') +      expected_combination({'wrapmargin', 8, '', 8, 2, 'global', 'setglobal'}) + +      command('noa setglobal wrapmargin=8') +      command('noa setlocal wrapmargin=1') +      command('setlocal wrapmargin=2') +      expected_combination({'wrapmargin', 1, 1, '', 2, 'local', 'setlocal'}) + +      command('noa setglobal wrapmargin=8') +      command('noa setlocal wrapmargin=1') +      command('set wrapmargin=2') +      expected_combination({'wrapmargin', 1, 1, 8, 2, 'global', 'set'}) + +      command('noa set wrapmargin=8') +      command('set wrapmargin=2') +      expected_combination({'wrapmargin', 8, 8, 8, 2, 'global', 'set'}) +    end) + +    it('with number global-local (to window) option', function() +      command('noa setglobal scrolloff=8') +      command('noa setlocal scrolloff=1') +      command('setglobal scrolloff=2') +      expected_combination({'scrolloff', 8, '', 8, 2, 'global', 'setglobal'}) + +      command('noa setglobal scrolloff=8') +      command('noa setlocal scrolloff=1') +      command('setlocal scrolloff=2') +      expected_combination({'scrolloff', 1, 1, '', 2, 'local', 'setlocal'}) + +      command('noa setglobal scrolloff=8') +      command('noa setlocal scrolloff=1') +      command('set scrolloff=2') +      expected_combination({'scrolloff', 1, 1, 8, 2, 'global', 'set'}) + +      command('noa set scrolloff=8') +      command('set scrolloff=2') +      expected_combination({'scrolloff', 8, 8, 8, 2, 'global', 'set'}) +    end) + +    it('with number local (to window) option', function() +      command('noa setglobal foldcolumn=8') +      command('noa setlocal foldcolumn=1') +      command('setglobal foldcolumn=2') +      expected_combination({'foldcolumn', 8, '', 8, 2, 'global', 'setglobal'}) + +      command('noa setglobal foldcolumn=8') +      command('noa setlocal foldcolumn=1') +      command('setlocal foldcolumn=2') +      expected_combination({'foldcolumn', 1, 1, '', 2, 'local', 'setlocal'}) + +      command('noa setglobal foldcolumn=8') +      command('noa setlocal foldcolumn=1') +      command('set foldcolumn=2') +      expected_combination({'foldcolumn', 1, 1, 8, 2, 'global', 'set'}) + +      command('noa set foldcolumn=8') +      command('set foldcolumn=2') +      expected_combination({'foldcolumn', 8, 8, 8, 2, 'global', 'set'}) +    end) + +    it('with boolean global option', function() +      command('noa setglobal nowrapscan') +      command('noa setlocal wrapscan') -- Sets the global(!) value +      command('setglobal nowrapscan') +      expected_combination({'wrapscan', 1, '', 1, 0, 'global', 'setglobal'}) + +      command('noa setglobal nowrapscan') +      command('noa setlocal wrapscan') -- Sets the global(!) value +      command('setlocal nowrapscan') +      expected_combination({'wrapscan', 1, 1, '', 0, 'local', 'setlocal'}) + +      command('noa setglobal nowrapscan') +      command('noa setlocal wrapscan') -- Sets the global(!) value +      command('set nowrapscan') +      expected_combination({'wrapscan', 1, 1, 1, 0, 'global', 'set'}) + +      command('noa set nowrapscan') +      command('set wrapscan') +      expected_combination({'wrapscan', 0, 0, 0, 1, 'global', 'set'}) +    end) + +    it('with boolean global-local (to buffer) option', function() +      command('noa setglobal noautoread') +      command('noa setlocal autoread') +      command('setglobal autoread') +      expected_combination({'autoread', 0, '', 0, 1, 'global', 'setglobal'}) + +      command('noa setglobal noautoread') +      command('noa setlocal autoread') +      command('setlocal noautoread') +      expected_combination({'autoread', 1, 1, '', 0, 'local', 'setlocal'}) + +      command('noa setglobal noautoread') +      command('noa setlocal autoread') +      command('set autoread') +      expected_combination({'autoread', 1, 1, 0, 1, 'global', 'set'}) + +      command('noa set noautoread') +      command('set autoread') +      expected_combination({'autoread', 0, 0, 0, 1, 'global', 'set'}) +    end) + +    it('with boolean local (to buffer) option', function() +      command('noa setglobal nocindent') +      command('noa setlocal cindent') +      command('setglobal cindent') +      expected_combination({'cindent', 0, '', 0, 1, 'global', 'setglobal'}) + +      command('noa setglobal nocindent') +      command('noa setlocal cindent') +      command('setlocal nocindent') +      expected_combination({'cindent', 1, 1, '', 0, 'local', 'setlocal'}) + +      command('noa setglobal nocindent') +      command('noa setlocal cindent') +      command('set cindent') +      expected_combination({'cindent', 1, 1, 0, 1, 'global', 'set'}) + +      command('noa set nocindent') +      command('set cindent') +      expected_combination({'cindent', 0, 0, 0, 1, 'global', 'set'}) +    end) + +    it('with boolean local (to window) option', function() +      command('noa setglobal nocursorcolumn') +      command('noa setlocal cursorcolumn') +      command('setglobal cursorcolumn') +      expected_combination({'cursorcolumn', 0, '', 0, 1, 'global', 'setglobal'}) + +      command('noa setglobal nocursorcolumn') +      command('noa setlocal cursorcolumn') +      command('setlocal nocursorcolumn') +      expected_combination({'cursorcolumn', 1, 1, '', 0, 'local', 'setlocal'}) + +      command('noa setglobal nocursorcolumn') +      command('noa setlocal cursorcolumn') +      command('set cursorcolumn') +      expected_combination({'cursorcolumn', 1, 1, 0, 1, 'global', 'set'}) + +      command('noa set nocursorcolumn') +      command('set cursorcolumn') +      expected_combination({'cursorcolumn', 0, 0, 0, 1, 'global', 'set'}) +    end) + +    it('with option value converted internally', function() +      command('noa set backspace=1') +      command('set backspace=2') +      expected_combination(({ +        'backspace', 'indent,eol', 'indent,eol', 'indent,eol', '2', 'global', 'set' +      })) +    end)    end)    describe('with specific option', function() @@ -228,13 +566,13 @@ describe('au OptionSet', function()        expected_empty()        command('setlocal ro') -      expected_combination({'readonly', 0, 1, 'local'}) +      expected_combination({'readonly', 0, 0, '', 1, 'local', 'setlocal'})        command('setglobal ro') -      expected_combination({'readonly', 0, 1, 'global'}) +      expected_combination({'readonly', 0, '', 0, 1, 'global', 'setglobal'})        command('set noro') -      expected_combination({'readonly', 1, 0, 'global'}) +      expected_combination({'readonly', 1, 1, 1, 0, 'global', 'set'})      end)      describe('being set by setbufvar()', function() @@ -249,7 +587,7 @@ describe('au OptionSet', function()          set_hook('backup')          command('call setbufvar(1, "&backup", 1)') -        expected_combination({'backup', 0, 1, 'local'}) +        expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'})        end)        it('should trigger if the current buffer is different from the targetted buffer', function() @@ -259,7 +597,7 @@ describe('au OptionSet', function()          local new_bufnr = buf.get_number(new_buffer)          command('call setbufvar(' .. new_bufnr .. ', "&buftype", "nofile")') -        expected_combination({'buftype', '', 'nofile', 'local', {bufnr = new_bufnr}}) +        expected_combination({'buftype', '', '', '', 'nofile', 'local', 'setlocal', {bufnr = new_bufnr}})        end)      end) @@ -275,7 +613,7 @@ describe('au OptionSet', function()          set_hook('backup')          command('call setwinvar(1, "&backup", 1)') -        expected_combination({'backup', 0, 1, 'local'}) +        expected_combination({'backup', 0, 0, '', 1, 'local', 'setlocal'})        end)        it('should not trigger if the current window is different from the targetted window', function() @@ -295,7 +633,7 @@ describe('au OptionSet', function()          nvim.set_option('autochdir', true)          eq(true, nvim.get_option('autochdir')) -        expected_combination({'autochdir', '0', '1', 'global'}) +        expected_combination({'autochdir', 0, '', 0, 1, 'global', 'setglobal'})        end)        it('should trigger if a number option be set globally', function() @@ -303,7 +641,7 @@ describe('au OptionSet', function()          nvim.set_option('cmdheight', 5)          eq(5, nvim.get_option('cmdheight')) -        expected_combination({'cmdheight', 1, 5, 'global'}) +        expected_combination({'cmdheight', 1, '', 1, 5, 'global', 'setglobal'})        end)        it('should trigger if a string option be set globally', function() @@ -311,7 +649,7 @@ describe('au OptionSet', function()          nvim.set_option('ambiwidth', 'double')          eq('double', nvim.get_option('ambiwidth')) -        expected_combination({'ambiwidth', 'single', 'double', 'global'}) +        expected_combination({'ambiwidth', 'single', '', 'single', 'double', 'global', 'setglobal'})        end)      end)    end) | 
