vim-abolish
vim-abolish copied to clipboard
[RFC] Coersion for inner and outer textobjects, WORD and visual mode
Currently coercion works always for the textobject iw
. This is as far as I can see hardcoded into the s:coerce
function as norm! yiw; let word = @@
I would like to have a more flexible way of deciding what coercion operates on:
-
cr<coercion char>
invoked in visual mode operates on the visual selection.
This could also allow the user to coerce non-reversible characters such as spaces, dots, dashes. -
crip<coercion char>
,crap<coercion char>
textobjects starting withi/a
. -
cR<coercion char>
operates onWORD
(iW
).
UPDATE: This could also mean till end of line or whole line and usecriW
forWORD
.
I have one particular example in mind for the visual mode operation. Consider this file:
"filename with spaces.txt"
I want to change the spaces within the double quotes to underscores. This feels like a task for abolish-coercion. I'd like to get following file with these commands vi"
and cr_
"filename_with_spaces.txt"
I have implemented a first try for the visual mode operation, however, it does not work:
diff --git a/plugin/abolish.vim b/plugin/abolish.vim
index ea9c80f..f98c83a 100644
--- a/plugin/abolish.vim
+++ b/plugin/abolish.vim
@@ -572,6 +572,53 @@ call extend(Abolish.Coercions, {
\ "function missing": s:function("s:unknown_coercion")
\}, "keep")
+function! s:get_visual_selection()
+ let [lnum1, col1] = getpos("'<")[1:2]
+ let [lnum2, col2] = getpos("'>")[1:2]
+ let lines = getline(lnum1, lnum2)
+ let lines[-1] = lines[-1][: col2 - (&selection == 'inclusive' ? 1 : 2)]
+ let lines[0] = lines[0][col1 - 1:]
+ return join(lines, "\n")
+endfunction
+
+function! s:vcoerce(transformation)
+ " TODO: Are counts correctly considered? What are they for anyway?
+ " TODO: Does dot-repeat make sense?
+ " TODO: Cursor position as expected?
+ let clipboard = &clipboard
+ try
+ set clipboard=
+ let regbody = getreg('"')
+ let regtype = getregtype('"')
+ let c = v:count1
+ while c > 0
+ let c -= 1
+ " norm! yiw
+ let word = s:get_visual_selection()
+ let @@ = s:send(g:Abolish.Coercions,a:transformation,word)
+ if !exists('begin')
+ let begin = getpos("'[")
+ endif
+ if word !=# @@
+ let changed = 1
+ " norm! viwpw
+ norm! gvp
+ echom "paste new stuff"
+ " else
+ " norm! w
+ endif
+ endwhile
+ call setreg('"',regbody,regtype)
+ call setpos("'[",begin)
+ call setpos(".",begin)
+ if exists("changed")
+ silent! call repeat#set("\<Plug>Coerce".a:transformation)
+ endif
+ finally
+ let &clipboard = clipboard
+ endtry
+endfunction
+
function! s:coerce(transformation)
let clipboard = &clipboard
try
@@ -606,11 +653,13 @@ function! s:coerce(transformation)
endfunction
nnoremap <silent> <Plug>Coerce :<C-U>call <SID>coerce(nr2char(getchar()))<CR>
+xnoremap <silent> <Plug>VCoerce :<C-U>call <SID>vcoerce(nr2char(getchar()))<CR>
" }}}1
if !exists("g:abolish_no_mappings") || ! g:abolish_no_mappings
nmap cr <Plug>Coerce
+ xmap cr <Plug>VCoerce
endif
command! -nargs=+ -bang -bar -range=0 -complete=custom,s:Complete Abolish
So the main problem with visual mode is there's not really a good map to use. c
already means change.
You are right. cr
is a no go in visual mode.
I have checked :h visual-index
. I would consider following: v_gC
, v_R
or v_zr
. v_gC
would be my favorite after a short recap of the options but I am not entirely convinced:
I know commentary.vim uses already v_gc
but, as far as I see, not gC
and it could also be used for normal mode.
A consisting mapping between modes is for me a strong argument. Keeping both mappings seems to me not a good idea since there are not many free good mappings available anymore and this could make existing user unhappy. I dislike the combination of lowercase and uppercase letters.
Catching up the idea of surround.vim which uses S
in visual mode and in normal mode ys
, one could use R
for visual mode and cr
for normal mode. However, R
might be used by vim itself in the future as documented in v_R
.
zr
is also not perfect. It would be inconsistent between normal and visual mode and the z
-namespace is often linked to folding.
I've noticed that replacing spaces by the chosen character was missing in the function snakecase. This was of course not yet necessary.
The following diff works and defines n_cR
for WORD and v_R
visual mode:
diff --git a/plugin/abolish.vim b/plugin/abolish.vim
index ea9c80f..4d27a09 100644
--- a/plugin/abolish.vim
+++ b/plugin/abolish.vim
@@ -122,6 +122,7 @@ function! s:snakecase(word)
let word = substitute(word,'\(\u\+\)\(\u\l\)','\1_\2','g')
let word = substitute(word,'\(\l\|\d\)\(\u\)','\1_\2','g')
let word = substitute(word,'[.-]','_','g')
+ let word = substitute(word,' ','_','g')
let word = tolower(word)
return word
endfunction
@@ -572,6 +573,81 @@ call extend(Abolish.Coercions, {
\ "function missing": s:function("s:unknown_coercion")
\}, "keep")
+function! s:get_visual_selection()
+ let [lnum1, col1] = getpos("'<")[1:2]
+ let [lnum2, col2] = getpos("'>")[1:2]
+ let lines = getline(lnum1, lnum2)
+ let lines[-1] = lines[-1][: col2 - (&selection == 'inclusive' ? 1 : 2)]
+ let lines[0] = lines[0][col1 - 1:]
+ return join(lines, "\n")
+endfunction
+
+function! s:vcoerce(transformation)
+ " TODO: Are counts correctly considered? What are they for anyway?
+ " TODO: Does dot-repeat make sense?
+ " TODO: Cursor position as expected?
+ let clipboard = &clipboard
+ try
+ set clipboard=
+ let regbody = getreg('"')
+ let regtype = getregtype('"')
+ let c = v:count1
+ while c > 0
+ let c -= 1
+ let word = s:get_visual_selection()
+ let @@ = s:send(g:Abolish.Coercions,a:transformation,word)
+ if !exists('begin')
+ let begin = getpos("'[")
+ endif
+ if word !=# @@
+ let changed = 1
+ norm! gvp
+ endif
+ endwhile
+ call setreg('"',regbody,regtype)
+ call setpos("'[",begin)
+ call setpos(".",begin)
+ if exists("changed")
+ silent! call repeat#set("\<Plug>Coerce".a:transformation)
+ endif
+ finally
+ let &clipboard = clipboard
+ endtry
+endfunction
+
+function! s:coerceWORD(transformation)
+ let clipboard = &clipboard
+ try
+ set clipboard=
+ let regbody = getreg('"')
+ let regtype = getregtype('"')
+ let c = v:count1
+ while c > 0
+ let c -= 1
+ norm! yiW
+ let word = @@
+ let @@ = s:send(g:Abolish.Coercions,a:transformation,word)
+ if !exists('begin')
+ let begin = getpos("'[")
+ endif
+ if word !=# @@
+ let changed = 1
+ norm! viWpW
+ else
+ norm! W
+ endif
+ endwhile
+ call setreg('"',regbody,regtype)
+ call setpos("'[",begin)
+ call setpos(".",begin)
+ if exists("changed")
+ silent! call repeat#set("\<Plug>Coerce".a:transformation)
+ endif
+ finally
+ let &clipboard = clipboard
+ endtry
+endfunction
+
function! s:coerce(transformation)
let clipboard = &clipboard
try
@@ -606,11 +682,17 @@ function! s:coerce(transformation)
endfunction
nnoremap <silent> <Plug>Coerce :<C-U>call <SID>coerce(nr2char(getchar()))<CR>
+nnoremap <silent> <Plug>CoerceWORD :<C-U>call <SID>coerceWORD(nr2char(getchar()))<CR>
+xnoremap <silent> <Plug>VCoerce :<C-U>call <SID>vcoerce(nr2char(getchar()))<CR>
" }}}1
if !exists("g:abolish_no_mappings") || ! g:abolish_no_mappings
nmap cr <Plug>Coerce
+ nmap cR <Plug>CoerceWORD
+ xmap R <Plug>VCoerce
+ " TODO: omap cr operator pending for i and a
+ " TODO: nmap crr coerce line
endif
command! -nargs=+ -bang -bar -range=0 -complete=custom,s:Complete Abolish
@tpope Have you given a reason why you don't want to implement Coerce as an operator (accepting a motion as argument)? I feel like it would be more your style at least, and it would make all the coersions reversible.
Is the fact that there is no clear choice for a visual mode mapping what's holding this up? You could let the user pick it themself. I'd probably just use <leader>cr
personally.
Alternative Plugin vim-caser
https://github.com/arthurxavierx/vim-caser
There is a new plugin called vim-caser
which supports
-
{VISUAL}
coerce - coerce
{motion/text object}
As a drop-in replacement for the mapping cr
you can add to your vimrc
let g:caser_prefix='cr'
The mapping requires always a text object (iw
), a motion, or a visual selection.
I personally would like to have a linewise operation with crr{char}
as well.
I am also wondering whether support for {operator}cr{char}
, e.g. to adress conveniently a dot-cased word numpy.pi+1.0
with ycr.
, would be a useful addition.
@kiryph Thanks, that plugin looks promising!