" targets.vim Provides additional text objects
" Author:  Christian Wellenbrock <christian.wellenbrock@gmail.com>
" License: MIT license

if exists("g:loaded_targets") || &cp || v:version < 700
    finish
endif
let g:loaded_targets = '0.4.5' " version number
let s:save_cpoptions = &cpoptions
set cpo&vim

function! s:addMapping1(mapType, mapping, aiAI)
    if a:aiAI !=# ' '
        silent! execute a:mapType . 'noremap <silent> <unique>' . a:aiAI . a:mapping
    endif
endfunction

function! s:addMapping2(mapType, mapping, aiAI, nlNL)
    if a:aiAI !=# ' ' && a:nlNL !=# ' '
        silent! execute a:mapType . 'noremap <silent> <unique>' . a:aiAI . a:nlNL . a:mapping
    endif
endfunction

" pair text objects (multi line objects with single line seek)
" cursor  │                        .........
" line    │ a ( bbbbbb ) ( ccccc ) ( ddddd ) ( eeeee ) ( ffffff ) g
" command │   ││└2Il)┘│││││└Il)┘│││││└─I)┘│││││└In)┘│││││└2In)┘│││
"         │   │└─2il)─┘│││└─il)─┘│││└──i)─┘│││└─in)─┘│││└─2in)─┘││
"         │   ├──2al)──┘│├──al)──┘│├───a)──┘│├──an)──┘│├──2an)──┘│
"         │   └──2Al)───┘└──Al)───┘└───A)───┘└──An)───┘└──2An)───┘
" cursor  │                          .........
" line    │ a ( b ( cccccc ) d ) ( e ( fffff ) g ) ( h ( iiiiii ) j ) k
" command │   │││ ││└2Il)┘││││││││││ ││└─I)┘││││││││││ ││└2In)┘│││││││
"         │   │││ │└─2il)─┘│││││││││ │└──i)─┘│││││││││ │└─2in)─┘││││││
"         │   │││ ├──2al)──┘││││││││ ├───a)──┘││││││││ ├──2an)──┘│││││
"         │   │││ └──2Al)───┘│││││││ └───A)───┘│││││││ └──2An)───┘││││
"         │   ││└─────Il)────┘│││││└────2I)────┘│││││└─────In)────┘│││
"         │   │└──────il)─────┘│││└─────2i)─────┘│││└──────in)─────┘││
"         │   ├───────al)──────┘│├──────2a)──────┘│├───────an)──────┘│
"         │   └───────Al)───────┘└──────2A)───────┘└───────An)───────┘
function! s:createPairTextObjects(mapType)
    for trigger in split(g:targets_pairs, '\zs')
        if trigger ==# ' '
            continue
        endif
        let triggerMap = trigger . " :<C-U>call targets#" . a:mapType . "('" . trigger
        call s:addMapping1(a:mapType, triggerMap . "ci', v:count1)<CR>", s:i)
        call s:addMapping1(a:mapType, triggerMap . "ca', v:count1)<CR>", s:a)
        call s:addMapping1(a:mapType, triggerMap . "cI', v:count1)<CR>", s:I)
        call s:addMapping1(a:mapType, triggerMap . "cA', v:count1)<CR>", s:A)
        call s:addMapping2(a:mapType, triggerMap . "ni', v:count1)<CR>", s:i, s:n)
        call s:addMapping2(a:mapType, triggerMap . "na', v:count1)<CR>", s:a, s:n)
        call s:addMapping2(a:mapType, triggerMap . "nI', v:count1)<CR>", s:I, s:n)
        call s:addMapping2(a:mapType, triggerMap . "nA', v:count1)<CR>", s:A, s:n)
        call s:addMapping2(a:mapType, triggerMap . "li', v:count1)<CR>", s:i, s:l)
        call s:addMapping2(a:mapType, triggerMap . "la', v:count1)<CR>", s:a, s:l)
        call s:addMapping2(a:mapType, triggerMap . "lI', v:count1)<CR>", s:I, s:l)
        call s:addMapping2(a:mapType, triggerMap . "lA', v:count1)<CR>", s:A, s:l)
    endfor
endfunction

