From 4397227119e3ebaa879552dcbcb18338d18f4850 Mon Sep 17 00:00:00 2001 From: Christian Brabandt Date: Mon, 18 May 2026 18:59:03 +0000 Subject: [PATCH] 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 Signed-off-by: Christian Brabandt --- runtime/doc/options.txt | 7 ++++- src/option.c | 20 ++++++++++---- src/testdir/test_modeline.vim | 52 +++++++++++++++++++++++++++++++++++ src/version.c | 2 ++ 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 2ba59824f6..b7d74e9f8c 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 31f84c98d1..d15cc3f3c5 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 cb489ee15f..c436cbc270 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 f348ffc9ef..4ce6175650 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, /**/