Commit: patch 9.2.0499: modeline: allow to disable modelines with modelinestrict

0 views
Skip to first unread message

Christian Brabandt

unread,
May 18, 2026, 3:15:16 PM (10 hours ago) May 18
to vim...@googlegroups.com
patch 9.2.0499: modeline: allow to disable modelines with modelinestrict

Commit: https://github.com/vim/vim/commit/4397227119e3ebaa879552dcbcb18338d18f4850
Author: Christian Brabandt <c...@256bit.org>
Date: Mon May 18 18:59:03 2026 +0000

patch 9.2.0499: modeline: allow to disable modelines with modelinestrict

Problem: Cannot disable modeline processing when loading a file
(Mao-Yining, after v9.2.0350)
Solution: Allow to disable modeline processing even when
'modelienstrict' is in effect.

fixes: #20103
closes: #20229

Signed-off-by: Christian Brabandt <c...@256bit.org>
Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 2ba59824f..b7d74e9f8 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt* For Vim version 9.2. Last change: 2026 May 17
+*options.txt* For Vim version 9.2. Last change: 2026 May 18


VIM REFERENCE MANUAL by Bram Moolenaar
@@ -6239,6 +6239,11 @@ A jump table for the options with a short description can be found at |Q_op|.
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.

+ As an exception, `set nomodeline` is honored from within a modeline
+ even when 'modelinestrict' is on. Other forms (`set modeline=0`,
+ `set modeline!`, `set invmodeline`) are still silently ignored.
+ This lets a file disable further modeline processing for itself.
+
The behaviour of 'modeline', 'modelinestrict' and 'modelineexpr' is
as follows:

diff --git a/src/option.c b/src/option.c
index 31f84c98d..d15cc3f3c 100644
--- a/src/option.c
+++ b/src/option.c
@@ -1589,7 +1589,12 @@ is_modeline_whitelisted(char *name)
* if it can be changed.
*/
static int
-validate_opt_idx(int opt_idx, int opt_flags, long_u flags, char **errmsg)
+validate_opt_idx(
+ int opt_idx,
+ int opt_flags,
+ long_u flags,
+ char **errmsg,
+ set_prefix_T prefix)
{
// Skip all options that are not window-local (used when showing
// an already loaded buffer in a window).
@@ -1617,9 +1622,14 @@ validate_opt_idx(int opt_idx, int opt_flags, long_u flags, char **errmsg)
}
// When 'modelinestrict' is on, only whitelisted options may be
// set from a modeline. Silently skip others.
- if (p_mlstr && opt_idx >= 0
- && !is_modeline_whitelisted(options[opt_idx].fullname))
- return FAIL;
+ if (p_mlstr && opt_idx >= 0)
+ {
+ // special case: allow disabling modeline
+ if (options[opt_idx].indir == PV_ML && prefix == PREFIX_NO)
+ return OK;
+ else if (!is_modeline_whitelisted(options[opt_idx].fullname))
+ return FAIL;
+ }
#ifdef FEAT_DIFF
// In diff mode some options are overruled. This avoids that
// 'foldmethod' becomes "marker" instead of "diff" and that
@@ -2864,7 +2874,7 @@ do_set_option(
}

// Make sure the option value can be changed.
- if (validate_opt_idx(opt_idx, opt_flags, flags, &errmsg) == FAIL)
+ if (validate_opt_idx(opt_idx, opt_flags, flags, &errmsg, prefix) == FAIL)
goto skip;

int cp_val = p_cp;
diff --git a/src/testdir/test_modeline.vim b/src/testdir/test_modeline.vim
index cb489ee15..c436cbc27 100644
--- a/src/testdir/test_modeline.vim
+++ b/src/testdir/test_modeline.vim
@@ -666,4 +666,56 @@ func Test_modeline_strict_cannot_be_set_from_modeline()
let &modeline = modeline
endfunc

+func Test_modeline_nomodeline_with_modelinestrict()
+ let modeline = &modeline
+ let modelinestrict = &modelinestrict
+ let modelines = &modelines
+ set modelinestrict modeline modelines=5
+ let ml_before = &g:modeline
+
+ call writefile(['# vim: set nomodeline:', 'line2'], 'Xnoml', 'D')
+ split Xnoml
+ call assert_equal(0, &l:modeline, 'b_p_ml should be off')
+ call assert_equal(ml_before, &g:modeline, 'global p_ml must not change')
+ bwipe!
+
+ " A fresh buffer must still inherit the unchanged global default
+ new
+ call assert_equal(ml_before, &l:modeline,
+ \ 'new buffer should inherit unchanged global')
+ bwipe!
+
+ let &modeline = modeline
+ let &modelinestrict = modelinestrict
+ let &modelines = modelines
+endfunc
+
+func Test_modeline_nomodeline_skips_trailing_modelines()
+ let modeline = &modeline
+ let modelinestrict = &modelinestrict
+ let ts_save = &ts
+ set modeline modelinestrict ts=8
+
+ " Line 1 disables modelines; the trailing modeline must therefore
+ " never execute even though 'tabstop' is whitelisted.
+ call writefile([
+ \ '# vim: set nomodeline :',
+ \ 'middle line 1',
+ \ 'middle line 2',
+ \ 'middle line 3',
+ \ '# vim: set ts=99 :',
+ \ ], 'Xmodeline_disable_top', 'D')
+ split Xmodeline_disable_top
+
+ call assert_equal(0, &l:modeline,
+ \ 'top modeline must have disabled b_p_ml')
+ call assert_equal(8, &ts,
+ \ 'trailing modeline must not have run after nomodeline')
+
+ bwipe!
+ let &modeline = modeline
+ let &modelinestrict = modelinestrict
+ let &ts = ts_save
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/version.c b/src/version.c
index f348ffc9e..4ce617565 100644
--- a/src/version.c
+++ b/src/version.c
@@ -729,6 +729,8 @@ static char *(features[]) =

static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 499,
/**/
498,
/**/
Reply all
Reply to author
Forward
0 new messages