aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/autocmd.txt19
-rw-r--r--runtime/doc/eval.txt9
-rw-r--r--src/nvim/auevents.lua1
-rw-r--r--src/nvim/eval.c13
-rw-r--r--src/nvim/eval.h3
-rw-r--r--src/nvim/fileio.c3
-rw-r--r--src/nvim/option.c93
-rw-r--r--src/nvim/version.c8
-rw-r--r--test/functional/legacy/autocmd_option_spec.lua277
9 files changed, 413 insertions, 13 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt
index a0ed91c95d..38d53249d1 100644
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -258,6 +258,7 @@ Name triggered by ~
|Syntax| when the 'syntax' option has been set
|EncodingChanged| after the 'encoding' option has been changed
|TermChanged| after the value of 'term' has changed
+|OptionSet| after setting any option
Startup and exit
|VimEnter| after doing all the startup stuff
@@ -745,6 +746,24 @@ MenuPopup Just before showing the popup menu (under the
o Operator-pending
i Insert
c Command line
+ *OptionSet*
+OptionSet After setting an option. The pattern is
+ matched against the long option name.
+ The |v:option_old| variable indicates the
+ old option value, |v:option_new| variable
+ indicates the newly set value, the
+ |v:option_type| variable indicates whether
+ it's global or local scoped and |<amatch>|
+ indicates what option has been set.
+
+ Note: It's a bad idea, to reset an option
+ during this autocommand, since this will
+ probably break plugins. You can always use
+ |noa| to prevent triggering this autocommand.
+ Could be used, to check for existence of the
+ 'backupdir' and 'undodir' options and create
+ directories, if they don't exist yet.
+
*QuickFixCmdPre*
QuickFixCmdPre Before a quickfix command is run (|:make|,
|:lmake|, |:grep|, |:lgrep|, |:grepadd|,
diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt
index 7b12d2082f..4ff0636b61 100644
--- a/runtime/doc/eval.txt
+++ b/runtime/doc/eval.txt
@@ -1535,6 +1535,15 @@ v:oldfiles List of file names that is loaded from the |shada| file on
than String this will cause trouble.
{only when compiled with the |+shada| feature}
+ *v:option_new*
+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.
+ *v:option_type*
+v:option_type Scope of the set command. Valid while executing an
+ |OptionSet| autocommand. Can be either "global" or "local"
*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/auevents.lua b/src/nvim/auevents.lua
index 7624dd2303..aa4a8d8332 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -57,6 +57,7 @@ return {
'InsertLeave', -- when leaving Insert mode
'JobActivity', -- when job sent some data
'MenuPopup', -- just before popup menu is displayed
+ 'OptionSet', -- after setting any option
'QuickFixCmdPost', -- after :make, :grep etc.
'QuickFixCmdPre', -- before :make, :grep etc.
'QuitPre', -- before :quit
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index f104098dbf..4a2bf2ac7a 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -373,6 +373,9 @@ static struct vimvar {
{VV_NAME("progpath", VAR_STRING), VV_RO},
{VV_NAME("command_output", VAR_STRING), 0},
{VV_NAME("completed_item", VAR_DICT), VV_RO},
+ {VV_NAME("option_new", VAR_STRING), VV_RO},
+ {VV_NAME("option_old", VAR_STRING), VV_RO},
+ {VV_NAME("option_type", VAR_STRING), VV_RO},
{VV_NAME("msgpack_types", VAR_DICT), VV_RO},
};
@@ -21238,9 +21241,13 @@ void ex_oldfiles(exarg_T *eap)
}
}
-
-
-
+// reset v:option_new, v:option_old and v:option_type
+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_TYPE, NULL, -1);
+}
/*
* Adjust a filename, according to a string of modifiers.
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index 8ccf71068c..19a1bbb083 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -108,6 +108,9 @@ enum {
VV_PROGPATH,
VV_COMMAND_OUTPUT,
VV_COMPLETED_ITEM,
+ VV_OPTION_NEW,
+ VV_OPTION_OLD,
+ VV_OPTION_TYPE,
VV_MSGPACK_TYPES,
VV_LEN, /* number of v: vars */
};
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 11673785fd..bc5b08ef24 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -6407,7 +6407,7 @@ apply_autocmds_group (
* invalid.
*/
if (fname_io == NULL) {
- if (event == EVENT_COLORSCHEME)
+ if (event == EVENT_COLORSCHEME || event == EVENT_OPTIONSET)
autocmd_fname = NULL;
else if (fname != NULL && *fname != NUL)
autocmd_fname = fname;
@@ -6457,6 +6457,7 @@ apply_autocmds_group (
if (event == EVENT_COLORSCHEME
|| event == EVENT_FILETYPE
|| event == EVENT_FUNCUNDEFINED
+ || event == EVENT_OPTIONSET
|| event == EVENT_QUICKFIXCMDPOST
|| event == EVENT_QUICKFIXCMDPRE
|| event == EVENT_REMOTEREPLY
diff --git a/src/nvim/option.c b/src/nvim/option.c
index f5080c7a91..dbcd230186 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -1503,9 +1503,10 @@ do_set (
} else if (opt_idx >= 0) { /* string */
char_u *save_arg = NULL;
char_u *s = NULL;
- char_u *oldval; /* previous value if *varp */
+ char_u *oldval = NULL; // previous value if *varp
char_u *newval;
- char_u *origval;
+ char_u *origval = NULL;
+ char_u *saved_origval = NULL;
unsigned newlen;
int comma;
int bs;
@@ -1772,14 +1773,37 @@ do_set (
/* Set the new value. */
*(char_u **)(varp) = newval;
+ if (!starting && origval != NULL) {
+ // origval may be freed by
+ // did_set_string_option(), make a copy.
+ saved_origval = vim_strsave(origval);
+ }
+
/* Handle side effects, and set the global value for
* ":set" on local options. */
errmsg = did_set_string_option(opt_idx, (char_u **)varp,
new_value_alloced, oldval, errbuf, opt_flags);
- /* If error detected, print the error message. */
- if (errmsg != NULL)
+ // If error detected, print the error message.
+ if (errmsg != NULL) {
+ xfree(saved_origval);
goto skip;
+ }
+
+ if (saved_origval != NULL) {
+ char_u buf_type[7];
+ vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s",
+ (opt_flags & OPT_LOCAL) ? "local" : "global");
+ set_vim_var_string(VV_OPTION_NEW,
+ *(char_u **)varp, -1);
+ set_vim_var_string(VV_OPTION_OLD, saved_origval, -1);
+ set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
+ apply_autocmds(EVENT_OPTIONSET,
+ (char_u *)options[opt_idx].fullname,
+ NULL, false, NULL);
+ reset_v_option_vars();
+ xfree(saved_origval);
+ }
} else {
// key code option(FIXME(tarruda): Show a warning or something
// similar)
@@ -2329,6 +2353,7 @@ set_string_option (
char_u *s;
char_u **varp;
char_u *oldval;
+ char_u *saved_oldval = NULL;
char_u *r = NULL;
if (options[opt_idx].var == NULL) /* don't set hidden option */
@@ -2342,10 +2367,30 @@ set_string_option (
: opt_flags);
oldval = *varp;
*varp = s;
- if ((r = did_set_string_option(opt_idx, varp, TRUE, oldval, NULL,
+
+ if (!starting) {
+ saved_oldval = vim_strsave(oldval);
+ }
+
+ if ((r = did_set_string_option(opt_idx, varp, (int)true, oldval, NULL,
opt_flags)) == NULL)
did_set_option(opt_idx, opt_flags, TRUE);
+ // call autocommand after handling side effects
+ if (saved_oldval != NULL) {
+ char_u buf_type[7];
+ vim_snprintf((char *)buf_type, ARRAY_SIZE(buf_type), "%s",
+ (opt_flags & OPT_LOCAL) ? "local" : "global");
+ set_vim_var_string(VV_OPTION_NEW, *varp, -1);
+ set_vim_var_string(VV_OPTION_OLD, saved_oldval, -1);
+ set_vim_var_string(VV_OPTION_TYPE, buf_type, -1);
+ apply_autocmds(EVENT_OPTIONSET,
+ (char_u *)options[opt_idx].fullname,
+ NULL, false, NULL);
+ reset_v_option_vars();
+ xfree(saved_oldval);
+ }
+
return r;
}
@@ -3814,8 +3859,29 @@ set_bool_option (
* End of handling side effects for bool options.
*/
+ // after handling side effects, call autocommand
+
options[opt_idx].flags |= P_WAS_SET;
+ if (!starting) {
+ char_u buf_old[2];
+ char_u buf_new[2];
+ char_u buf_type[7];
+ vim_snprintf((char *)buf_old, ARRAY_SIZE(buf_old), "%d",
+ old_value ? true: false);
+ vim_snprintf((char *)buf_new, ARRAY_SIZE(buf_new), "%d",
+ value ? true: false);
+ vim_snprintf((char *)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);
+ reset_v_option_vars();
+ }
+
comp_col(); /* in case 'ruler' or 'showcmd' changed */
if (curwin->w_curswant != MAXCOL
&& (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0)
@@ -4187,6 +4253,23 @@ set_num_option (
options[opt_idx].flags |= P_WAS_SET;
+ if (!starting && errmsg == NULL) {
+ char_u buf_old[NUMBUFLEN];
+ char_u buf_new[NUMBUFLEN];
+ char_u buf_type[7];
+ vim_snprintf((char *)buf_old, ARRAY_SIZE(buf_old), "%ld", old_value);
+ vim_snprintf((char *)buf_new, ARRAY_SIZE(buf_new), "%ld", value);
+ vim_snprintf((char *)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);
+ reset_v_option_vars();
+ }
+
comp_col(); /* in case 'columns' or 'ls' changed */
if (curwin->w_curswant != MAXCOL
&& (options[opt_idx].flags & (P_CURSWANT | P_RALL)) != 0)
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 83a47c207d..2b0d6f22f2 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -206,11 +206,11 @@ static int included_patches[] = {
793,
// 792,
791,
- // 790,
- // 789,
+ 790,
+ 789,
// 788 NA
- // 787,
- // 786,
+ 787,
+ 786,
// 785,
784,
// 783 NA
diff --git a/test/functional/legacy/autocmd_option_spec.lua b/test/functional/legacy/autocmd_option_spec.lua
new file mode 100644
index 0000000000..855e9c6271
--- /dev/null
+++ b/test/functional/legacy/autocmd_option_spec.lua
@@ -0,0 +1,277 @@
+local helpers = require('test.functional.helpers')
+local nvim = helpers.meths
+local clear, eq, neq = helpers.clear, helpers.eq, helpers.neq
+local curbuf, buf = helpers.curbuf, helpers.bufmeths
+local source, execute = helpers.source, helpers.execute
+
+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' : {
+ \ 'bufnr' : a:bufnr,
+ \ 'winnr' : a:winnr,
+ \ }
+ \ }
+ call add(g:ret, l:acc)
+ endfu
+ ]])
+end
+
+local function set_hook(pattern)
+ execute(
+ 'au OptionSet '
+ .. pattern ..
+ ' :call AutoCommand(expand("<amatch>"), bufnr("%"), winnr())'
+ )
+end
+
+local function init_var()
+ execute('let g:ret = []')
+end
+
+local function get_result()
+ local ret = nvim.get_var('ret')
+ init_var()
+ return ret
+end
+
+local function expected_table(option, oldval, newval, scope, attr)
+ return {
+ option = option,
+ oldval = tostring(oldval),
+ newval = tostring(newval),
+ scope = scope,
+ attr = attr,
+ }
+end
+
+local function expected_combination(...)
+ local args = {...}
+ local ret = get_result()
+
+ if not (#args == #ret) then
+ local expecteds = {}
+ for _, v in pairs(args) do
+ table.insert(expecteds, expected_table(unpack(v)))
+ end
+ eq(expecteds, ret)
+ return
+ end
+
+ for i, v in ipairs(args) do
+ local attr = v[5]
+ if not attr then
+ -- remove attr entries
+ ret[i].attr = nil
+ else
+ -- remove attr entries which are not required
+ for k in pairs(ret[i].attr) do
+ if not attr[k] then
+ ret[i].attr[k] = nil
+ end
+ end
+ end
+ eq(expected_table(unpack(v)), ret[i])
+ end
+end
+
+local function expected_empty()
+ eq({}, get_result())
+end
+
+local function make_buffer()
+ local old_buf = curbuf()
+ execute('new')
+ local new_buf = curbuf()
+ execute('wincmd p') -- move previous window
+
+ neq(old_buf, new_buf)
+ eq(old_buf, curbuf())
+
+ return new_buf
+end
+
+describe('au OptionSet', function()
+ describe('with any opton (*)', function()
+
+ before_each(function()
+ clear()
+ declare_hook_function()
+ init_var()
+ set_hook('*')
+ end)
+
+ it('should be called in setting number option', function()
+ execute('set nu')
+ expected_combination({'number', 0, 1, 'global'})
+
+ execute('setlocal nonu')
+ expected_combination({'number', 1, 0, 'local'})
+
+ execute('setglobal nonu')
+ expected_combination({'number', 1, 0, 'global'})
+ end)
+
+ it('should be called in setting autoindent option',function()
+ execute('setlocal ai')
+ expected_combination({'autoindent', 0, 1, 'local'})
+
+ execute('setglobal ai')
+ expected_combination({'autoindent', 0, 1, 'global'})
+
+ execute('set noai')
+ expected_combination({'autoindent', 1, 0, 'global'})
+ end)
+
+ it('should be called in inverting global autoindent option',function()
+ execute('set ai!')
+ expected_combination({'autoindent', 0, 1, 'global'})
+ end)
+
+ it('should be called in being unset local autoindent option',function()
+ execute('setlocal ai')
+ expected_combination({'autoindent', 0, 1, 'local'})
+
+ execute('setlocal ai<')
+ expected_combination({'autoindent', 1, 0, 'local'})
+ end)
+
+ it('should be called in setting global list and number option at the same time',function()
+ execute('set list nu')
+ expected_combination(
+ {'list', 0, 1, 'global'},
+ {'number', 0, 1, 'global'}
+ )
+ end)
+
+ it('should not print anything, use :noa', function()
+ execute('noa set nolist nonu')
+ expected_empty()
+ end)
+
+ it('should be called in setting local acd', function()
+ execute('setlocal acd')
+ expected_combination({'autochdir', 0, 1, 'local'})
+ end)
+
+ it('should be called in setting autoread', function()
+ execute('set noar')
+ expected_combination({'autoread', 1, 0, 'global'})
+
+ execute('setlocal ar')
+ expected_combination({'autoread', 0, 1, 'local'})
+ end)
+
+ it('should be called in inverting global autoread', function()
+ execute('setglobal invar')
+ expected_combination({'autoread', 1, 0, 'global'})
+ end)
+
+ it('should be called in setting backspace option through :let', function()
+ execute('let &bs=""')
+ expected_combination({'backspace', 'indent,eol,start', '', 'global'})
+ end)
+
+ describe('being set by setbufvar()', function()
+ it('should not trigger because option name is invalid', function()
+ execute('call setbufvar(1, "&l:bk", 1)')
+ expected_empty()
+ end)
+
+ it('should trigger using correct option name', function()
+ execute('call setbufvar(1, "&backup", 1)')
+ expected_combination({'backup', 0, 1, 'local'})
+ end)
+
+ it('should trigger if the current buffer is different from the targetted buffer', function()
+ local new_buffer = make_buffer()
+ local new_bufnr = buf.get_number(new_buffer)
+
+ execute('call setbufvar(' .. new_bufnr .. ', "&buftype", "nofile")')
+ expected_combination({'buftype', '', 'nofile', 'local', {bufnr = new_bufnr}})
+ end)
+ end)
+ end)
+
+ describe('with specific option', function()
+
+ before_each(function()
+ clear()
+ declare_hook_function()
+ init_var()
+ end)
+
+ it('should be called iff setting readonly', function()
+ set_hook('readonly')
+
+ execute('set nu')
+ expected_empty()
+
+ execute('setlocal ro')
+ expected_combination({'readonly', 0, 1, 'local'})
+
+ execute('setglobal ro')
+ expected_combination({'readonly', 0, 1, 'global'})
+
+ execute('set noro')
+ expected_combination({'readonly', 1, 0, 'global'})
+ end)
+
+ describe('being set by setbufvar()', function()
+ it('should not trigger because option name does not match with backup', function()
+ set_hook('backup')
+
+ execute('call setbufvar(1, "&l:bk", 1)')
+ expected_empty()
+ end)
+
+ it('should trigger, use correct option name backup', function()
+ set_hook('backup')
+
+ execute('call setbufvar(1, "&backup", 1)')
+ expected_combination({'backup', 0, 1, 'local'})
+ end)
+
+ it('should trigger if the current buffer is different from the targetted buffer', function()
+ set_hook('buftype')
+
+ local new_buffer = make_buffer()
+ local new_bufnr = buf.get_number(new_buffer)
+
+ execute('call setbufvar(' .. new_bufnr .. ', "&buftype", "nofile")')
+ expected_combination({'buftype', '', 'nofile', 'local', {bufnr = new_bufnr}})
+ end)
+ end)
+
+ describe('being set by neovim api', function()
+ it('should trigger if a boolean option be set globally', function()
+ set_hook('autochdir')
+
+ nvim.set_option('autochdir', true)
+ eq(true, nvim.get_option('autochdir'))
+ expected_combination({'autochdir', '0', '1', 'global'})
+ end)
+
+ it('should trigger if a number option be set globally', function()
+ set_hook('cmdheight')
+
+ nvim.set_option('cmdheight', 5)
+ eq(5, nvim.get_option('cmdheight'))
+ expected_combination({'cmdheight', 1, 5, 'global'})
+ end)
+
+ it('should trigger if a string option be set globally', function()
+ set_hook('ambiwidth')
+
+ nvim.set_option('ambiwidth', 'double')
+ eq('double', nvim.get_option('ambiwidth'))
+ expected_combination({'ambiwidth', 'single', 'double', 'global'})
+ end)
+ end)
+ end)
+end)