changeset 35707:878d117d7d6b v9.1.0589

patch 9.1.0589: vi: d{motion} and cw work differently than expected Commit: https://github.com/vim/vim/commit/22105fd1fe0dcfe993b5c04c6ebe017a626116e3 Author: Christian Brabandt <cb@256bit.org> Date: Mon Jul 15 20:51:11 2024 +0200 patch 9.1.0589: vi: d{motion} and cw work differently than expected Problem: vi: d{motion} and cw command work differently than expected Solution: add new cpo-z flag to make the behaviour configurable There are two special vi compatible behaviours (or should I say bugs?): 1): cw behaves differently than dw. That is, because cw is special cased by Vim and is effectively aliased to ce. POSIX behaviour is documented here: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi.html#tag_20_152_13_81 2): d{motion} may make the whole delete operation linewise, if the start and end of the motion are on different lines and there are only blanks before the start and after the end of the motion. Did not find a related POSIX link that requires this behaviour. Both behaviours can be considered inconsistent, but we cannot easily change it, because it would be a backward incompatible change and also incompatible to how classic vi behaved. So let's add the new cpo flag "z", which when not included fixes both behaviours and make them more consistent to what users would expect. This has been requested several times: https://groups.google.com/d/msg/vim_use/aaBqT6ECkA4/ALf4odKzEDgJ https://groups.google.com/d/msg/vim_dev/Dpn3xtUF16I/T6JcOPKN6usJ http://www.reddit.com/r/vim/comments/26nut8/why_does_cw_work_like_ce/ https://groups.google.com/d/msg/vim_use/vunNWLFWfQg/MmJh_ZGaAgAJ https://github.com/vim/vim/issues/4390 So in summary, if you want to have the w motion work more consistent, remove the 'z' from the cpo settings. related: #4390 closes: #15263 Signed-off-by: Christian Brabandt <cb@256bit.org>
author Christian Brabandt <cb@256bit.org>
date Wed, 17 Jul 2024 08:13:52 +0200
parents 512ee93a7241
children 636cd9a906df
files runtime/doc/change.txt runtime/doc/motion.txt runtime/doc/options.txt runtime/doc/tags runtime/doc/version9.txt src/normal.c src/ops.c src/option.h src/testdir/test_codestyle.vim src/testdir/test_cpoptions.vim src/testdir/test_options.vim src/testdir/test_quickfix.vim src/testdir/test_vim9_script.vim src/version.c
diffstat 14 files changed, 85 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/change.txt
+++ b/runtime/doc/change.txt
@@ -1,4 +1,4 @@
-*change.txt*    For Vim version 9.1.  Last change: 2024 Jun 23
+*change.txt*    For Vim version 9.1.  Last change: 2024 Jul 14
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -95,13 +95,14 @@ 1. Deleting text					*deleting* *E470*
 These commands delete text.  You can repeat them with the `.` command
 (except `:d`) and undo them.  Use Visual mode to delete blocks of text.  See
 |registers| for an explanation of registers.
-
+							*d-special*
 An exception for the d{motion} command: If the motion is not linewise, the
 start and end of the motion are not in the same line, and there are only
 blanks before the start and there are no non-blanks after the end of the
 motion, the delete becomes linewise.  This means that the delete also removes
 the line of blanks that you might expect to remain. Use the |o_v| operator to
-force the motion to be characterwise.
+force the motion to be characterwise or remove the "z" flag from 'cpoptions'
+(see |cpo-z|) to disable this peculiarity.
 
 Trying to delete an empty region of text (e.g., "d0" in the first column)
 is an error when 'cpoptions' includes the 'E' flag.
@@ -251,7 +252,7 @@ blank; this is probably a bug, because "
 
 If you prefer "cw" to include the space after a word, use this mapping: >
 	:map cw dwi
-Or use "caw" (see |aw|).
+Alternatively use "caw" (see also |aw| and |cpo-z|).
 
 							*:c* *:ch* *:change*
 :{range}c[hange][!]	Replace lines of text with some different text.
--- a/runtime/doc/motion.txt
+++ b/runtime/doc/motion.txt
@@ -1,4 +1,4 @@
-*motion.txt*    For Vim version 9.1.  Last change: 2023 Dec 27
+*motion.txt*    For Vim version 9.1.  Last change: 2024 Jul 14
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -434,7 +434,7 @@ WORD before the fold.
 
 Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is
 on a non-blank.  This is because "cw" is interpreted as change-word, and a
-word does not include the following white space.
+word does not include the following white space (see also |cw|).
 
 Another special case: When using the "w" motion in combination with an
 operator and the last word moved over is at the end of a line, the end of
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -2486,6 +2486,9 @@ A jump table for the options with a shor
 								*cpo-Z*
 		Z	When using "w!" while the 'readonly' option is set,
 			don't reset 'readonly'.
