0

I want to map an operator-pending key combination to erase contents of any embedded combination of (text), [more text], <other text> or {misc text} by placing my cursor inside and issuing the c+<mapped_key> o d+<mapped_key> operator-pending mode cmds in buffer.

Until now I have always used (in ~/.vimrc)

onoremap p i(

But that only concerns () and their embeddings, such that if I place my cursor on "more" inside (text [more te <001 >xt] misc text), issuing dp will not only delete "more te <001 >xt", which is what I would like, but (as expected) everything between ( and ). Having:

onoremap p( i(
onoremap p[ i[
onoremap p{ i{
onoremap p< i<    

is not practical. I would like one mapping, NOT four different ones.

Is there a way to onoremap p <RHS> to ANY occurence of (), [], <> or {} and their embeddings ?

EDIT: Please test with something such as the following line of plain text:

dd xsd  sdl(sddi{dss[ asd]aa }e9<888>n 0:)"wdwww
  • 2
    github.com/andymass/vim-matchup has di% for this. there are a few other plugins which have something similar – Mass Apr 10 at 16:56
  • @Mass: Thanks ! Just read the README.rd. Unless I need new glasses, deleting inside cursor-position defined brackets is not mentioned anywhere. The vim-matchup plugin looks great, but it's also way bigger than what I'd like in my vim-runtime. – Cbhihe Apr 10 at 17:08
  • 1
    @Cbhihe My solution would always select the brackets after the cursor. I would have edited it to first look to see if it can select the surrounding brackets, but that would be really really complicated. I'll undelete it if I can come up with a good solution – DJMcMayhem Apr 10 at 17:38
1

warning

!!!! Dangerous cmd when used with cursor outside visible set of bracketing characters (), <>, [] or {}, because it will happily wrap around end-of-lines and greedily execute on the nearest set of unrelated opening and closing orphan bracketing symbols.

implemention

It's a bit complicated, read comment as explanation.

" objs  : any valid text object without i or a, eg: '{[(<'
" ai    : 'a' or 'i'
" force : start visual mode even if no valid text object found
"
" visually select minimal textobject
function! SelectMinTextObject(objs, ai, force)
  " init data
  let [minObj, minObjStart, minObjEnd] = ['', [], []]

  " split objs into list, loop it
  for obj in split(a:objs, '\zs')

    " get textobject range, continue if it's blank
    let [startPos, endPos] = GetTextObjectPos(a:ai . obj)
    if startPos == [] | continue | endif

    " updat data if it's empty or current text object is smaller
    if minObj ==# '' || minObjStart[1] < startPos[1] ||
          \ minObjStart[1] == startPos[1] && minObjStart[2] < startPos[2]
      let [minObj, minObjStart, minObjEnd] = [obj, startPos, endPos]
    endif

  endfor

  " return if no valid obj found
  if minObj ==# ''
    if a:force
      normal! v
    endif
    return 0
  endif

  " visually select textobject.
  execute 'normal! v'. a:ai . minObj

  return 1
endfunction

" get text object range as [startPos, endPos], pos format is the same as
" getpos(), except it returns [[],[]] if text object is blank
function! GetTextObjectPos(obj)
  try
    " record cursor, @a, '[, ']
    let [ cursorPos, regText, regType, yankPos0, yankPos1 ] =
          \ [ getcurpos(), getreg('"'), getregtype('"'),
          \   getpos("'["), getpos("']")]
    " clear register. it's necessary because "ay.. won't fill a if .. is blank
    let @a = ''
    " copy text object into @a if it's not blank
    exec 'normal! "ay' . a:obj
    " return blank if nothing copied, otherwise return start and end of copied
    " area
    return @a ==# '' ? [[],[]] : [getpos("'["), getpos("']")]
  finally
    " restore cursor, @a, '[, ']
    call setpos('.', cursorPos) | call setreg('a', regText, regType)
    call setpos("'[", yankPos0) | call setpos("']", yankPos1)
  endtry
endfunction

" you can't implement omap with vmap, because miminal v area is 1 character,
" miminal o area is 0 character
vnoremap ip <esc>:call SelectMinTextObject('({[<', 'i', 1)<cr>
vnoremap ap <esc>:call SelectMinTextObject('({[<', 'a', 1)<cr>
onoremap ip :call SelectMinTextObject('({[<', 'i', 0)<cr>
onoremap ap :call SelectMinTextObject('({[<', 'a', 0)<cr>

" dd xsd  sdl(sddi{dss[ asd]aa }e9<888>n 0:)"wdwww

known issue

vip will discard whatever you visually selected before. It's inconsistent with viw.

  • 1
    +1: This is grand. Thanks much, really. It is especially nice to have implemented the "a" and "i" versions of the same functionality. Allow me two comments: (i) the line if startPos == [] | continue | endif did not work for me in vim 8.1. I just unfolded the if block on 3 separate lines. (ii) !!!! Dangerous cmd when used with cursor outside visible set of bracketing characters (), <>, [] or {}, because it will happily wrap around end-of-lines and greedily execute on the nearest set of unrelated opening and closing orphan bracketing symbols. Add warning at the top of yr excellent answer ? – Cbhihe Apr 11 at 7:02
  • I tried vim8, tag v8.1.1142, coundn't recreate (i). – dedowsdi Apr 11 at 7:56
  • I am on 8.1.1073 as per Arch linux latest extra repo update. Stranger things happen. ;-P – Cbhihe Apr 11 at 8:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.