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

let s:save_cpo = &cpo
set cpo&vim

let s:is_windows = has('win32')
let s:is_cygwin = has('win32unix')
let s:is_mac = !s:is_windows && !s:is_cygwin
      \ && (has('mac') || has('macunix') || has('gui_macvim') ||
      \   (!isdirectory('/proc') && executable('sw_vers')))

function! neobundle#util#substitute_path_separator(path) abort "{{{
  return (s:is_windows && a:path =~ '\\') ?
        \ tr(a:path, '\', '/') : a:path
endfunction"}}}
function! neobundle#util#expand(path) abort "{{{
  let path = (a:path =~ '^\~') ? fnamemodify(a:path, ':p') :
        \ (a:path =~ '^\$\h\w*') ? substitute(a:path,
        \               '^\$\h\w*', '\=eval(submatch(0))', '') :
        \ a:path
  return (s:is_windows && path =~ '\\') ?
        \ neobundle#util#substitute_path_separator(path) : path
endfunction"}}}
function! neobundle#util#join_paths(path1, path2) abort "{{{
  " Joins two paths together, handling the case where the second path
  " is an absolute path.
  if s:is_absolute(a:path2)
    return a:path2
  endif
  if a:path1 =~ (s:is_windows ? '[\\/]$' : '/$') ||
        \ a:path2 =~ (s:is_windows ? '^[\\/]' : '^/')
    " the appropriate separator already exists
    return a:path1 . a:path2
  else
    " note: I'm assuming here that '/' is always valid as a directory
    " separator on Windows. I know Windows has paths that start with \\?\ that
    " diasble behavior like that, but I don't know how Vim deals with that.
    return a:path1 . '/' . a:path2
  endif
endfunction "}}}
if s:is_windows
  function! s:is_absolute(path) abort "{{{
    return a:path =~ '^[\\/]\|^\a:'
  endfunction "}}}
else
  function! s:is_absolute(path) abort "{{{
    return a:path =~ "^/"
  endfunction "}}}
endif

function! neobundle#util#is_windows() abort "{{{
  return s:is_windows
endfunction"}}}
function! neobundle#util#is_mac() abort "{{{
  return s:is_mac
endfunction"}}}
function! neobundle#util#is_cygwin() abort "{{{
  return s:is_cygwin
endfunction"}}}

" Sudo check.
function! neobundle#util#is_sudo() abort "{{{
  return $SUDO_USER != '' && $USER !=# $SUDO_USER
      \ && $HOME !=# expand('~'.$USER)
      \ && $HOME ==# expand('~'.$SUDO_USER)
endfunction"}}}

" Check vimproc. "{{{
function! neobundle#util#has_vimproc() abort "{{{
  if !exists('*vimproc#version')
    try
      call vimproc#version()
    catch
    endtry
  endif

  return exists('*vimproc#version')
endfunction"}}}
"}}}
" iconv() wrapper for safety.
function! s:iconv(expr, from, to) abort "{{{
  if a:from == '' || a:to == '' || a:from ==? a:to
    return a:expr
  endif
  let result = iconv(a:expr, a:from, a:to)
  return result != '' ? result : a:expr
endfunction"}}}
function! neobundle#util#system(str, ...) abort "{{{
  let command = a:str
  let input = a:0 >= 1 ? a:1 : ''
  let command = s:iconv(command, &encoding, 'char')
  let input = s:iconv(input, &encoding, 'char')

  if a:0 == 0
    let output = neobundle#util#has_vimproc() ?
          \ vimproc#system(command) : system(command, "\<C-d>")
  elseif a:0 == 1
    let output = neobundle#util#has_vimproc() ?
          \ vimproc#system(command, input) : system(command, input)
  else
    " ignores 3rd argument unless you have vimproc.
    let output = neobundle#util#has_vimproc() ?
          \ vimproc#system(command, input, a:2) : system(command, input)
  endif

  let output = s:iconv(output, 'char', &encoding)

  return substitute(output, '\n$', '', '')
endfunction"}}}
function! neobundle#util#get_last_status() abort "{{{
  return neobundle#util#has_vimproc() ?
        \ vimproc#get_last_status() : v:shell_error
endfunction"}}}

" Split a comma separated string to a list.
function! neobundle#util#split_rtp(runtimepath) abort "{{{
  if stridx(a:runtimepath, '\,') < 0
    return split(a:runtimepath, ',')
  endif

  let split = split(a:runtimepath, '\\\@<!\%(\\\\\)*\zs,')
  return map(split,'substitute(v:val, ''\\\([\\,]\)'', "\\1", "g")')
endfunction"}}}

function! neobundle#util#join_rtp(list, runtimepath, rtp) abort "{{{
  return (stridx(a:runtimepath, '\,') < 0 && stridx(a:rtp, ',') < 0) ?
        \ join(a:list, ',') : join(map(copy(a:list), 's:escape(v:val)'), ',')
endfunction"}}}

function! neobundle#util#split_envpath(path) abort "{{{
  let delimiter = neobundle#util#is_windows() ? ';' : ':'
  if stridx(a:path, '\' . delimiter) < 0
    return split(a:path, delimiter)
  endif

  let split = split(a:path, '\\\@<!\%(\\\\\)*\zs' . delimiter)
  return map(split,'substitute(v:val, ''\\\([\\'
        \ . delimiter . ']\)'', "\\1", "g")')
endfunction"}}}

function! neobundle#util#join_envpath(list, orig_path, add_path) abort "{{{
  let delimiter = neobundle#util#is_windows() ? ';' : ':'
  return (stridx(a:orig_path, '\' . delimiter) < 0
        \ && stridx(a:add_path, delimiter) < 0) ?
        \   join(a:list, delimiter) :
        \   join(map(copy(a:list), 's:escape(v:val)'), delimiter)
endfunction"}}}

" Removes duplicates from a list.
function! neobundle#util#uniq(list, ...) abort "{{{
  let list = a:0 ? map(copy(a:list), printf('[v:val, %s]', a:1)) : copy(a:list)
  let i = 0
  let seen = {}
  while i < len(list)
    let key = string(a:0 ? list[i][1] : list[i])
    if has_key(seen, key)
      call remove(list, i)
    else
      let seen[key] = 1
      let i += 1
    endif
  endwhile
  return a:0 ? map(list, 'v:val[0]') : list
endfunction"}}}

function! neobundle#util#set_default(var, val, ...) abort  "{{{
  if !exists(a:var) || type({a:var}) != type(a:val)
    let alternate_var = get(a:000, 0, '')

    let {a:var} = exists(alternate_var) ?
          \ {alternate_var} : a:val
  endif
endfunction"}}}
function! neobundle#util#set_dictionary_helper(variable, keys, pattern) abort "{{{
  for key in split(a:keys, '\s*,\s*')
    if !has_key(a:variable, key)
      let a:variable[key] = a:pattern
    endif
  endfor
endfunction"}}}

function! neobundle#util#get_filetypes() abort "{{{
  let filetype = exists('b:neocomplcache.context_filetype') ?
        \ b:neocomplcache.context_filetype : &filetype
  return split(filetype, '\.')
endfunction"}}}

function! neobundle#util#convert2list(expr) abort "{{{
  return type(a:expr) ==# type([]) ? a:expr :
        \ type(a:expr) ==# type('') ?
        \   (a:expr == '' ? [] : split(a:expr, '\r\?\n', 1))
        \ : [a:expr]
endfunction"}}}

function! neobundle#util#print_error(expr) abort "{{{
  return s:echo(a:expr, 'error')
endfunction"}}}

function! neobundle#util#redraw_echo(expr) abort "{{{
  return s:echo(a:expr, 'echo')
endfunction"}}}

function! neobundle#util#redraw_echomsg(expr) abort "{{{
  return s:echo(a:expr, 'echomsg')
endfunction"}}}

function! s:echo(expr, mode) abort "{{{
  let msg = map(neobundle#util#convert2list(a:expr),
        \ "'[neobundle] ' .  v:val")
  if empty(msg)
    return
  endif

  if has('vim_starting') || a:mode ==# 'error'
    call s:echo_mode(join(msg, "\n"), a:mode)
    return
  endif

  let more_save = &more
  let showcmd_save = &showcmd
  let ruler_save = &ruler
  try
    set nomore
    set noshowcmd
    set noruler

    let height = max([1, &cmdheight])
    echo ''
    for i in range(0, len(msg)-1, height)
      redraw

      call s:echo_mode(join(msg[i : i+height-1], "\n"), a:mode)
    endfor
  finally
    let &more = more_save
    let &showcmd = showcmd_save
    let &ruler = ruler_save
  endtry
endfunction"}}}
function! s:echo_mode(m, mode) abort "{{{
  for m in split(a:m, '\r\?\n', 1)
    if !has('vim_starting') && a:mode !=# 'error'
      let m = neobundle#util#truncate_skipping(
            \ m, &columns - 1, &columns/3, '...')
    endif

    if a:mode ==# 'error'
      echohl WarningMsg | echomsg m | echohl None
    elseif a:mode ==# 'echomsg'
      echomsg m
    else
      echo m
    endif
  endfor
endfunction"}}}

function! neobundle#util#name_conversion(path) abort "{{{
  return fnamemodify(split(a:path, ':')[-1], ':s?/$??:t:s?\c\.git\s*$??')
endfunction"}}}

function! neobundle#util#vim2json(expr) abort "{{{
  return has('patch-7.4.1498') ? json_encode(a:expr) : string(a:expr)
endfunction "}}}
function! neobundle#util#json2vim(expr) abort "{{{
  sandbox return has('patch-7.4.1498') ? json_decode(a:expr) : eval(a:expr)
endfunction "}}}

" Escape a path for runtimepath.
function! s:escape(path) abort"{{{
  return substitute(a:path, ',\|\\,\@=', '\\\0', 'g')
endfunction"}}}

function! neobundle#util#unify_path(path) abort "{{{
  return fnamemodify(resolve(a:path), ':p:gs?\\\+?/?')
endfunction"}}}

function! neobundle#util#cd(path) abort "{{{
  if isdirectory(a:path)
    execute (haslocaldir() ? 'lcd' : 'cd') fnameescape(a:path)
  endif
endfunction"}}}

function! neobundle#util#writefile(path, list) abort "{{{
  let path = neobundle#get_neobundle_dir() . '/.neobundle/' . a:path
  let dir = fnamemodify(path, ':h')
  if !isdirectory(dir)
    call mkdir(dir, 'p')
  endif

  return writefile(a:list, path)
endfunction"}}}

function! neobundle#util#cleandir(path) abort "{{{
  let path = neobundle#get_neobundle_dir() . '/.neobundle/' . a:path

  for file in filter(split(globpath(path, '*', 1), '\n'),
        \ '!isdirectory(v:val)')
    call delete(file)
  endfor
endfunction"}}}

function! neobundle#util#rmdir(path) abort "{{{
  if has('patch-7.4.1120')
    call delete(a:path, 'rf')
  else
    let cmdline = '"' . a:path . '"'
    if neobundle#util#is_windows()
      " Note: In rm command, must use "\" instead of "/".
      let cmdline = substitute(cmdline, '/', '\\\\', 'g')
    endif

    " Use system instead of vimproc#system()
    let result = system(g:neobundle#rm_command . ' ' . cmdline)
    if v:shell_error
      call neobundle#installer#error(result)
    endif
  endif
endfunction"}}}

function! neobundle#util#copy_bundle_files(bundles, directory) abort "{{{
  " Delete old files.
  call neobundle#util#cleandir(a:directory)

  let files = {}
  for bundle in a:bundles
    for file in filter(split(globpath(
          \ bundle.rtp, a:directory.'/**', 1), '\n'),
          \ '!isdirectory(v:val)')
      let filename = fnamemodify(file, ':t')
      let files[filename] = readfile(file)
    endfor
  endfor

  for [filename, list] in items(files)
    if filename =~# '^tags\%(-.*\)\?$'
      call sort(list)
    endif
    call neobundle#util#writefile(a:directory . '/' . filename, list)
  endfor
endfunction"}}}
function! neobundle#util#merge_bundle_files(bundles, directory) abort "{{{
  " Delete old files.
  call neobundle#util#cleandir(a:directory)

  let files = []
  for bundle in a:bundles
    for file in filter(split(globpath(
          \ bundle.rtp, a:directory.'/**', 1), '\n'),
          \ '!isdirectory(v:val)')
      let files += readfile(file, ':t')
    endfor
  endfor

  call neobundle#util#writefile(
        \ a:directory.'/'.a:directory . '.vim', files)
endfunction"}}}

" Sorts a list using a set of keys generated by mapping the values in the list
" through the given expr.
" v:val is used in {expr}
function! neobundle#util#sort_by(list, expr) abort "{{{
  let pairs = map(a:list, printf('[v:val, %s]', a:expr))
  return map(s:sort(pairs,
  \      'a:a[1] ==# a:b[1] ? 0 : a:a[1] ># a:b[1] ? 1 : -1'), 'v:val[0]')
endfunction"}}}

" Executes a command and returns its output.
" This wraps Vim's `:redir`, and makes sure that the `verbose` settings have
" no influence.
function! neobundle#util#redir(cmd) abort "{{{
  let [save_verbose, save_verbosefile] = [&verbose, &verbosefile]
  set verbose=0 verbosefile=
  redir => res
    silent! execute a:cmd
  redir END
  let [&verbose, &verbosefile] = [save_verbose, save_verbosefile]
  return res
endfunction"}}}

" Sorts a list with expression to compare each two values.
" a:a and a:b can be used in {expr}.
function! s:sort(list, expr) abort "{{{
  if type(a:expr) == type(function('function'))
    return sort(a:list, a:expr)
  endif
  let s:expr = a:expr
  return sort(a:list, 's:_compare')
endfunction"}}}

function! s:_compare(a, b) abort
  return eval(s:expr)
endfunction

function! neobundle#util#print_bundles(bundles) abort "{{{
  echomsg string(map(copy(a:bundles), 'v:val.name'))
endfunction"}}}

function! neobundle#util#sort_human(filenames) abort "{{{
  return sort(a:filenames, 's:compare_filename')
endfunction"}}}

" Compare filename by human order. "{{{
function! s:compare_filename(i1, i2) abort
  let words_1 = s:get_words(a:i1)
  let words_2 = s:get_words(a:i2)
  let words_1_len = len(words_1)
  let words_2_len = len(words_2)

  for i in range(0, min([words_1_len, words_2_len])-1)
    if words_1[i] >? words_2[i]
      return 1
    elseif words_1[i] <? words_2[i]
      return -1
    endif
  endfor

  return words_1_len - words_2_len
endfunction"}}}

function! s:get_words(filename) abort "{{{
  let words = []
  for split in split(a:filename, '\d\+\zs\ze')
    let words += split(split, '\D\zs\ze\d\+')
  endfor

  return map(words, "v:val =~ '^\\d\\+$' ? str2nr(v:val) : v:val")
endfunction"}}}

function! neobundle#util#wget(uri, outpath) abort "{{{
  if executable('curl')
    return printf('curl --fail -s -o "%s" "%s"', a:outpath, a:uri)
  elseif executable('wget')
    return printf('wget -q -O "%s" "%s"', a:outpath, a:uri)
  else
    return 'E: curl or wget command is not available!'
  endif
endfunction"}}}

function! neobundle#util#truncate_skipping(str, max, footer_width, separator) abort "{{{
  let width = s:wcswidth(a:str)
  if width <= a:max
    let ret = a:str
  else
    let header_width = a:max - s:wcswidth(a:separator) - a:footer_width
    let ret = s:strwidthpart(a:str, header_width) . a:separator
          \ . s:strwidthpart_reverse(a:str, a:footer_width)
  endif

  return ret
endfunction"}}}
function! s:strwidthpart(str, width) abort "{{{
  if a:width <= 0
    return ''
  endif
  let ret = a:str
  let width = s:wcswidth(a:str)
  while width > a:width
    let char = matchstr(ret, '.$')
    let ret = ret[: -1 - len(char)]
    let width -= s:wcswidth(char)
  endwhile

  return ret
endfunction"}}}
function! s:strwidthpart_reverse(str, width) abort "{{{
  if a:width <= 0
    return ''
  endif
  let ret = a:str
  let width = s:wcswidth(a:str)
  while width > a:width
    let char = matchstr(ret, '^.')
    let ret = ret[len(char) :]
    let width -= s:wcswidth(char)
  endwhile

  return ret
endfunction"}}}
function! s:wcswidth(str) abort "{{{
  return v:version >= 704 ? strwidth(a:str) : strlen(a:str)
endfunction"}}}

let &cpo = s:save_cpo
unlet s:save_cpo

" vim: foldmethod=marker