" vim match-up - even better matching
"
" Maintainer: Andy Massimino
" Email:      a@normed.space
"

let s:save_cpo = &cpo
set cpo&vim

function! matchup#init()
  call matchup#perf#tic('loading')

  call s:init_options()
  call s:init_modules()
  call s:init_default_mappings()

  call matchup#perf#toc('loading', 'init_done')
endfunction

function! s:init_options()
  call s:init_option('matchup_matchparen_enabled',
    \ !(&t_Co < 8 && !has('gui_running')))
  let l:offs = {'method': 'status'}
  if !get(g:, 'matchup_matchparen_status_offscreen', 1)
    let l:offs = {}
  endif
  if get(g:, 'matchup_matchparen_status_offscreen_manual', 0)
    let l:offs.method = 'status_manual'
  endif
  if exists('g:matchup_matchparen_scrolloff')
    let l:offs.scrolloff = g:matchup_matchparen_scrolloff
  endif
  call s:init_option('matchup_matchparen_offscreen', l:offs)
  call s:init_option('matchup_matchparen_singleton', 0)
  call s:init_option('matchup_matchparen_deferred', 0)
  call s:init_option('matchup_matchparen_deferred_show_delay', 50)
  call s:init_option('matchup_matchparen_deferred_hide_delay', 700)
  call s:init_option('matchup_matchparen_deferred_fade_time', 0)
  call s:init_option('matchup_matchparen_stopline', 400)
  call s:init_option('matchup_matchparen_pumvisible', 1)
  call s:init_option('matchup_matchparen_nomode', '')
  call s:init_option('matchup_matchparen_hi_surround_always', 0)
  call s:init_option('matchup_matchparen_hi_background', 0)

  call s:init_option('matchup_matchparen_timeout',
    \ get(g:, 'matchparen_timeout', 300))
  call s:init_option('matchup_matchparen_insert_timeout',
    \ get(g:, 'matchparen_insert_timeout', 60))

  call s:init_option('matchup_delim_count_fail', 0)
  call s:init_option('matchup_delim_count_max', 8)
  call s:init_option('matchup_delim_start_plaintext', 1)
  call s:init_option('matchup_delim_noskips', 0)

  call s:init_option('matchup_motion_enabled', 1)
  call s:init_option('matchup_motion_cursor_end', 1)
  call s:init_option('matchup_motion_override_Npercent', 6)

  call s:init_option('matchup_text_obj_enabled', 1)
  call s:init_option('matchup_text_obj_linewise_operators', ['d', 'y'])

  call s:init_option('matchup_transmute_enabled', 0)
  call s:init_option('matchup_transmute_breakundo', 0)

  call s:init_option('matchup_mouse_enabled', 1)

  call s:init_option('matchup_surround_enabled', 0)

  call s:init_option('matchup_where_enabled', 1)
  call s:init_option('matchup_where_separator', '')

  call s:init_option('matchup_matchpref', {})
endfunction

function! s:init_option(option, default)
  let l:option = 'g:' . a:option
  if !exists(l:option)
    let {l:option} = a:default
  endif
endfunction

function! s:init_modules()
  for l:mod in [ 'loader', 'matchparen' ]
    if !get(g:, 'matchup_'.l:mod.'_enabled', 1)
      continue
    endif
    call matchup#perf#tic('loading_module')
    call matchup#{l:mod}#init_module()
    call matchup#perf#toc('loading_module', l:mod)
  endfor

  call s:motion_init_module()
  call s:text_obj_init_module()
  call s:misc_init_module()
  call s:surround_init_module()
  call s:where_init_module()
endfunction