" tag text objects work on tags (similar to pair text objects)
function! s:createTagTextObjects(mapType)
    let trigger = g:targets_tagTrigger
    let triggerMap = trigger . " :<C-U>call targets#" . a:mapType . "('" . trigger
    call s:addMapping1(a:mapType, triggerMap . "ci', v:count1)<CR>", s:i)
    call s:addMapping1(a:mapType, triggerMap . "ca', v:count1)<CR>", s:a)
    call s:addMapping1(a:mapType, triggerMap . "cI', v:count1)<CR>", s:I)
    call s:addMapping1(a:mapType, triggerMap . "cA', v:count1)<CR>", s:A)
    call s:addMapping2(a:mapType, triggerMap . "ni', v:count1)<CR>", s:i, s:n)
    call s:addMapping2(a:mapType, triggerMap . "na', v:count1)<CR>", s:a, s:n)
    call s:addMapping2(a:mapType, triggerMap . "nI', v:count1)<CR>", s:I, s:n)
    call s:addMapping2(a:mapType, triggerMap . "nA', v:count1)<CR>", s:A, s:n)
    call s:addMapping2(a:mapType, triggerMap . "li', v:count1)<CR>", s:i, s:l)
    call s:addMapping2(a:mapType, triggerMap . "la', v:count1)<CR>", s:a, s:l)
    call s:addMapping2(a:mapType, triggerMap . "lI', v:count1)<CR>", s:I, s:l)
    call s:addMapping2(a:mapType, triggerMap . "lA', v:count1)<CR>", s:A, s:l)
endfunction

" quote text objects expand into quote (by counting quote signs)
" `aN'` is a shortcut for `2an'` to jump from within one quote into the
" next one, instead of the quote in between
" cursor  │                   ........
" line    │ a ' bbbbb ' ccccc ' dddd ' eeeee ' fffff ' g
" command │   ││└IL'┘│││└Il'┘│││└I'┘│││└In'┘│││└IN'┘│││
"         │   │└─iL'─┘│├─il'─┘│├─i'─┘│├─in'─┘│├─iN'─┘││
"         │   ├──aL'──┤│      ├┼─a'──┤│      ├┼─aN'──┘│
"         │   └──AL'──┼┘      ├┼─A'──┼┘      ├┼─AN'───┘
"         │           ├──al'──┘│     ├──an'──┘│
"         │           └──Al'───┘     └──An'───┘
" cursor  │ ..........      │      ......      │      ..........
" line    │ a ' bbbb ' c '' │ ' a ' bbbb ' c ' │ '' b ' cccc ' d
" command │   ││└I'┘│││     │     ││└I'┘│││    │      ││└I'┘│││
"         │   │└─i'─┘││     │     │└─i'─┘││    │      │└─i'─┘││
"         │   ├──a'──┘│     │     ├──a'──┘│    │      ├──a'──┘│
"         │   └──A'───┘     │     └──A'───┘    │      └──A'───┘
function! s:createQuoteTextObjects(mapType)
    " quote text objects
    for trigger in split(g:targets_quotes, '\zs')
        if trigger ==# " "
            continue
        elseif trigger ==# "'"
            let triggerMap = "' :<C-U>call targets#" . a:mapType . "('''"
        else
            let triggerMap = trigger . " :<C-U>call targets#" . a:mapType . "('" . trigger
        endif
        call s:addMapping1(a:mapType, triggerMap . "ci', v:count1)<CR>", s:i)
        call s:addMapping1(a:mapType, triggerMap . "ca', v:count1)<CR>", s:a)
        call s:addMapping1(a:mapType, triggerMap . "cI', v:count1)<CR>", s:I)
        call s:addMapping1(a:mapType, triggerMap . "cA', v:count1)<CR>", s:A)
        call s:addMapping2(a:mapType, triggerMap . "ni', v:count1)<CR>", s:i, s:n)
        call s:addMapping2(a:mapType, triggerMap . "na', v:count1)<CR>", s:a, s:n)
        call s:addMapping2(a:mapType, triggerMap . "nI', v:count1)<CR>", s:I, s:n)
        call s:addMapping2(a:mapType, triggerMap . "nA', v:count1)<CR>", s:A, s:n)
        call s:addMapping2(a:mapType, triggerMap . "li', v:count1)<CR>", s:i, s:l)
        call s:addMapping2(a:mapType, triggerMap . "la', v:count1)<CR>", s:a, s:l)
        call s:addMapping2(a:mapType, triggerMap . "lI', v:count1)<CR>", s:I, s:l)
        call s:addMapping2(a:mapType, triggerMap . "lA', v:count1)<CR>", s:A, s:l)
    endfor
endfunction