+								*cpo-z*
+		z	Special casing the "cw" and "d" command (see |cw| and
+			|d-special|).
 								*cpo-!*
 		!	When redoing a filter command, use the last used
 			external command, whatever it was.  Otherwise the last
--- a/runtime/doc/tags
+++ b/runtime/doc/tags
@@ -6672,6 +6672,7 @@ cpo-v	options.txt	/*cpo-v*
 cpo-w	options.txt	/*cpo-w*
 cpo-x	options.txt	/*cpo-x*
 cpo-y	options.txt	/*cpo-y*
+cpo-z	options.txt	/*cpo-z*
 cpo-{	options.txt	/*cpo-{*
 cpp.vim	syntax.txt	/*cpp.vim*
 crash-recovery	recover.txt	/*crash-recovery*
@@ -6734,6 +6735,7 @@ cw	change.txt	/*cw*
 cweb.vim	syntax.txt	/*cweb.vim*
 cynlib.vim	syntax.txt	/*cynlib.vim*
 d	change.txt	/*d*
+d-special	change.txt	/*d-special*
 daB	motion.txt	/*daB*
 daW	motion.txt	/*daW*
 dab	motion.txt	/*dab*
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2024 Jul 12
+*version9.txt*  For Vim version 9.1.  Last change: 2024 Jul 15
 
 
 		  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41586,6 +41586,8 @@ Changed~
 - allow to complete directories from 'cdpath' for |:cd| and similar commands,
   add the "cd_in_path" completion type for e.g. |:command-complete| and
   |getcompletion()|
+- add 'cpoptions' flag "z" |cpo-z|, to disable some (traditional) vi
+  behaviour/inconsistency (see |d-special| and |cw|).
 
 							*added-9.2*
 Added ~
--- a/src/normal.c
+++ b/src/normal.c
@@ -6598,8 +6598,12 @@ nv_wordcmd(cmdarg_T *cap)
 		// "ce" will change until the end of the next word, but "cw"
 		// will change only one character! This is done by setting
 		// flag.
-		cap->oap->inclusive = TRUE;
-		word_end = TRUE;
+		// This can be configured using :set cpo-z
+		if (vim_strchr(p_cpo, CPO_WORD) != NULL)
+		{
+		    cap->oap->inclusive = TRUE;
+		    word_end = TRUE;
+		}
 		flag = TRUE;
 	    }
 	}
--- a/src/ops.c
+++ b/src/ops.c
@@ -676,6 +676,7 @@ op_delete(oparg_T *oap)
 	    && !oap->block_mode
 	    && oap->line_count > 1
 	    && oap->motion_force == NUL
+	    && (vim_strchr(p_cpo, CPO_WORD) != NULL)
 	    && oap->op_type == OP_DELETE)
     {
 	ptr = ml_get(oap->end.lnum) + oap->end.col;
--- a/src/option.h
+++ b/src/option.h
@@ -212,6 +212,7 @@ typedef enum {
 #define CPO_REPLCNT	'X'	// "R" with a count only deletes chars once
 #define CPO_YANK	'y'
 #define CPO_KEEPRO	'Z'	// don't reset 'readonly' on ":w!"
+#define CPO_WORD	'z'	// do not special-case word motions cw and dw
 #define CPO_DOLLAR	'$'
 #define CPO_FILTER	'!'
 #define CPO_MATCH	'%'
@@ -231,9 +232,9 @@ typedef enum {
 #define CPO_SCOLON	';'	// using "," and ";" will skip over char if
 				// cursor would not move
 // default values for Vim, Vi and POSIX
-#define CPO_VIM		"aABceFs"
-#define CPO_VI		"aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;"
-#define CPO_ALL		"aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\\.;"
+#define CPO_VIM		"aABceFsz"
+#define CPO_VI		"aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZz$!%*-+<>;"
+#define CPO_ALL		"aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZz$!%*-+<>#{|&/\\.;"
 
 // characters for p_ww option:
 #define WW_ALL		"bshl<>[]~"
--- a/src/testdir/test_codestyle.vim
+++ b/src/testdir/test_codestyle.vim
@@ -68,7 +68,8 @@ def Test_test_files()
         && fname !~ 'test_listchars.vim'
         && fname !~ 'test_visual.vim'
       cursor(1, 1)
-      var lnum = search(fname =~ "test_regexp_latin" ? '[^á] \t' : ' \t')
+      var skip = 'getline(".") =~ "codestyle: ignore"'
+      var lnum = search(fname =~ "test_regexp_latin" ? '[^á] \t' : ' \t', 'W', 0, 0, skip)
       ReportError('testdir/' .. fname, lnum, 'space before Tab')
     endif
 
@@ -155,4 +156,4 @@ def Test_help_files()
 enddef
 
 
-" vim: shiftwidth=2 sts=2 expandtab
+" vim: shiftwidth=2 sts=2 expandtab nofoldenable
--- a/src/testdir/test_cpoptions.vim
+++ b/src/testdir/test_cpoptions.vim
@@ -912,4 +912,49 @@ func Test_cpo_dot()
   let &cpo = save_cpo
 endfunc
 
+" Test for the 'z' flag in 'cpo' (make cw and dw work similar and avoid
+" inconsistencies, see :h cpo-z)
+func Test_cpo_z()
+  let save_cpo = &cpo
+  new
+  " Test 1: dw behaves differently from cw
+  call setline(1, ['foo bar baz', 'one two three'])
+  call cursor(1, 1)
+  " dw does not delete the whitespace after the word
+  norm! wcwanother
+  set cpo-=z
+  " dw deletes the whitespace after the word
+  call cursor(2, 1)
+  norm! wcwfour
+  call assert_equal(['foo another baz', 'one fourthree'], getline(1, '$'))
+  " Test 2: d{motion} becomes linewise :h d-special
+  %d
+  call setline(1, ['one ', '     bar', '    e	        ', 'zwei'])
+  call cursor(2, 1)
+  set cpo+=z
+  " delete operation becomes linewise
+  call feedkeys("fbd/e\\zs\<cr>", 'tnx')
+  call assert_equal(['one ', 'zwei'], getline(1, '$'))
+  %d
+  call setline(1, ['one ', '     bar', '    e	        ', 'zwei'])
+  call cursor(2, 1)
+  call feedkeys("fbd2w", 'tnx')
+  call assert_equal(['one ', 'zwei'], getline(1, '$'))
+
+  " delete operation does not become line wise
+  set cpo-=z
+  call setline(1, ['one ', '     bar', '    e	        ', 'zwei'])
+  call cursor(2, 1)
+  call feedkeys("fbd/e\\zs\<cr>", 'tnx')
+  call assert_equal(['one ', '     	        ', 'zwei'], getline(1, '$')) " codestyle: ignore
+  %d
+  call setline(1, ['one ', '     bar', '    e	        ', 'zwei'])
+  call cursor(2, 1)
+  call feedkeys("fbd2w", 'tnx')
+  call assert_equal(['one ', '     ', 'zwei'], getline(1, '$'))
+
+  " clean up
+  bw!
+  let &cpo = save_cpo
+endfunc
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -1728,7 +1728,7 @@ func Test_VIM_POSIX()
     qall
   [CODE]
   if RunVim([], after, '')
-    call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\.;',
+    call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZz$!%*-+<>#{|&/\.;',
           \            'AS'], readfile('X_VIM_POSIX'))
   endif
 
@@ -1738,7 +1738,7 @@ func Test_VIM_POSIX()
     qall
   [CODE]
   if RunVim([], after, '')
-    call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;',
+    call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZz$!%*-+<>;',
           \            'S'], readfile('X_VIM_POSIX'))
   endif
 
--- a/src/testdir/test_quickfix.vim
+++ b/src/testdir/test_quickfix.vim
@@ -893,7 +893,7 @@ func Test_helpgrep()
 endfunc
 
 def Test_helpgrep_vim9_restore_cpo()
-  assert_equal('aABceFs', &cpo)
+  assert_equal('aABceFsz', &cpo)
 
   var rtp_save = &rtp
   var dir = 'Xruntime/after'
@@ -905,7 +905,7 @@ def Test_helpgrep_vim9_restore_cpo()
   cwindow
   silent helpgrep grail
 
-  assert_equal('aABceFs', &cpo)
+  assert_equal('aABceFsz', &cpo)
   &rtp = rtp_save
   cclose
   helpclose
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -4007,7 +4007,7 @@ def Test_restoring_cpo()
   edit XanotherScript
   so %
   assert_equal('aABceFsMny>', &cpo)
-  assert_equal('aABceFs', g:cpoval)
+  assert_equal('aABceFsz', g:cpoval)
   :1del
   setline(1, 'let g:cpoval = &cpo')
   w
@@ -4048,10 +4048,10 @@ def Test_restoring_cpo()
     exe "silent !" .. cmd
 
     assert_equal([
-        'before: aABceFs',
-        'after: aABceFsM',
-        'later: aABceFsM',
-        'vim9: aABceFs'], readfile('Xrporesult'))
+        'before: aABceFsz',
+        'after: aABceFszM',
+        'later: aABceFszM',
+        'vim9: aABceFsz'], readfile('Xrporesult'))
 
     $HOME = save_HOME
     delete('Xrporesult')
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    589,
+/**/
     588,
 /**/
     587,