"=============================================================================
" FILE: init.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

" Global options definition. "{{{
let g:vimshell_enable_start_insert =
      \ get(g:, 'vimshell_enable_start_insert', 1)
"}}}

let s:manager = vimshell#util#get_vital().import('Vim.Buffer')

function! vimshell#init#_start(path, ...) "{{{
  " Check vimproc. "{{{
  if !vimshell#util#has_vimproc()
    call vimshell#echo_error(v:errmsg)
    call vimshell#echo_error(v:exception)
    call vimshell#echo_error('Error occurred while loading vimproc.')
    call vimshell#echo_error('Please install vimproc Ver.6.0 or above.')
    return
  elseif vimproc#version() < 600
    call vimshell#echo_error('Your vimproc is too old.')
    call vimshell#echo_error('Please install vimproc Ver.6.0 or above.')
    return
  endif"}}}

  if vimshell#util#is_cmdwin()
    call vimshell#echo_error(
          \ '[vimshell] Command line buffer is detected!')
    call vimshell#echo_error(
          \ '[vimshell] Please close command line buffer.')
    return
  endif

  " Detect autochdir option. "{{{
  if exists('+autochdir') && &autochdir
    call vimshell#echo_error(
          \ '[vimshell] Detected autochdir!')
    call vimshell#echo_error(
          \ '[vimshell] vimshell does''t work if you set autochdir option.')
    return
  endif
  "}}}

  let path = a:path
  if path != ''
    let path = vimshell#util#substitute_path_separator(
          \ fnamemodify(vimshell#util#expand(a:path), ':p'))

    if !isdirectory(path)
      call vimshell#echo_error(
            \ printf('[vimshell] argument path: "%s" invalid!', path))
      " Don't use argument path.
      let path = ''
    endif
  endif

  let context = vimshell#init#_context(get(a:000, 0, {}))

  if context.create
    " Create shell buffer.
    call s:create_shell(path, context)
    return
  elseif context.toggle
        \ && vimshell#view#_close(context.buffer_name)
    return
  elseif &filetype ==# 'vimshell'
    " Search vimshell buffer.
    call s:switch_vimshell(bufnr('%'), context, path)
    return
  endif

  if !exists('t:vimshell')
    call vimshell#init#tab_variable()
  endif
  for bufnr in filter(insert(range(1, bufnr('$')),
        \ t:vimshell.last_vimshell_bufnr),
        \ "buflisted(v:val) &&
        \  getbufvar(v:val, '&filetype') ==# 'vimshell'")
    if (!exists('t:tabpagebuffer')
          \    || has_key(t:tabpagebuffer, bufnr))
      call s:switch_vimshell(bufnr, context, path)
      return
    endif
  endfor

  " Create shell buffer.
  call s:create_shell(path, context)
endfunction"}}}

function! vimshell#init#_context(context) "{{{
  let default_context = {
    \ 'buffer_name' : 'default',
    \ 'quit' : 0,
    \ 'toggle' : 0,
    \ 'create' : 0,
    \ 'simple' : 0,
    \ 'split' : 0,
    \ 'popup' : 0,
    \ 'winwidth' : 0,
    \ 'winminwidth' : 0,
    \ 'project' : 0,
    \ 'tab' : 0,
    \ 'direction' : '',
    \ 'prompt' : get(g:,
    \      'vimshell_prompt', 'vimshell% '),
    \ 'prompt_expr' : get(g:,
    \      'vimshell_prompt_expr', ''),
    \ 'prompt_pattern' : get(g:,
    \      'vimshell_prompt_pattern', ''),
    \ 'secondary_prompt' : get(g:,
    \      'vimshell_secondary_prompt', '%% '),
    \ 'user_prompt' : get(g:,
    \      'vimshell_user_prompt', ''),
    \ 'right_prompt' : get(g:,
    \      'vimshell_right_prompt', ''),
    \ }
  let context = extend(default_context, a:context)

  " Complex initializer.
  if !has_key(context, 'profile_name')
    let context.profile_name = context.buffer_name
  endif
  if !has_key(context, 'split_command')
    if context.popup && g:vimshell_popup_command == ''
      " Default popup command.
      let context.split_command = 'split | resize '
            \ . winheight(0)*g:vimshell_popup_height/100
    elseif context.popup
      let context.split_command = g:vimshell_popup_command
    elseif context.split
      let context.split_command = g:vimshell_split_command
    else
      let context.split_command = ''
    endif
  endif

  " Set prompt pattern.
  if context.prompt_pattern == ''
    if context.prompt_expr != ''
      " Error.
      call vimshell#echo_error(
            \ 'Your prompt_pattern is invalid. '.
            \ 'You must set prompt_pattern in vimshell.')
    endif

    let context.prompt_pattern =
          \ '^' . vimshell#util#escape_match(context.prompt)
  endif

  if &l:modified && !&l:hidden
    " Split automatically.
    let context.split = 1
  endif

  " Initialize.
  let context.has_head_spaces = 0
  let context.is_interactive = 1
  let context.is_insert = 1
  let context.fd = { 'stdin' : '', 'stdout': '', 'stderr': ''}

  return context
endfunction"}}}

function! vimshell#init#_internal_commands(command) "{{{
  " Initialize internal commands table.
  let internal_commands = vimshell#variables#internal_commands()

  if has_key(internal_commands, a:command) || a:command =~ '\.'
    return get(internal_commands, a:command, {})
  endif

  " Search autoload.
  for list in split(globpath(&runtimepath,
        \ 'autoload/vimshell/commands/' . a:command . '*.vim'), '\n')
    let command_name = fnamemodify(list, ':t:r')
    if command_name == '' ||
          \ has_key(internal_commands, command_name)
      continue
    endif

    let result = {'vimshell#commands#'.command_name.'#define'}()

    for command in (type(result) == type([])) ?
          \ result : [result]
      if !has_key(command, 'description')
        let command.description = ''
      endif

      let internal_commands[command.name] = command
    endfor

    unlet result
  endfor

  return get(internal_commands, a:command, {})
endfunction"}}}

function! vimshell#init#_default_settings() "{{{
  " Common.
  setlocal bufhidden=hide
  setlocal buftype=nofile
  setlocal nolist
  setlocal noswapfile
  setlocal tabstop=8
  setlocal foldcolumn=0
  setlocal foldmethod=manual
  setlocal winfixheight
  setlocal noreadonly
  setlocal iskeyword+=-,+,\\,!,~
  setlocal textwidth=0
  if has('conceal')
    setlocal conceallevel=3
    setlocal concealcursor=nvi
  endif
  if exists('&colorcolumn')
    setlocal colorcolumn=
  endif
endfunction"}}}

function! vimshell#init#tab_variable() "{{{
  let t:vimshell = {
        \ 'last_vimshell_bufnr' : -1,
        \ 'last_interactive_bufnr' : -1,
        \ }
endfunction"}}}

function! s:create_shell(path, context) "{{{
  let path = a:path
  if path == ''
    " Use current directory.
    let path = vimshell#util#substitute_path_separator(getcwd())
  endif

  if a:context.project
    let path = vimshell#util#path2project_directory(path)
  endif

  " Create new buffer.
  let prefix = '[vimshell] - '
  let prefix .= a:context.profile_name
  let postfix = s:get_postfix(prefix, 1)
  let bufname = prefix . postfix

  if a:context.split_command != ''
    call vimshell#helpers#split(a:context.split_command)
  endif

  " Save swapfile option.
  let swapfile_save = &swapfile
  set noswapfile

  try
    let loaded = s:manager.open(bufname, 'silent edit')
  finally
    let &swapfile = swapfile_save
  endtry

  if !loaded
    call vimshell#echo_error(
          \ '[vimshell] Failed to open Buffer.')
    return
  endif

  call s:initialize_vimshell(path, a:context)
  call vimshell#interactive#set_send_buffer(bufname('%'))

  call vimshell#print_prompt(a:context)

  if g:vimshell_enable_start_insert
    call vimshell#start_insert()
  endif

  " Check prompt value. "{{{
  let prompt = vimshell#get_prompt()
  if vimshell#util#head_match(prompt, vimshell#get_secondary_prompt())
        \ || vimshell#util#head_match(vimshell#get_secondary_prompt(), prompt)
    call vimshell#echo_error(printf('Head matched g:vimshell_prompt("%s")'.
          \ ' and your g:vimshell_secondary_prompt("%s").',
          \ prompt, vimshell#get_secondary_prompt()))
    finish
  elseif vimshell#util#head_match(prompt, '[%] ')
        \ || vimshell#util#head_match('[%] ', prompt)
    call vimshell#echo_error(printf('Head matched g:vimshell_prompt("%s")'.
          \ ' and your g:vimshell_user_prompt("[%] ").', prompt))
    finish
  elseif vimshell#util#head_match('[%] ', vimshell#get_secondary_prompt())
        \ || vimshell#util#head_match(vimshell#get_secondary_prompt(), '[%] ')
    call vimshell#echo_error(printf('Head matched g:vimshell_user_prompt("[%] ")'.
          \ ' and your g:vimshell_secondary_prompt("%s").',
          \ vimshell#get_secondary_prompt()))
    finish
  endif"}}}
endfunction"}}}

function! s:switch_vimshell(bufnr, context, path) "{{{
  if bufwinnr(a:bufnr) > 0
    execute bufwinnr(a:bufnr) 'wincmd w'
  else
    if a:context.split_command != ''
      call vimshell#helpers#split(a:context.split_command)
    endif

    execute 'buffer' a:bufnr
  endif

  if !empty(b:vimshell.continuation)
    return
  endif

  if a:path != '' && isdirectory(a:path)
    " Change current directory.
    let current = fnamemodify(a:path, ':p')
    let b:vimshell.current_dir = current
    call vimshell#cd(current)
  endif

  if getline('$') =~# a:context.prompt_pattern.'$'
    " Delete current prompt.
    let promptnr = vimshell#view#_check_user_prompt(line('$')) > 0 ?
          \ vimshell#view#_check_user_prompt(line('$')) . ',' : ''
    silent execute promptnr . '$delete _'
  endif

  normal! zb

  call vimshell#print_prompt()
  if g:vimshell_enable_start_insert
    call vimshell#start_insert()
  endif
endfunction"}}}

function! s:initialize_vimshell(path, context) "{{{
  " Load history.
  let g:vimshell#hist_buffer = vimshell#history#read()

  " Initialize variables.
  let b:vimshell = {}

  let b:vimshell.current_dir = a:path
  let b:vimshell.alias_table = {}
  let b:vimshell.galias_table = {}
  let b:vimshell.altercmd_table = {}
  let b:vimshell.commandline_stack = []
  let b:vimshell.variables = {}
  let b:vimshell.system_variables = { 'status' : 0 }
  let b:vimshell.directory_stack = []
  let b:vimshell.prompt_current_dir = {}
  let b:vimshell.continuation = {}
  let b:vimshell.prompts_save = {}
  let b:vimshell.statusline =
        \ '*vimshell* : %{vimshell#get_status_string()}' .
        \ "\ %=%{exists('b:vimshell') ? printf('%s %4d/%d',
        \  b:vimshell.right_prompt, line('.'), line('$')) : ''}"
  let b:vimshell.right_prompt = ''

  " Default settings.
  call s:default_settings()

  " Change current directory.
  call vimshell#cd(a:path)

  call vimshell#set_context(a:context)

  " Set interactive variables.
  let b:interactive = {
        \ 'type' : 'vimshell',
        \ 'syntax' : 'vimshell',
        \ 'process' : {},
        \ 'continuation' : {},
        \ 'fd' : a:context.fd,
        \ 'encoding' : &encoding,
        \ 'is_pty' : 0,
        \ 'echoback_linenr' : -1,
        \ 'stdout_cache' : '',
        \ 'stderr_cache' : '',
        \ 'width' : vimshell#helpers#get_winwidth(),
        \ 'height' : g:vimshell_scrollback_limit,
        \ 'hook_functions_table' : {},
        \}

  " Load rc file.
  if filereadable(g:vimshell_vimshrc_path)
    call vimshell#helpers#execute_internal_command('vimsh',
          \ [g:vimshell_vimshrc_path],
          \ { 'has_head_spaces' : 0, 'is_interactive' : 0 })
    let b:vimshell.loaded_vimshrc = 1
  endif

  setfiletype vimshell

  call vimshell#help#init()
  call vimshell#interactive#init()

  call vimshell#handlers#_restore_statusline()
endfunction"}}}

function! s:default_settings() "{{{
  " Common.
  call vimshell#init#_default_settings()

  " Set autocommands.
  augroup vimshell
    autocmd BufDelete,VimLeavePre <buffer>
          \ call vimshell#interactive#hang_up(expand('<afile>'))
    autocmd BufEnter,BufWinEnter,WinEnter,BufRead <buffer>
          \ call vimshell#handlers#_on_bufwin_enter(expand('<abuf>'))
  augroup end

  call vimshell#handlers#_on_bufwin_enter(bufnr('%'))

  " Define mappings.
  call vimshell#mappings#define_default_mappings()
endfunction"}}}
function! s:get_postfix(prefix, is_create) "{{{
  let buffers = get(a:000, 0, range(1, bufnr('$')))
  let buflist = vimshell#util#sort_by(filter(map(buffers,
        \ 'bufname(v:val)'), 'stridx(v:val, a:prefix) >= 0'),
        \ "str2nr(matchstr(v:val, '\\d\\+$'))")
  if empty(buflist)
    return ''
  endif

  let num = matchstr(buflist[-1], '@\zs\d\+$')
  return num == '' && !a:is_create ? '' :
        \ '@' . (a:is_create ? (num + 1) : num)
endfunction"}}}

let &cpo = s:save_cpo
unlet s:save_cpo

" vim: foldmethod=marker