" separator text objects expand to the right
" cursor  │              .............
" line    │ a ' bbbbbbb ' c ' dddddd ' e ' fffffff ' g ~
" command │   ││└ Il' ┘│││  ││└ I' ┘│││  ││└ In' ┘│││
"         │   │└─ il' ─┘││  │└─ i' ─┘││  │└─ in' ─┘││
"         │   ├── al' ──┘│  ├── a' ──┘│  ├── an' ──┘│
"         │   └── Al' ───┘  └── A' ───┘  └── An' ───┘
" cursor  │ .........        │       ..........
" line    │ a , bbbb , c , d │ a , b , cccc , d
" command │   ││└I,┘│ │      │       ││└I,┘│ │
"         │   │└─i,─┤ │      │       │└─i,─┤ │
"         │   ├──a,─┘ │      │       ├──a,─┘ │
"         │   └──A,───┘      │       └──A,───┘
function! s:createSeparatorTextObjects(mapType)
    " separator text objects
    for trigger in split(g:targets_separators, '\zs')
        if trigger ==# ' '
            continue
        elseif trigger ==# '|'
            let trigger = '\|'
        endif
        let triggerMap = trigger . " :<C-U>call targets#" . a:mapType . "('" . trigger
        call s:addMapping1(a:mapType, triggerMap . "ci', v:count1)<CR>", s:i)
        call s:addMapping1(a:mapType, triggerMap . "ca', v:count1)<CR>", s:a)
        call s:addMapping1(a:mapType, triggerMap . "cI', v:count1)<CR>", s:I)
        call s:addMapping1(a:mapType, triggerMap . "cA', v:count1)<CR>", s:A)
        call s:addMapping2(a:mapType, triggerMap . "ni', v:count1)<CR>", s:i, s:n)
        call s:addMapping2(a:mapType, triggerMap . "na', v:count1)<CR>", s:a, s:n)
        call s:addMapping2(a:mapType, triggerMap . "nI', v:count1)<CR>", s:I, s:n)
        call s:addMapping2(a:mapType, triggerMap . "nA', v:count1)<CR>", s:A, s:n)
        call s:addMapping2(a:mapType, triggerMap . "li', v:count1)<CR>", s:i, s:l)
        call s:addMapping2(a:mapType, triggerMap . "la', v:count1)<CR>", s:a, s:l)
        call s:addMapping2(a:mapType, triggerMap . "lI', v:count1)<CR>", s:I, s:l)
        call s:addMapping2(a:mapType, triggerMap . "lA', v:count1)<CR>", s:A, s:l)
        call s:addMapping2(a:mapType, triggerMap . "Ni', v:count1)<CR>", s:i, s:N)
        call s:addMapping2(a:mapType, triggerMap . "Na', v:count1)<CR>", s:a, s:N)
        call s:addMapping2(a:mapType, triggerMap . "NI', v:count1)<CR>", s:I, s:N)
        call s:addMapping2(a:mapType, triggerMap . "NA', v:count1)<CR>", s:A, s:N)
        call s:addMapping2(a:mapType, triggerMap . "Li', v:count1)<CR>", s:i, s:L)
        call s:addMapping2(a:mapType, triggerMap . "La', v:count1)<CR>", s:a, s:L)
        call s:addMapping2(a:mapType, triggerMap . "LI', v:count1)<CR>", s:I, s:L)
        call s:addMapping2(a:mapType, triggerMap . "LA', v:count1)<CR>", s:A, s:L)
    endfor
endfunction

