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>
diff --git a/src/normal.c b/src/normal.c
index 541c8ed..a929dd8 100644
--- a/src/normal.c
+++ b/src/normal.c
@@ -6598,8 +6598,12 @@
 		// "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;
 	    }
 	}
diff --git a/src/ops.c b/src/ops.c
index dcb48d3..2de2557 100644
--- a/src/ops.c
+++ b/src/ops.c
@@ -676,6 +676,7 @@
 	    && !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;
diff --git a/src/option.h b/src/option.h
index e84f7f9..fba2672 100644
--- a/src/option.h
+++ b/src/option.h
@@ -212,6 +212,7 @@
 #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 @@
 #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<>[]~"
diff --git a/src/testdir/test_codestyle.vim b/src/testdir/test_codestyle.vim
index a455264..58ccb6a 100644
--- a/src/testdir/test_codestyle.vim
+++ b/src/testdir/test_codestyle.vim
@@ -68,7 +68,8 @@
         && 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 @@
 enddef
 
 
-" vim: shiftwidth=2 sts=2 expandtab
+" vim: shiftwidth=2 sts=2 expandtab nofoldenable
diff --git a/src/testdir/test_cpoptions.vim b/src/testdir/test_cpoptions.vim
index 1f7d4af..7bfbcd1 100644
--- a/src/testdir/test_cpoptions.vim
+++ b/src/testdir/test_cpoptions.vim
@@ -912,4 +912,49 @@
   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
diff --git a/src/testdir/test_options.vim b/src/testdir/test_options.vim
index 02f5d7e..94c98fe 100644
--- a/src/testdir/test_options.vim
+++ b/src/testdir/test_options.vim
@@ -1728,7 +1728,7 @@
     qall
   [CODE]
   if RunVim([], after, '')
-    call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>#{|&/\.;',
+    call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZz$!%*-+<>#{|&/\.;',
           \            'AS'], readfile('X_VIM_POSIX'))
   endif
 
@@ -1738,7 +1738,7 @@
     qall
   [CODE]
   if RunVim([], after, '')
-    call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZ$!%*-+<>;',
+    call assert_equal(['aAbBcCdDeEfFgHiIjJkKlLmMnoOpPqrRsStuvwWxXyZz$!%*-+<>;',
           \            'S'], readfile('X_VIM_POSIX'))
   endif
 
diff --git a/src/testdir/test_quickfix.vim b/src/testdir/test_quickfix.vim
index 0b61815..47b9b47 100644
--- a/src/testdir/test_quickfix.vim
+++ b/src/testdir/test_quickfix.vim
@@ -893,7 +893,7 @@
 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 @@
   cwindow
   silent helpgrep grail
 
-  assert_equal('aABceFs', &cpo)
+  assert_equal('aABceFsz', &cpo)
   &rtp = rtp_save
   cclose
   helpclose
diff --git a/src/testdir/test_vim9_script.vim b/src/testdir/test_vim9_script.vim
index a169c79..bcb590d 100644
--- a/src/testdir/test_vim9_script.vim
+++ b/src/testdir/test_vim9_script.vim
@@ -4007,7 +4007,7 @@
   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 @@
     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')
diff --git a/src/version.c b/src/version.c
index e97346d..024ed1a 100644
--- a/src/version.c
+++ b/src/version.c
@@ -705,6 +705,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    589,
+/**/
     588,
 /**/
     587,