dotar/SpaceVim.d/autoload/next-textobject.vim

137 lines
4.8 KiB
VimL
Raw Normal View History

2020-02-10 10:56:29 +08:00
" Stolen from Steve Losh
" https://github.com/sjl/dotfiles/blob/master/vim/vimrc#L1380
"
" Motion for "next/last object". "Last" here means "previous", not "final".
" Unfortunately the "p" motion was already taken for paragraphs.
"
" Next acts on the next object of the given type, last acts on the previous
" object of the given type. These don't necessarily have to be in the current
" line.
"
" Currently works for (, [, {, and their shortcuts b, r, B.
"
" Next kind of works for ' and " as long as there are no escaped versions of
" them in the string (TODO: fix that). Last is currently broken for quotes
" (TODO: fix that).
"
" Some examples (C marks cursor positions, V means visually selected):
"
" din' -> delete in next single quotes foo = bar('spam')
" C
" foo = bar('')
" C
"
" canb -> change around next parens foo = bar('spam')
" C
" foo = bar
" C
"
" vin" -> select inside next double quotes print "hello ", name
" C
" print "hello ", name
" VVVVVV
onoremap an :<c-u>call <SID>NextTextObject('a', '/')<cr>
xnoremap an :<c-u>call <SID>NextTextObject('a', '/')<cr>
onoremap in :<c-u>call <SID>NextTextObject('i', '/')<cr>
xnoremap in :<c-u>call <SID>NextTextObject('i', '/')<cr>
onoremap al :<c-u>call <SID>NextTextObject('a', '?')<cr>
xnoremap al :<c-u>call <SID>NextTextObject('a', '?')<cr>
onoremap il :<c-u>call <SID>NextTextObject('i', '?')<cr>
xnoremap il :<c-u>call <SID>NextTextObject('i', '?')<cr>
function! s:NextTextObject(motion, dir)
let c = nr2char(getchar())
let d = ''
if c ==# "b" || c ==# "(" || c ==# ")"
let c = "("
elseif c ==# "B" || c ==# "{" || c ==# "}"
let c = "{"
elseif c ==# "r" || c ==# "[" || c ==# "]"
let c = "["
elseif c ==# "'"
let c = "'"
elseif c ==# '"'
let c = '"'
else
return
endif
" Find the next opening-whatever.
execute "normal! " . a:dir . c . "\<cr>"
if a:motion ==# 'a'
" If we're doing an 'around' method, we just need to select around it
" and we can bail out to Vim.
execute "normal! va" . c
else
" Otherwise we're looking at an 'inside' motion. Unfortunately these
" get tricky when you're dealing with an empty set of delimiters because
" Vim does the wrong thing when you say vi(.
let open = ''
let close = ''
if c ==# "("
let open = "("
let close = ")"
elseif c ==# "{"
let open = "{"
let close = "}"
elseif c ==# "["
let open = "\\["
let close = "\\]"
elseif c ==# "'"
let open = "'"
let close = "'"
elseif c ==# '"'
let open = '"'
let close = '"'
endif
" We'll start at the current delimiter.
let start_pos = getpos('.')
let start_l = start_pos[1]
let start_c = start_pos[2]
" Then we'll find it's matching end delimiter.
if c ==# "'" || c ==# '"'
" searchpairpos() doesn't work for quotes, because fuck me.
let end_pos = searchpos(open)
else
let end_pos = searchpairpos(open, '', close)
endif
let end_l = end_pos[0]
let end_c = end_pos[1]
call setpos('.', start_pos)
if start_l == end_l && start_c == (end_c - 1)
" We're in an empty set of delimiters. We'll append an "x"
" character and select that so most Vim commands will do something
" sane. v is gonna be weird, and so is y. Oh well.
execute "normal! ax\<esc>\<left>"
execute "normal! vi" . c
elseif start_l == end_l && start_c == (end_c - 2)
" We're on a set of delimiters that contain a single, non-newline
" character. We can just select that and we're done.
execute "normal! vi" . c
else
" Otherwise these delimiters contain something. But we're still not
" sure Vim's gonna work, because if they contain nothing but
" newlines Vim still does the wrong thing. So we'll manually select
" the guts ourselves.
let whichwrap = &whichwrap
set whichwrap+=h,l
execute "normal! va" . c . "hol"
let &whichwrap = whichwrap
endif
endif
endfunction