"=============================================================================
" FILE: buffer.vim
" AUTHOR:  Shougo Matsushita <Shougo.Matsu@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.
" }}}
"=============================================================================

let s:save_cpo = &cpo
set cpo&vim

" Variables  "{{{
call unite#util#set_default(
      \ 'g:unite_source_buffer_time_format',
      \ '(%Y/%m/%d %H:%M:%S) ')
"}}}

function! unite#sources#buffer#define() abort "{{{
  return [s:source_buffer_all, s:source_buffer_tab]
endfunction"}}}

let s:source_buffer_all = {
      \ 'name' : 'buffer',
      \ 'description' : 'candidates from buffer list',
      \ 'syntax' : 'uniteSource__Buffer',
      \ 'hooks' : {},
      \ 'default_kind' : 'buffer',
      \}

function! s:source_buffer_all.hooks.on_init(args, context) abort "{{{
  let a:context.source__is_bang =
        \ (get(a:args, 0, '') ==# '!')
  let a:context.source__is_question =
        \ (get(a:args, 0, '') ==# '?')
  let a:context.source__is_plus =
        \ (get(a:args, 0, '') ==# '+')
  let a:context.source__is_minus =
        \ (get(a:args, 0, '') ==# '-')
  let a:context.source__is_terminal =
        \ (get(a:args, 0, '') ==# 't')
  let a:context.source__buffer_list =
        \ s:get_buffer_list(a:context.source__is_bang,
        \                   a:context.source__is_question,
        \                   a:context.source__is_plus,
        \                   a:context.source__is_minus,
        \                   a:context.source__is_terminal)
endfunction"}}}
function! s:source_buffer_all.hooks.on_syntax(args, context) abort "{{{
  syntax match uniteSource__Buffer_Name /[^/ \[\]]\+\s/
        \ contained containedin=uniteSource__Buffer
  highlight default link uniteSource__Buffer_Name Function
  syntax match uniteSource__Buffer_Prefix /\d\+\s\%(\S\+\)\?/
        \ contained containedin=uniteSource__Buffer
  highlight default link uniteSource__Buffer_Prefix Constant
  syntax match uniteSource__Buffer_Info /\[.\{-}\] /
        \ contained containedin=uniteSource__Buffer
  highlight default link uniteSource__Buffer_Info PreProc
  syntax match uniteSource__Buffer_Modified /\[.\{-}+\]/
        \ contained containedin=uniteSource__Buffer
  highlight default link uniteSource__Buffer_Modified Statement
  syntax match uniteSource__Buffer_NoFile /\[nofile\]/
        \ contained containedin=uniteSource__Buffer
  highlight default link uniteSource__Buffer_NoFile Function
  syntax match uniteSource__Buffer_Time /(.\{-}) /
        \ contained containedin=uniteSource__Buffer
  highlight default link uniteSource__Buffer_Time Statement
endfunction"}}}
function! s:source_buffer_all.hooks.on_post_filter(args, context) abort "{{{
  for candidate in a:context.candidates
    let candidate.action__path =
          \ unite#util#substitute_path_separator(
          \       fnamemodify(s:make_word(candidate.action__buffer_nr), ':p'))
    let candidate.action__directory =
          \ unite#helper#get_buffer_directory(candidate.action__buffer_nr)
  endfor
endfunction"}}}

function! s:source_buffer_all.gather_candidates(args, context) abort "{{{
  if a:context.is_redraw
    " Recaching.
    let a:context.source__buffer_list =
          \ s:get_buffer_list(a:context.source__is_bang,
          \                   a:context.source__is_question,
          \                   a:context.source__is_plus,
          \                   a:context.source__is_minus,
          \                   a:context.source__is_terminal)
  endif

  let candidates = map(a:context.source__buffer_list, "{
        \ 'word' : unite#util#substitute_path_separator(
        \         s:make_word(v:val.action__buffer_nr)),
        \ 'abbr' : s:make_abbr(v:val.action__buffer_nr, v:val.source__flags)
        \        . s:format_time(v:val.source__time),
        \ 'action__buffer_nr' : v:val.action__buffer_nr,
        \}")

  return candidates
endfunction"}}}
function! s:source_buffer_all.complete(args, context, arglead, cmdline, cursorpos) abort "{{{
  return ['!', '?', '+', '-', 't']
endfunction"}}}

let s:source_buffer_tab = deepcopy(s:source_buffer_all)
let s:source_buffer_tab.name = 'buffer_tab'
let s:source_buffer_tab.description =
      \ 'candidates from buffer list in current tab'

function! s:source_buffer_tab.gather_candidates(args, context) abort "{{{
  if a:context.is_redraw
    " Recaching.
    let a:context.source__buffer_list =
          \ s:get_buffer_list(a:context.source__is_bang,
          \                   a:context.source__is_question,
          \                   a:context.source__is_plus,
          \                   a:context.source__is_minus,
          \                   a:context.source__is_terminal)
  endif

  if !exists('g:loaded_tabpagebuffer') && !exists('g:CtrlSpaceLoaded')
    call unite#print_source_message(
          \ 'tabpagebuffer or ctrlspace plugin is not installed.', self.name)
    return []
  endif

  if exists('t:tabpagebuffer')
    let bufferlist = t:tabpagebuffer
  elseif exists('t:CtrlSpaceList')
    let bufferlist = t:CtrlSpaceList
  else
    return []
  endif

  let list = filter(copy(a:context.source__buffer_list),
        \ 'has_key(bufferlist, v:val.action__buffer_nr)')

  let candidates = map(list, "{
        \ 'word' : unite#util#substitute_path_separator(
        \       fnamemodify(s:make_word(v:val.action__buffer_nr), ':p')),
        \ 'abbr' : s:make_abbr(v:val.action__buffer_nr, v:val.source__flags)
        \        . s:format_time(v:val.source__time),
        \ 'action__buffer_nr' : v:val.action__buffer_nr,
        \}")

  return candidates
endfunction"}}}

" Misc
function! s:make_word(bufnr) abort "{{{
  let filetype = getbufvar(a:bufnr, '&filetype')
  if filetype ==# 'vimfiler'
    let path = unite#util#substitute_path_separator(
          \ simplify(getbufvar(a:bufnr, 'vimfiler').current_dir))
    let path = unite#util#substitute_path_separator(
          \ simplify(bufname(a:bufnr))) . ' ' . path . '/'
  elseif filetype ==# 'vimshell'
    let path = unite#util#substitute_path_separator(
          \ simplify(getbufvar(a:bufnr, 'vimshell').current_dir))
    let path = unite#util#substitute_path_separator(
          \ simplify(bufname(a:bufnr))) . ' ' . path . '/'
  else
    let path = unite#util#substitute_path_separator(
          \ simplify(bufname(a:bufnr)))
  endif

  return path