function! s:init_oldstyle_ops() " {{{1
  if get(g:, 'matchup_motion_enabled', 0)
        \ || get(g:, 'matchup_text_obj_enabled', 0)
    for l:opforce in ['', 'v', 'V', '<c-v>']
      call s:map('onore', '<expr> <plug>(matchup-o_'.l:opforce.')',
            \ '<sid>force('''.l:opforce.''')')
    endfor
  endif

  if get(g:, 'matchup_motion_enabled', 0)
    for l:opforce in ['', 'v', 'V', '<c-v>']
      call s:map('o', l:opforce.'%',
            \ '<plug>(matchup-o_'.l:opforce.')<plug>(matchup-%)')
      call s:map('o', l:opforce.'g%',
            \ '<plug>(matchup-o_'.l:opforce.')<plug>(matchup-g%)')
      call s:map('o', l:opforce.']%',
            \ '<plug>(matchup-o_'.l:opforce.')<plug>(matchup-]%)')
      call s:map('o', l:opforce.'[%',
            \ '<plug>(matchup-o_'.l:opforce.')<plug>(matchup-[%)')
      call s:map('o', l:opforce.'z%',
            \ '<plug>(matchup-o_'.l:opforce.')<plug>(matchup-z%)')
    endfor
  endif

  if get(g:, 'matchup_text_obj_enabled', 0)
    for l:opforce in ['', 'v', 'V', '<c-v>']
      call s:map('o', l:opforce.'i%',
            \ '<plug>(matchup-o_'.l:opforce.')<plug>(matchup-i%)')
      call s:map('o', l:opforce.'a%',
            \ '<plug>(matchup-o_'.l:opforce.')<plug>(matchup-a%)')
    endfor
  endif
endfunction

function! s:make_oldstyle_omaps(lhs, rhs)
  if !s:old_style_ops
    return 0
  endif
  for l:opforce in ['', 'v', 'V', '<c-v>']
    silent! execute 'omap' l:opforce.a:lhs
          \ '<plug>(matchup-o_'.l:opforce.')<plug>(matchup-'.a:rhs.')'
  endfor
  return 1
endfunction

let s:old_style_ops = !has('patch-8.1.0648')

let g:v_motion_force = ''
function! s:force(wise)
  let g:v_motion_force = a:wise
  return ''
endfunction

function! matchup#motion_force() abort
  if !s:old_style_ops
    let l:mode = mode(1)
    let g:v_motion_force = len(l:mode) >= 3
          \ && l:mode[0:1] ==# 'no' ? l:mode[2] : ''
  endif
  return g:v_motion_force
endfunction

" }}}1

function! s:init_default_mappings()
  if !get(g:,'matchup_mappings_enabled', 1) | return | endif

  function! s:map(mode, lhs, rhs, ...)
    if !hasmapto(a:rhs, a:mode)
          \ && ((a:0 > 0) || (maparg(a:lhs, a:mode) ==# ''))
      silent execute a:mode . 'map <silent> ' a:lhs a:rhs
    endif
  endfunction

  if s:old_style_ops
    call s:init_oldstyle_ops()
  endif

  " these won't conflict since matchit should not be loaded at this point
  if get(g:, 'matchup_motion_enabled', 0)
    call s:map('n', '%',  '<plug>(matchup-%)' )
    call s:map('n', 'g%', '<plug>(matchup-g%)')

    call s:map('x', '%',  '<plug>(matchup-%)' )
    call s:map('x', 'g%', '<plug>(matchup-g%)')

    call s:map('n', ']%', '<plug>(matchup-]%)')
    call s:map('n', '[%', '<plug>(matchup-[%)')

    call s:map('x', ']%', '<plug>(matchup-]%)')
    call s:map('x', '[%', '<plug>(matchup-[%)')

    call s:map('n', 'z%', '<plug>(matchup-z%)')
    call s:map('x', 'z%', '<plug>(matchup-z%)')

    if !s:old_style_ops
      call s:map('o', '%', '<plug>(matchup-%)')
      call s:map('o', 'g%', '<plug>(matchup-g%)')
      call s:map('o', ']%', '<plug>(matchup-]%)')
      call s:map('o', '[%', '<plug>(matchup-[%)')
      call s:map('o', 'z%', '<plug>(matchup-z%)')
    endif

    call s:map('i', '<c-g>%', '<plug>(matchup-c_g%)')
  endif

  if get(g:, 'matchup_text_obj_enabled', 0)
    call s:map('x', 'i%', '<plug>(matchup-i%)')
    call s:map('x', 'a%', '<plug>(matchup-a%)')

    if !s:old_style_ops
      call s:map('o', 'i%', '<plug>(matchup-i%)')
      call s:map('o', 'a%', '<plug>(matchup-a%)')
    endif
  endif

  if get(g:, 'matchup_mouse_enabled', 1)
    call s:map('n', '<2-LeftMouse>', '<plug>(matchup-double-click)')
  endif

  if get(g:, 'matchup_surround_enabled', 0)
    call s:map('n', 'ds%', '<plug>(matchup-ds%)')
    call s:map('n', 'cs%', '<plug>(matchup-cs%)')
  endif
endfunction

" module initialization

function! s:motion_init_module() " {{{1
  if !g:matchup_motion_enabled | return | endif

  call matchup#perf#tic('loading_module')

  " gets the current forced motion type
  nnoremap <silent><expr> <sid>(wise)
        \ empty(g:v_motion_force) ? 'v' : g:v_motion_force

  " the basic motions % and g%
  nnoremap <silent> <plug>(matchup-%)
        \ :<c-u>call matchup#motion#find_matching_pair(0, 1)<cr>
  nnoremap <silent> <plug>(matchup-g%)
        \ :<c-u>call matchup#motion#find_matching_pair(0, 0)<cr>

  " visual and operator-pending
  xnoremap <silent> <sid>(matchup-%)
        \ :<c-u>call matchup#motion#find_matching_pair(1, 1)<cr>
  xmap     <silent> <plug>(matchup-%) <sid>(matchup-%)
  onoremap <silent> <plug>(matchup-%)
        \ :<c-u>call matchup#motion#op('%')<cr>

  xnoremap <silent> <sid>(matchup-g%)
        \ :<c-u>call matchup#motion#find_matching_pair(1, 0)<cr>
  xmap     <silent> <plug>(matchup-g%) <sid>(matchup-g%)
  onoremap <silent> <plug>(matchup-g%)
        \ :<c-u>call matchup#motion#op('g%')<cr>

  " ]% and [%
  nnoremap <silent> <plug>(matchup-]%)
        \ :<c-u>call matchup#motion#find_unmatched(0, 1)<cr>
  nnoremap <silent> <plug>(matchup-[%)
        \ :<c-u>call matchup#motion#find_unmatched(0, 0)<cr>

  xnoremap <silent> <sid>(matchup-]%)
        \ :<c-u>call matchup#motion#find_unmatched(1, 1)<cr>
  xnoremap <silent> <sid>(matchup-[%)
        \ :<c-u>call matchup#motion#find_unmatched(1, 0)<cr>
  xmap     <plug>(matchup-]%) <sid>(matchup-]%)
  xmap     <plug>(matchup-[%) <sid>(matchup-[%)
  onoremap <silent> <plug>(matchup-]%)
        \ :<c-u>call matchup#motion#op(']%')<cr>
  onoremap <silent> <plug>(matchup-[%)
        \ :<c-u>call matchup#motion#op('[%')<cr>

  " jump inside z%
  nnoremap <silent> <plug>(matchup-z%)
        \ :<c-u>call matchup#motion#jump_inside(0)<cr>

  xnoremap <silent> <sid>(matchup-z%)
        \ :<c-u>call matchup#motion#jump_inside(1)<cr>
  xmap     <silent> <plug>(matchup-z%) <sid>(matchup-z%)
  onoremap <silent> <plug>(matchup-z%)
        \ :<c-u>call matchup#motion#op('z%')<cr>

  inoremap <silent> <plug>(matchup-c_g%)
        \ <c-\><c-o>:call matchup#motion#insert_mode()<cr>

  call matchup#perf#toc('loading_module', 'motion')
endfunction

" TODO redo this
function! s:snr()
  return str2nr(matchstr(expand('<sfile>'), '<SNR>\zs\d\+\ze_snr$'))
endfunction
let s:sid = printf("\<SNR>%d_", s:snr())

function! matchup#motion_sid()
  return s:sid
endfunction

" }}}1
function! s:text_obj_init_module() " {{{1
  if !g:matchup_text_obj_enabled | return | endif

  call matchup#perf#tic('loading_module')

  for [l:map, l:name, l:opt] in [
        \ ['%', 'delimited', 'delim_all'],
        \]
    let l:p1 = 'noremap <silent> <plug>(matchup-'
    let l:p2 = l:map . ') :<c-u>call matchup#text_obj#' . l:name
    let l:p3 = empty(l:opt) ? ')<cr>' : ', ''' . l:opt . ''')<cr>'
    execute 'x' . l:p1 . 'i' . l:p2 . '(1, 1' . l:p3
    execute 'x' . l:p1 . 'a' . l:p2 . '(0, 1' . l:p3
    execute 'o' . l:p1 . 'i' . l:p2 . '(1, 0' . l:p3
    execute 'o' . l:p1 . 'a' . l:p2 . '(0, 0' . l:p3
  endfor

  nnoremap <silent> <plug>(matchup-double-click)
    \ :<c-u>call matchup#text_obj#double_click()<cr>

  call matchup#perf#toc('loading_module', 'motion')
endfunction

" }}}1
function! s:misc_init_module() " {{{1
  call matchup#perf#tic('loading_module')
  command! MatchupReload          call matchup#misc#reload()
  nnoremap <plug>(matchup-reload) :<c-u>MatchupReload<cr>
  call matchup#perf#toc('loading_module', 'misc')
endfunction

" }}}1
function! s:surround_init_module() " {{{1
  if !g:matchup_surround_enabled | return | endif

  call matchup#perf#tic('loading_module')

  for [l:map, l:name, l:opt] in [
        \ ['%', 'delimited', 'delim_all'],
        \]
    let l:p1 = 'noremap <silent> <plug>(matchup-'
    let l:p2 = l:map . ') :<c-u>call matchup#surround#' . l:name
    let l:p3 = empty(l:opt) ? ')<cr>' : ', ''' . l:opt . ''')<cr>'
    execute 'n' . l:p1 . 'ds' . l:p2 . '(0, "d"' . l:p3
    execute 'n' . l:p1 . 'cs' . l:p2 . '(0, "c"' . l:p3
  endfor

  call matchup#perf#toc('loading_module', 'surround')
endfunction

" }}}1
function! s:where_init_module() " {{{1
  if !g:matchup_where_enabled | return | endif

  command! -nargs=? -bang MatchupWhereAmI
        \ call matchup#where#print('<bang>' . <q-args>)
endfunction

" }}}1

let &cpo = s:save_cpo

" vim: fdm=marker sw=2