"=============================================================================
" FILE: line.vim
" AUTHOR:  Shougo Matsushita <Shougo.Matsu at gmail.com>
"          t9md <taqumd at gmail.com>
" License: MIT license  {{{
"     Permission is hereby granted, free of charge, to any person obtaining
"     a copy of this software and associated documentation files (the
"     "Software"), to deal in the Software without restriction, including
"     without limitation the rights to use, copy, modify, merge, publish,
"     distribute, sublicense, and/or sell copies of the Software, and to
"     permit persons to whom the Software is furnished to do so, subject to
"     the following conditions:
"
"     The above copyright notice and this permission notice shall be included
"     in all copies or substantial portions of the Software.
"
"     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
"     OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
"     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
"     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
"     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
"     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
"     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
" }}}
"=============================================================================

" original verion is http://d.hatena.ne.jp/thinca/20101105/1288896674

call unite#util#set_default(
      \ 'g:unite_source_line_enable_highlight', 0)

let s:supported_search_direction =
      \ ['forward', 'backward', 'all', 'buffers', 'args']

function! unite#sources#line#define() abort "{{{
  return s:source_line
endfunction "}}}

" line source. "{{{
let s:source_line = {
      \ 'name' : 'line',
      \ 'syntax' : 'uniteSource__Line',
      \ 'hooks' : {},
      \ 'max_candidates': 100,
      \ 'default_kind' : 'jump_list',
      \ 'matchers' : 'matcher_regexp',
      \ 'sorters' : 'sorter_nothing',
      \ }

function! s:source_line.hooks.on_init(args, context) abort "{{{
  let a:context.source__linenr = line('.')
  let a:context.source__linemax = line('$')
  let a:context.source__is_bang =
        \ (get(a:args, 0, '') ==# '!')

  let options = filter(copy(a:args), "v:val != '!'")
  let direction = get(options, 0, '')
  if direction == ''
    let direction = 'all'
  endif
  let a:context.source__wrap = get(options, 1,
        \ (&wrapscan ? 'wrap' : 'nowrap')) ==# 'wrap'

  if index(s:supported_search_direction, direction) == -1
    let direction = 'all'
  endif

  let a:context.source__bufnrs =
        \ (direction ==# 'buffers') ?
        \    filter(range(1, bufnr('$')), 'buflisted(v:val)') :
        \ (direction ==# 'args') ?
        \    filter(map(argv(), "bufnr(v:val)"), 'v:val > 0') :
        \ [bufnr('%')]
  " Dummy loading
  call s:dummy_loading(a:context.source__bufnrs)

  if len(a:context.source__bufnrs) == 1
    let a:context.source__syntax =
          \ getbufvar(a:context.source__bufnrs[0], '&l:syntax')
  endif

  let a:context.source__input = a:context.input
  if a:context.source__linemax > 10000 && a:context.source__input == ''
    " Note: In huge buffer, you must input narrowing text.
    let a:context.source__input = unite#util#input('Narrowing text: ', '')
  endif

  if a:context.source__input != ''
    call unite#print_source_message(
          \ 'Narrowing text: ' . a:context.source__input,
          \ s:source_line.name)
  endif

  if len(a:context.source__bufnrs) == 1
    call unite#print_source_message(
          \ 'Target: ' . bufname(a:context.source__bufnrs[0]),
          \ s:source_line.name)
  endif

  let a:context.source__direction = direction
endfunction"}}}
function! s:source_line.hooks.on_syntax(args, context) abort "{{{
  let highlight = get(a:context, 'custom_line_enable_highlight',
        \ g:unite_source_line_enable_highlight)

  if len(a:context.source__bufnrs) > 1
    syntax match uniteSource__LineFile /^[^:]*:/ contained
          \ containedin=uniteSource__Line
          \ nextgroup=uniteSource__LineSeparator
    highlight default link uniteSource__LineFile Comment
  endif
  syntax match uniteSource__LineLineNR /\d\+:/ contained
        \ containedin=uniteSource__Line
  syntax match uniteSource__LineSeparator /:/ contained conceal
        \ containedin=uniteSource__LineFile,uniteSource__LineLineNR
  highlight default link uniteSource__LineLineNR LineNR

  if !highlight || len(a:context.source__bufnrs) > 1
    return
  endif

  let save_current_syntax = get(b:, 'current_syntax', '')
  unlet! b:current_syntax

  try
    execute 'silent! syntax include @LineSyntax' 'syntax/'
          \ . a:context.source__syntax . '.vim'
    syntax region uniteSource__Line_LineSyntax
          \ start='' end='$'
          \ contains=@LineSyntax
          \ containedin=uniteSource__Line contained
  finally
    let b:current_syntax = save_current_syntax
  endtry
endfunction"}}}

function! s:source_line.hooks.on_post_filter(args, context) abort "{{{
  for candidate in a:context.candidates
    let candidate.is_multiline = 1
    let candidate.action__col_pattern = a:context.source__input
    let candidate.action__buffer_nr = candidate.source__info[0]
    let candidate.action__line = candidate.source__info[1][0]
    let candidate.action__text = candidate.source__info[1][1]
  endfor
endfunction"}}}

function! s:source_line.gather_candidates(args, context) abort "{{{
  let direction = a:context.source__direction
  let start = a:context.source__linenr

  let _ = s:get_context_lines(a:context, direction, start)

  let a:context.source__format =
        \ (len(a:context.source__bufnrs) > 1) ?
        \ '%s:%4s: %s' :
        \ '%' . strlen(len(_)) . 'd: %s'

  return direction ==# 'backward' ? reverse(_) : _
endfunction"}}}

function! s:source_line.complete(args, context, arglead, cmdline, cursorpos) abort "{{{
  return s:supported_search_direction
endfunction"}}}

function! s:source_line.source__converter(candidates, context) abort "{{{
  if len(a:context.source__bufnrs) > 1
    for candidate in a:candidates
      let candidate.abbr = printf(a:context.source__format,
            \  unite#util#substitute_path_separator(
            \     fnamemodify(bufname(candidate.source__info[0]), ':.')),
            \ candidate.source__info[1][0],
            \ substitute(candidate.source__info[1][1], '\s\+$', '', ''))
    endfor
  else
    for candidate in a:candidates
      let candidate.abbr = printf(a:context.source__format,
            \ candidate.source__info[1][0],
            \ substitute(candidate.source__info[1][1], '\s\+$', '', ''))
    endfor
  endif

  return a:candidates
endfunction"}}}

let s:source_line.converters = [s:source_line.source__converter]
"}}}

" Misc. "{{{
function! s:on_gather_candidates(direction, context, start, max) abort "{{{
  let candidates = []
  for bufnr in a:context.source__bufnrs
    " source__info = [bufnr, [line, text]]
    let candidates += map(s:get_lines(a:context, a:direction,
          \ bufnr, a:start, a:max), "{
          \ 'word' : v:val[1],
          \ 'source__info' : [bufnr, v:val],
          \ }")
  endfor

  return candidates
endfunction"}}}
function! s:get_lines(context, direction, bufnr, start, max) abort "{{{
  let [start, end] =
        \ a:direction ==# 'forward' ?
        \ [a:start, (a:max == 0 ? '$' : a:start + a:max - 1)] :
        \ [((a:max == 0 || a:start == a:max) ?
        \    1 : a:start - a:max), a:start]

  let _ = []
  let linenr = start
  let input = tolower(a:context.source__input)
  let is_expr = input =~ '[~\\.^$\[\]*]'
  for line in getbufline(a:bufnr, start, end)
    if input == ''
          \ || (!is_expr && stridx(tolower(line), input) >= 0)
          \ || line =~? input
      call add(_, [linenr, line])
    endif

    let linenr += 1
  endfor

  return _
endfunction"}}}

function! s:get_context_lines(context, direction, start) abort "{{{
  if a:direction !=# 'forward' && a:direction !=# 'backward'
    let lines = s:on_gather_candidates('forward', a:context, 1, 0)
  else
    let lines = s:on_gather_candidates(a:direction, a:context, a:start, 0)

    if a:context.source__wrap
      let start = ((a:direction ==# 'forward') ?
            \       1 : a:context.source__linemax)
      let max = ((a:direction ==# 'forward') ?
            \       a:context.source__linenr-1 :
            \       a:context.source__linemax-a:context.source__linenr-1)
      if max != 0
        let lines += s:on_gather_candidates(a:direction, a:context, start, max)
      endif
    endif
  endif

  return lines
endfunction"}}}
function! s:dummy_loading(bufnrs) abort "{{{
  let load_bufnrs = filter(copy(a:bufnrs), '!bufloaded(v:val)')
  if empty(load_bufnrs)
    return
  endif

  let prev_bufnr = bufnr('%')
  try
    silent bufdo echo
  finally
    execute 'buffer' prev_bufnr
  endtry
endfunction"}}}
"}}}

" vim: foldmethod=marker