endfunction"}}}
function! s:make_abbr(bufnr, flags) abort "{{{
  let bufname = fnamemodify(bufname(a:bufnr), ':t')
  if bufname == ''
    let bufname = bufname(a:bufnr)
  endif

  let filetype = getbufvar(a:bufnr, '&filetype')
  if filetype ==# 'vimfiler' || filetype ==# 'vimshell'
    if filetype ==# 'vimfiler'
      let vimfiler = getbufvar(a:bufnr, 'vimfiler')
      let path = vimfiler.current_dir
      if vimfiler.source !=# 'file'
        let path = vimfiler.source . ':' . path
      endif
    else
      let path = simplify(getbufvar(a:bufnr, 'vimshell').current_dir)
    endif

    let path = printf('%s [%s : %s]', bufname, path, filetype)
  else
    let path = bufname(a:bufnr) == '' ? 'No Name' :
          \ simplify(fnamemodify(bufname(a:bufnr), ':~:.'))
    if a:flags != ''
      " Format flags so that buffer numbers are aligned on the left.
      " example: '42 a% +' => '42 a%+ '
      "          '3 h +'   => ' 3 h+  '
      let nowhitespace = substitute(a:flags, '\s*', '', 'g')
      let path = substitute(nowhitespace, '\(\d\+\)\(.*\)',
            \ '\=printf("%*s %-*s", len(bufnr("$")),
            \    submatch(1), 4, submatch(2))', 'g') . path
    endif

    if filetype != ''
      let path .= ' [' . filetype . ']'
    endif
  endif

  return (getbufvar(a:bufnr, '&buftype') =~# 'nofile' ? '[nofile] ' : '' ) .
         \ unite#util#substitute_path_separator(path) . ' '
endfunction"}}}
function! s:compare(candidate_a, candidate_b) abort "{{{
  return a:candidate_a.action__buffer_nr == unite#get_current_unite().prev_bufnr ?  1 :
      \ (a:candidate_b.action__buffer_nr == unite#get_current_unite().prev_bufnr ? -1 :
      \ a:candidate_b.source__time - a:candidate_a.source__time)
endfunction"}}}
function! s:get_buffer_list(is_bang, is_question, is_plus, is_minus, is_terminal) abort "{{{
  " Get :ls flags.
  let flag_dict = {}
  for out in map(split(unite#util#redir('ls'), '\n'), 'split(v:val)')
    let flag_dict[out[0]] = matchstr(join(out), '^.*\ze\s\+"')
  endfor

  " Make buffer list.
  let list = []
  let bufnr = 1
  let buffer_list = unite#sources#buffer#variables#get_buffer_list()
  while bufnr <= bufnr('$')
    if s:is_listed(a:is_bang, a:is_question, a:is_plus, a:is_minus, a:is_terminal, bufnr)
      let dict = get(buffer_list, bufnr, {
            \ 'action__buffer_nr' : bufnr,
            \ 'source__time' : 0,
            \ })
      let dict.source__flags = get(flag_dict, bufnr, '')

      call add(list, dict)
    endif
    let bufnr += 1
  endwhile

  call sort(list, 's:compare')

  return list
endfunction"}}}

function! s:is_listed(is_bang, is_question, is_plus, is_minus, is_terminal, bufnr) abort "{{{
  let bufname = bufname(a:bufnr)
  let buftype = getbufvar(a:bufnr, '&buftype')
  return bufexists(a:bufnr) &&
        \ (a:is_question ? !buflisted(a:bufnr) :
        \    (a:is_bang || buflisted(a:bufnr)))
        \ && (!a:is_plus || getbufvar(a:bufnr, '&mod'))
        \ && (!a:is_minus || buftype == ''
        \                     && bufname != ''
        \                     && !isdirectory(bufname))
        \ && (!a:is_terminal || buftype ==# 'terminal' )
        \ && (getbufvar(a:bufnr, '&filetype') !=# 'unite'
        \      || getbufvar(a:bufnr, 'unite').buffer_name !=#
        \         unite#get_current_unite().buffer_name)
endfunction"}}}

function! s:format_time(time) abort "{{{
  if empty(a:time)
    return ''
  endif
  return strftime(g:unite_source_buffer_time_format, a:time)
endfunction"}}}

let &cpo = s:save_cpo
unlet s:save_cpo

" vim: foldmethod=marker