I have following

function! s:get_visual_selection()
    let [line_start, column_start] = getpos("'<")[1:2]
    let [line_end, column_end] = getpos("'>")[1:2]
    let lines = getline(line_start, line_end)
    if len(lines) == 0
        return ''
    let lines[-1] = lines[-1][: column_end - 2]
    let lines[0] = lines[0][column_start - 1:]
    return join(lines, "\n")

" dependencies - tpope's surround.vim plugin
function! s:singleDoubleQuotesToggler() "{{{
  " select between "" using
  norm va"
  let doubleq_sel = s:get_visual_selection()
  " echo string(doubleq_sel)
  let doubleq_sel_length = strlen(doubleq_sel)
  echo string(doubleq_sel_length)
  exec "norm! \<Esc>"
  if doubleq_sel_length == 0
    " echo string("single")
    norm cs'"
    " echo string('double')
    norm cs"'
endfunction "}}}

nnoremap <silent> GS :call <SID>singleDoubleQuotesToggler()<CR>

problem is that GS work through once and I dont know why



2 Answers 2


For your approach to work, you could define an omap that finds the smallest enclosing pair, and tests the character under the cursor to trigger the mapping from Surround you wish. I remember a plugin that has defined the operator pending mode you'll need. Its approach was quite smart by testing all possible vi{whatever} to find the one with the smallest result. From there, you'll just need to test the character under '<. Alas I can't remember the name of this plugin.

It seems I've implemented an equivalent in lh-brackets (s:outer_blocks()), it doesn't returns the first characters, but the last one. From there, you'll just need to test the character under the closest position, and you'll know which surround mapping you want to execute.

That's the if p[0] =~ '["'']' part that'll be of interest to you. You can assume your s:GetPairs() results in [ ['"', '"'], ["'", "'"] ] -- s:GetPairs() returns the list of brackets pairs registered for the current buffer.

function! s:outer_blocks() abort
  let crt_pairs = copy(s:GetPairs(0))
  call extend(crt_pairs, s:GetPairs(1))
  let matches = {}
  for p in crt_pairs
    if p[0] != p[1] " searchpos doesn't work in that case
      let pos = searchpairpos(p[0], '', p[1], 'cWn', "lh#syntax#is_a_comment('.')")
      call s:Verbose('Testing searchpos(%1) -> %2', p, pos)
    elseif p[0] =~ '["'']'
      " Stuff which can be checked with vi', vi"
      let crt_pos = getpos('.')
      let cleanup = lh#on#exit()
            \.register('call setpos(".", '.string(crt_pos).')')
        let @a = ''
        silent! exe 'normal! "aya'.p[0]
        " 2 chars are to be expected for open and close
        if lh#encoding#strlen(@a) >= lh#encoding#strlen(p[0].p[1])
          " In two steps because it may fail
          " -- it shouldn't though thanks to len(@a) >= 2 * len(open)
          exe 'normal! v'
          silent! exe 'normal! a'.p[0]
          silent! exe "normal! \<esc>"
          let pos = getpos('.')[1:2]
          let pos = [0,0]
        if 0
          " getpos doesn't seem to work...
          let pos = getpos('`>')[1:2]
          if getpos('`<')[1:2] == pos
            let pos = [0,0]
        call s:Verbose('Testing va%1 -> %2 - %3', p[0], pos, @a)
        call cleanup.finalize()
      let pos = searchpos(p[0], 'cWnb', 'lh#syntax#is_a_comment(".")')
      if  pos != [0,0]
        let pos = searchpos(p[1], 'cWn', 'lh#syntax#is_a_comment(".")')
      call s:Verbose('Testing /%1 -> %2', p[0], pos)
    if pos != [0,0]
      let matches[p[0]] = pos
  call s:Verbose('Containing bracket pairs: %1', matches)
  return matches
  • Possibly you're thinking of rhysd/vim-textobj-anyblock? (Although there are probably a number of similar plugins) Edit: gcmt/wildfire.vim also looks close to what you describe.
    – Mass
    Oct 13, 2017 at 5:09

You can use vim-surround. It's easy to add " or '.
For example, ysw" will add " to surround the current word.

  • 2
    If you read the code of in OP's question they already use vim-surround. What they want to do is a bit more complexe than simply adding the surrounding quotes.
    – statox
    Sep 13, 2017 at 7:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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