]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.1.0589: vi: d{motion} and cw work differently than expected v9.1.0589
authorChristian Brabandt <cb@256bit.org>
Mon, 15 Jul 2024 18:51:11 +0000 (20:51 +0200)
committerChristian Brabandt <cb@256bit.org>
Mon, 15 Jul 2024 18:51:11 +0000 (20:51 +0200)
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>
14 files changed:
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

index 2b4bd189ad7567fb0985f3132a9ac28b7d707dfe..52a68c439a09cd60ee06c9515fd01f27ed07e56a 100644 (file)
@@ -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 @@ For inserting text see |insert.txt|.
 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 "dw" deletes all the blanks; use the
 
 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.
index 8b536055a7c852edb1a65c6b240710995b32a756..3e45884be9bf2cccf5857e07c572a39bf4e20ca0 100644 (file)
@@ -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
index cb2ddc39ec70504054fb3578cd4d4d7171aaf1fd..b211dcb8f78bd769fe925408b9b5ad9982c186a6 100644 (file)
@@ -2486,6 +2486,9 @@ A jump table for the options with a short description can be found at |Q_op|.
                                                                *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
index 13dd7a3ea25b87a809213a186eeb08b416ed3a8c..4e096ec12d2a3776093308cab98364074759010d 100644 (file)
@@ -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*
index 3b719eb9c7c9710346cb9e7a51692483186d77b8..26df6f2e589bf0ed57c7315759ce04767fb8eb22 100644 (file)
@@ -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 ~
index 541c8ed1c466d14f379770dd01cfacbd944ca5b8..a929dd8082d95496033e55dc5b7f8d8fd5cb21ad 100644 (file)
@@ -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;
            }
        }
index dcb48d32334261ddf9c6001f1c51940cc496fd99..2de2557fbd1aabc7d1ec1344225cfca18b9736dd 100644 (file)
--- 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;
index e84f7f9adda19398432990cef4acf1c000063b38..fba2672877a7167579a021015170a78aec49071f 100644 (file)
@@ -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<>[]~"
index a455264de7e8fce9ed202211ca8481de72406742..58ccb6abba66f9b69299b2ac840804adfe88da79 100644 (file)
@@ -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
index 1f7d4afa097ecc2f920058e70a917e4bb7a10716..7bfbcd12a1e6b8cf8b6b942e468e64da5cae6328 100644 (file)
@@ -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
index 02f5d7e9007dbb3cbb7d0e2d515d29d87bb79e22..94c98fea11879913f0e4ba5651c9b6ba4d0061df 100644 (file)
@@ -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
 
index 0b618153b1cb0a87f1ed72458de53b1de71946a6..47b9b4703d531db33d6728086c0a1a97fea278be 100644 (file)
@@ -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
index a169c79114ad5c32b0d9ad1b703f62daf406b66b..bcb590d107389607ecd0b266b65f9bf27ef57e4c 100644 (file)
@@ -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')
index e97346dbcb9de66c38f6e9cafbc17c4e9e9571e3..024ed1ac18b426fd174ca58c56deb67645d4df6b 100644 (file)
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    589,
 /**/
     588,
 /**/