" argument text objects expand to the right
" cursor  │                          .........
" line    │ a ( bbbbbb , ccccccc , d ( eeeeee , fffffff ) , gggggg ) h
" command │   ││├2Ila┘│││└─Ila─┘││││ ││├─Ia─┘│││└─Ina─┘│││││└2Ina┘│ │
"         │   │└┼2ila─┘│├──ila──┤│││ │└┼─ia──┘│├──ina──┤│││├─2ina─┤ │
"         │   │ └2ala──┼┤       ││││ │ └─aa───┼┤       │││├┼─2ana─┘ │
"         │   └──2Ala──┼┘       ││││ └───Aa───┼┘       │││└┼─2Ana───┘
"         │            ├───ala──┘│││          ├───ana──┘││ │
"         │            └───Ala───┼┤│          └───Ana───┼┤ │
"         │                      ││└─────2Ia────────────┘│ │
"         │                      │└──────2ia─────────────┤ │
"         │                      ├───────2aa─────────────┘ │
"         │                      └───────2Aa───────────────┘
function! s:createArgTextObjects(mapType)
    let trigger = g:targets_argTrigger
    let triggerMap = trigger . " :<C-U>call targets#" . a:mapType . "('" . trigger
    call s:addMapping1(a:mapType, triggerMap . "ci', v:count1)<CR>", s:i)
    call s:addMapping1(a:mapType, triggerMap . "ca', v:count1)<CR>", s:a)
    call s:addMapping1(a:mapType, triggerMap . "cI', v:count1)<CR>", s:I)
    call s:addMapping1(a:mapType, triggerMap . "cA', v:count1)<CR>", s:A)
    call s:addMapping2(a:mapType, triggerMap . "ni', v:count1)<CR>", s:i, s:n)
    call s:addMapping2(a:mapType, triggerMap . "na', v:count1)<CR>", s:a, s:n)
    call s:addMapping2(a:mapType, triggerMap . "nI', v:count1)<CR>", s:I, s:n)
    call s:addMapping2(a:mapType, triggerMap . "nA', v:count1)<CR>", s:A, s:n)
    call s:addMapping2(a:mapType, triggerMap . "li', v:count1)<CR>", s:i, s:l)
    call s:addMapping2(a:mapType, triggerMap . "la', v:count1)<CR>", s:a, s:l)
    call s:addMapping2(a:mapType, triggerMap . "lI', v:count1)<CR>", s:I, s:l)
    call s:addMapping2(a:mapType, triggerMap . "lA', v:count1)<CR>", s:A, s:l)
endfunction

" add expression mappings for `A` and `I` in visual mode #23 unless
" deactivated #49. Manually make mappings for older verions of vim #117.
function! s:addVisualMappings()
    if v:version >= 704 || (v:version == 703 && has('patch338'))
        silent! execute 'xnoremap <expr> <silent> <unique> ' . s:i . " targets#e('i')"
        silent! execute 'xnoremap <expr> <silent> <unique> ' . s:a . " targets#e('a')"
        silent! execute 'xnoremap <expr> <silent> <unique> ' . s:I . " targets#e('I')"
        silent! execute 'xnoremap <expr> <silent> <unique> ' . s:A . " targets#e('A')"
    else
        call s:createPairTextObjects('x')
        call s:createTagTextObjects('x')
        call s:createQuoteTextObjects('x')
        call s:createSeparatorTextObjects('x')
        call s:createArgTextObjects('x')
    endif
endfunction

function! s:loadSettings()
    if !exists('g:targets_aiAI')
        let g:targets_aiAI = 'aiAI'
    endif
    if !exists('g:targets_nlNL')
        let g:targets_nlNL = 'nlNL'
    endif
    if !exists('g:targets_pairs')
        let g:targets_pairs = '()b {}B [] <>'
    endif
    if !exists('g:targets_quotes')
        let g:targets_quotes = '" '' `'
    endif
    if !exists('g:targets_separators')
        let g:targets_separators = ', . ; : + - = ~ _ * # / \ | & $'
    endif
    if !exists('g:targets_tagTrigger')
        let g:targets_tagTrigger = 't'
    endif
    if !exists('g:targets_argTrigger')
        let g:targets_argTrigger = 'a'
    endif
    if !exists('g:targets_argOpening')
        let g:targets_argOpening = '[([]'
    endif
    if !exists('g:targets_argClosing')
        let g:targets_argClosing = '[])]'
    endif
    if !exists('g:targets_argSeparator')
        let g:targets_argSeparator = ','
    endif
    if !exists('g:targets_seekRanges')
        let g:targets_seekRanges = 'cr cb cB lc ac Ac lr rr ll lb ar ab lB Ar aB Ab AB rb al rB Al bb aa bB Aa BB AA'
    endif
    if !exists('g:targets_jumpRanges')
        let g:targets_jumpRanges = 'bb bB BB aa Aa AA'
    endif

    let [s:a, s:i, s:A, s:I] = split(g:targets_aiAI, '\zs')
    let [s:n, s:l, s:N, s:L] = split(g:targets_nlNL, '\zs')
endfunction

call s:loadSettings()

" create the text objects (current total count: 544)
" more specific ones first for #145
call s:createTagTextObjects('o')
call s:createArgTextObjects('o')
call s:createPairTextObjects('o')
call s:createQuoteTextObjects('o')
call s:createSeparatorTextObjects('o')
call s:addVisualMappings()

let &cpoptions = s:save_cpoptions
unlet s:save_cpoptions