From 23104bbfcc051e10c66cdb3c16096db7d52a7614 Mon Sep 17 00:00:00 2001 From: Tuure Piitulainen Date: Tue, 4 Nov 2025 23:35:25 +0200 Subject: [PATCH 1/4] Fix `cw` edge cases when cursor is on a space Resolves https://github.com/chaoren/vim-wordmotion/issues/84 Resolves half of https://github.com/chaoren/vim-wordmotion/issues/69 Additionally, when cursor is at a "space-like", such as the underscore in a snake case word: foo_bar ^ Fixes that it should only remove the underscore and not the word after. --- autoload/wordmotion.vim | 22 ++++++++++++---------- tests/cw.vader | 25 ++++++++++++++++++++----- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/autoload/wordmotion.vim b/autoload/wordmotion.vim index 9db0333..249197c 100644 --- a/autoload/wordmotion.vim +++ b/autoload/wordmotion.vim @@ -87,10 +87,14 @@ function wordmotion#motion(count, mode, flags, uppercase, extra, ...) set cpoptions+=c let l:flags = a:flags + let l:s = a:uppercase ? s:us : s:s + " cw special case (see :help cw) if a:mode == 'o' && v:operator == 'c' && l:flags == '' - " special case (see :help cw) - let l:flags = 'e' + let l:cursor_on_s = matchstr(getline('.'), '\%' . col('.') . 'c' . l:s) != '' + if !l:cursor_on_s + let l:flags = 'e' + endif let l:cw = 1 else let l:cw = 0 @@ -114,7 +118,7 @@ function wordmotion#motion(count, mode, flags, uppercase, extra, ...) let l:pos = getpos('.') let l:count = a:count - if l:cw + if l:cw && !l:cursor_on_s " cw on the last character of a word will match the cursor position call search('\m'.l:pattern, l:flags.'cW') let l:count -= 1 @@ -124,19 +128,17 @@ function wordmotion#motion(count, mode, flags, uppercase, extra, ...) let l:count -= 1 endwhile - " dw at the end of a line should not consume the newline or leading white - " space on the next line - let l:is_dw = a:mode == 'o' && v:operator == 'd' && l:flags == '' + " operator-pending w at the end of a line should not include the newline or leading + " white space on the next line let l:next_line = l:pos[1] < getpos('.')[1] - if l:is_dw && l:next_line - let l:s = a:uppercase ? s:us : s:s + if a:mode == 'o' && l:flags == '' && l:next_line " newline, leading whitespace, cursor if search('\m\n\%('.l:s.'\)*\%#', 'bW') != 0 - let l:dwpos = getpos('.') + let l:wpos = getpos('.') " need to make range inclusive call setpos('.', l:pos) normal! v - call setpos('.', l:dwpos) + call setpos('.', l:wpos) endif endif diff --git a/tests/cw.vader b/tests/cw.vader index 06d4ec1..bb944b5 100644 --- a/tests/cw.vader +++ b/tests/cw.vader @@ -262,18 +262,33 @@ Then (Assert that cursor is at z): Expect ("b c\nd e" changed to z): a z f +Given (Snake case word): + foo_bar + +Execute (cw on underscore): + normal lllcw + AssertEqual 3, col('.') + +Expect (Only underscore deleted): + foobar + Given (Leading spaces): foo + bar -Execute (FIXME: cw): - normal cw +Execute (cw): + normal jcw AssertEqual 1, col('.') Expect (Leading spaces deleted): - foo + foo + bar -Execute (reference cw): - normal! cw +Given (Leading spaces with only 1 line): + foo + +Execute (cw at the first line and col of the file): + normal cw AssertEqual 1, col('.') Expect (Leading spaces deleted): From 40dc6a2249c35c061b71d76d8c1482f0ba40e6fb Mon Sep 17 00:00:00 2001 From: Tuure Piitulainen Date: Wed, 5 Nov 2025 00:28:13 +0200 Subject: [PATCH 2/4] Honor `cpoptions_z` for `cw` Resolves the rest of https://github.com/chaoren/vim-wordmotion/issues/69 To have `cw` work similarly to `dw` and delete the spaces after a word, set this option: set cpoptions-=z (Vim) set cpoptions-=_ (Neovim) --- autoload/wordmotion.vim | 4 +++- tests/cpoptions.vader | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/autoload/wordmotion.vim b/autoload/wordmotion.vim index 249197c..8910672 100644 --- a/autoload/wordmotion.vim +++ b/autoload/wordmotion.vim @@ -90,7 +90,9 @@ function wordmotion#motion(count, mode, flags, uppercase, extra, ...) let l:s = a:uppercase ? s:us : s:s " cw special case (see :help cw) - if a:mode == 'o' && v:operator == 'c' && l:flags == '' + let l:cpo_z = has('nvim') ? stridx(l:cpo, '_') : stridx(l:cpo, 'z') + + if l:cpo_z != -1 && a:mode == 'o' && v:operator == 'c' && l:flags == '' let l:cursor_on_s = matchstr(getline('.'), '\%' . col('.') . 'c' . l:s) != '' if !l:cursor_on_s let l:flags = 'e' diff --git a/tests/cpoptions.vader b/tests/cpoptions.vader index 4bc3683..ce491bc 100644 --- a/tests/cpoptions.vader +++ b/tests/cpoptions.vader @@ -28,6 +28,17 @@ Execute (w with cpo-<): normal w AssertEqual 5, col('.') +Execute (cw without cpo-z): + if has('nvim') + set cpoptions-=_ + else + set cpoptions-=z + endif + normal cw + +Expect (Delete the space): + bar + Given (Leading spaces): foo From a82ad9a28c1669225d2aa65438ba16afd67c6b6b Mon Sep 17 00:00:00 2001 From: Tuure Piitulainen Date: Wed, 5 Nov 2025 20:48:08 +0200 Subject: [PATCH 3/4] Refactor to make special `cw` case easier to understand When cpo-z was not supported, we called it l:cw and did it unconditionally on cw command. Now we call it l:cw_special_case to differentiate, and only have to check if cursor is on whitespace in one place. --- autoload/wordmotion.vim | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/autoload/wordmotion.vim b/autoload/wordmotion.vim index 8910672..7798ec3 100644 --- a/autoload/wordmotion.vim +++ b/autoload/wordmotion.vim @@ -89,17 +89,17 @@ function wordmotion#motion(count, mode, flags, uppercase, extra, ...) let l:flags = a:flags let l:s = a:uppercase ? s:us : s:s - " cw special case (see :help cw) + " cw special case + " see :help cw, :help cpo-z (vim), :help cpo-_ (neovim) let l:cpo_z = has('nvim') ? stridx(l:cpo, '_') : stridx(l:cpo, 'z') + let l:cw_special_case = 0 if l:cpo_z != -1 && a:mode == 'o' && v:operator == 'c' && l:flags == '' let l:cursor_on_s = matchstr(getline('.'), '\%' . col('.') . 'c' . l:s) != '' if !l:cursor_on_s let l:flags = 'e' + let l:cw_special_case = 1 endif - let l:cw = 1 - else - let l:cw = 0 endif if a:mode == 'x' @@ -120,7 +120,7 @@ function wordmotion#motion(count, mode, flags, uppercase, extra, ...) let l:pos = getpos('.') let l:count = a:count - if l:cw && !l:cursor_on_s + if l:cw_special_case " cw on the last character of a word will match the cursor position call search('\m'.l:pattern, l:flags.'cW') let l:count -= 1 From d81a78b8fc3863a30235f928428cb3d6c440b898 Mon Sep 17 00:00:00 2001 From: Tuure Piitulainen Date: Sat, 8 Nov 2025 10:11:45 +0200 Subject: [PATCH 4/4] Simplify check for if cursor is on a space --- autoload/wordmotion.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autoload/wordmotion.vim b/autoload/wordmotion.vim index 7798ec3..3de792b 100644 --- a/autoload/wordmotion.vim +++ b/autoload/wordmotion.vim @@ -95,7 +95,7 @@ function wordmotion#motion(count, mode, flags, uppercase, extra, ...) let l:cw_special_case = 0 if l:cpo_z != -1 && a:mode == 'o' && v:operator == 'c' && l:flags == '' - let l:cursor_on_s = matchstr(getline('.'), '\%' . col('.') . 'c' . l:s) != '' + let l:cursor_on_s = getline('.') =~# '\%.c' . l:s if !l:cursor_on_s let l:flags = 'e' let l:cw_special_case = 1