diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index ebd7e53bca..748b5ae0a1 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 9.1. Last change: 2025 Sep 26 +*options.txt* For Vim version 9.1. Last change: 2025 Sep 28 VIM REFERENCE MANUAL by Bram Moolenaar @@ -10259,6 +10259,11 @@ A jump table for the options with a short description can be found at |Q_op|. < See 'sidescroll', 'listchars' and |wrap-off|. This option can't be set from a |modeline| when the 'diff' option is on. + If 'nowrap' was set from a |modeline| or in the |sandbox|, '>' is used + as the |lcs-extends| character regardless of the value of the 'list' + and 'listchars' options. This is to prevent malicious code outside + the viewport from going unnoticed. Use `:setlocal nowrap` manually + afterwards to disable this behavior. *'wrapmargin'* *'wm'* 'wrapmargin' 'wm' number (default 0) diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 5447b28c6a..4ad3eb2a4a 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.1. Last change: 2025 Sep 26 +*version9.txt* For Vim version 9.1. Last change: 2025 Sep 28 VIM REFERENCE MANUAL by Bram Moolenaar @@ -41710,6 +41710,9 @@ Options: ~ - 'rulerformat' now supports the |stl-%!| item - use 'smoothscroll' logic for CTRL-F / CTRL-B for pagewise scrolling and CTRL-D / CTRL-U for half-pagewise scrolling +- Setting 'nowrap' in a modeline could cause long lines to be hidden + off-screen. To make this visible, the listchars "extend" suboption is set + to ">" by default, indicating text that extends beyond the window width. Ex commands: ~ - allow to specify a priority when defining a new sign |:sign-define| diff --git a/src/drawline.c b/src/drawline.c index dc68c45c03..40a57e785c 100644 --- a/src/drawline.c +++ b/src/drawline.c @@ -15,6 +15,22 @@ #include "vim.h" +/* + * Get the 'listchars' "extends" characters to use for "wp", or NUL if it + * shouldn't be used. + */ + static int +get_lcs_ext(win_T *wp) +{ + if (wp->w_p_wrap) + // Line never continues beyond the right of the screen with 'wrap'. + return NUL; + if (wp->w_p_wrap_flags & P_INSECURE) + // If 'nowrap' was set from a modeline, forcibly use '>'. + return '>'; + return wp->w_p_list ? wp->w_lcs_chars.ext : NUL; +} + #ifdef FEAT_SYN_HL /* * Advance **color_cols and return TRUE when there are columns to draw. @@ -732,10 +748,7 @@ text_prop_position( // With 'nowrap' add one to show the "extends" character if needed (it // doesn't show if the text just fits). - if (!wp->w_p_wrap - && n_used < *n_extra - && wp->w_lcs_chars.ext != NUL - && wp->w_p_list) + if (n_used < *n_extra && get_lcs_ext(wp) != NUL) ++n_used; // add 1 for NUL, 2 for when '…' is used @@ -3947,12 +3960,10 @@ win_line( } } - // Show "extends" character from 'listchars' if beyond the line end and - // 'list' is set. - if (wp->w_lcs_chars.ext != NUL + // Show "extends" character from 'listchars' if beyond the line end. + int lcs_ext = get_lcs_ext(wp); + if (lcs_ext != NUL && wlv.draw_state == WL_LINE - && wp->w_p_list - && !wp->w_p_wrap #ifdef FEAT_DIFF && wlv.filler_todo <= 0 #endif @@ -3970,7 +3981,7 @@ win_line( #endif )) { - c = wp->w_lcs_chars.ext; + c = lcs_ext; wlv.char_attr = hl_combine_attr(wlv.win_attr, HL_ATTR(HLF_AT)); mb_c = c; if (enc_utf8 && utf_char2len(c) > 1) diff --git a/src/option.c b/src/option.c index 88da9c0995..4a4d21b881 100644 --- a/src/option.c +++ b/src/option.c @@ -3081,6 +3081,7 @@ insecure_flag(int opt_idx, int opt_flags) if (opt_flags & OPT_LOCAL) switch ((int)options[opt_idx].indir) { + case PV_WRAP: return &curwin->w_p_wrap_flags; #ifdef FEAT_STL_OPT case PV_STL: return &curwin->w_p_stl_flags; #endif diff --git a/src/structs.h b/src/structs.h index 981db0e7cf..72838bda2f 100644 --- a/src/structs.h +++ b/src/structs.h @@ -4214,6 +4214,7 @@ struct window_S #define GLOBAL_WO(p) ((char *)(p) + sizeof(winopt_T)) // A few options have local flags for P_INSECURE. + long_u w_p_wrap_flags; // flags for 'wrap' #ifdef FEAT_STL_OPT long_u w_p_stl_flags; // flags for 'statusline' #endif diff --git a/src/testdir/test_modeline.vim b/src/testdir/test_modeline.vim index 1f8686328a..a5762f7f6b 100644 --- a/src/testdir/test_modeline.vim +++ b/src/testdir/test_modeline.vim @@ -361,4 +361,53 @@ func Test_modeline_disable() call assert_equal(2, &sw) endfunc +" If 'nowrap' is set from a modeline, '>' is used forcibly as lcs-extends. +func Test_modeline_nowrap_lcs_extends() + call writefile([ + \ 'aaa', + \ 'bbb', + \ 'ccc evil', + \ 'ddd vim: nowrap', + \ ], 'Xmodeline_nowrap', 'D') + call NewWindow(10, 20) + + setlocal nolist listchars= + edit Xmodeline_nowrap + let expect_insecure = [ + \ 'aaa ', + \ 'bbb ', + \ 'ccc >', + \ 'ddd >', + \ '~ ', + \ ] + call assert_equal(expect_insecure, ScreenLines([1, 5], 20)) + + setlocal nowrap + let expect_secure = [ + \ 'aaa ', + \ 'bbb ', + \ 'ccc ', + \ 'ddd ', + \ '~ ', + \ ] + call assert_equal(expect_secure, ScreenLines([1, 5], 20)) + + setlocal list listchars=extends:+ + let expect_secure = [ + \ 'aaa ', + \ 'bbb ', + \ 'ccc +', + \ 'ddd +', + \ '~ ', + \ ] + call assert_equal(expect_secure, ScreenLines([1, 5], 20)) + + edit Xmodeline_nowrap + call assert_equal(expect_insecure, ScreenLines([1, 5], 20)) + setlocal nowrap + call assert_equal(expect_secure, ScreenLines([1, 5], 20)) + + call CloseWindow() +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/version.c b/src/version.c index 47d461971d..4d03167a73 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 */ +/**/ + 1802, /**/ 1801, /**/