vim-abolish icon indicating copy to clipboard operation
vim-abolish copied to clipboard

[RFC] Coersion for inner and outer textobjects, WORD and visual mode

Open kiryph opened this issue 8 years ago • 6 comments

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 with i/a.
  • cR<coercion char> operates on WORD (iW).
    UPDATE: This could also mean till end of line or whole line and use criW for WORD.

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

kiryph avatar Aug 30 '16 09:08 kiryph

So the main problem with visual mode is there's not really a good map to use. c already means change.

tpope avatar Sep 06 '16 02:09 tpope

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.

kiryph avatar Sep 06 '16 11:09 kiryph

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

kiryph avatar Sep 06 '16 12:09 kiryph

@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.

fictionic avatar Oct 25 '17 00:10 fictionic

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 avatar Sep 08 '18 10:09 kiryph

@kiryph Thanks, that plugin looks promising!

fictionic avatar Sep 08 '18 18:09 fictionic