"=============================================================================
" FILE: view.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_stay_insert =
      \ get(g:, 'vimshell_enable_stay_insert', 1)
"}}}

function! vimshell#view#_get_prompt(...) abort "{{{
  let line = get(a:000, 0, line('.'))
  let interactive = get(a:000, 1,
        \ (exists('b:interactive') ? b:interactive : {}))
  if empty(interactive)
    return ''
  endif

  if &filetype ==# 'vimshell' &&
        \ empty(b:vimshell.continuation)
    let context = vimshell#get_context()
    if context.prompt_expr != '' && context.prompt_pattern != ''
      return eval(context.prompt_expr)
    endif

    return context.prompt
  endif

  return vimshell#interactive#get_prompt(line, interactive)
endfunction"}}}
function! vimshell#view#_set_prompt_command(string) abort "{{{
  if !vimshell#check_prompt()
    " Search prompt.
    let lnum = searchpos(vimshell#get_context().prompt_pattern, 'bnW')[0]
  else
    let lnum = '.'
  endif

  call setline(lnum, vimshell#get_prompt() . a:string)
endfunction"}}}
function! vimshell#view#_get_prompt_command(...) abort "{{{
  " Get command without prompt.
  if a:0 > 0
    return a:1[vimshell#get_prompt_length(a:1) :]
  endif

  if !vimshell#check_prompt()
    " Search prompt.
    let lnum = searchpos(vimshell#get_context().prompt_pattern, 'bnW')[0]
  else
    let lnum = '.'
  endif
  let line = getline(lnum)[vimshell#get_prompt_length(getline(lnum)) :]

  let lnum += 1
  let secondary_prompt = vimshell#get_secondary_prompt()
  while lnum <= line('$') && !vimshell#check_prompt(lnum)
    if vimshell#view#_check_secondary_prompt(lnum)
      " Append secondary command.
      if line =~ '\\$'
        let line = substitute(line, '\\$', '', '')
      else
        let line .= "\<NL>"
      endif

      let line .= getline(lnum)[len(secondary_prompt):]
    endif

    let lnum += 1
  endwhile

  return line
endfunction"}}}
function! vimshell#view#_set_highlight() abort "{{{
  " Set syntax.
  let prompt_pattern = '/' .
        \ escape(vimshell#get_context().prompt_pattern, '/') . '/'
  let secondary_prompt_pattern = '/^' .
        \ escape(vimshell#util#escape_match(
        \ vimshell#get_secondary_prompt()), '/') . '/'
  execute 'syntax match vimshellPrompt'
        \ prompt_pattern 'nextgroup=vimshellCommand'
  execute 'syntax match vimshellPrompt'
        \ secondary_prompt_pattern 'nextgroup=vimshellCommand'
  syntax match   vimshellCommand '\s*\f\+'
        \ nextgroup=vimshellLine contained
  syntax region vimshellLine start='' end='$' keepend contained
        \ contains=vimshellDirectory,vimshellConstants,
        \vimshellArguments,vimshellQuoted,vimshellString,
        \vimshellVariable,vimshellSpecial,vimshellComment
endfunction"}}}
function! vimshell#view#_close(buffer_name) abort "{{{
  let quit_winnr = -1
  if a:buffer_name != ''
    let quit_winnr = vimshell#util#get_vimshell_winnr(a:buffer_name)
  elseif exists('t:vimshell')
    let quit_winnr = bufwinnr(t:vimshell.last_interactive_bufnr)
  endif

  if quit_winnr > 0
    " Hide unite buffer.
    silent execute quit_winnr 'wincmd w'

    if winnr('$') != 1
      close
    else
      call vimshell#util#alternate_buffer()
    endif
  endif

  return quit_winnr > 0
endfunction"}}}
function! vimshell#view#_print_prompt(...) abort "{{{
  if &filetype !=# 'vimshell' || line('.') != line('$')
        \ || !empty(b:vimshell.continuation)
    return
  endif

  " Save current directory.
  let b:vimshell.prompt_current_dir[s:get_prompt_linenr()] = getcwd()

  let context = a:0 >= 1? a:1 : vimshell#get_context()

  " Call preprompt hook.
  call vimshell#hook#call('preprompt', context, [])

  " Search prompt
  if empty(b:vimshell.commandline_stack)
    let new_prompt = vimshell#get_prompt()
  else
    let new_prompt = b:vimshell.commandline_stack[-1]
    call remove(b:vimshell.commandline_stack, -1)
  endif

  if vimshell#get_user_prompt() != '' ||
        \ vimshell#get_right_prompt() != ''
    " Insert user prompt line.
    call s:insert_user_and_right_prompt()
  endif

  " Insert prompt line.
  if getline('$') == ''
    call setline('$', new_prompt)
  else
    call append('$', new_prompt)
  endif

  call cursor(line('$'), 0)
  call cursor(0, col('$'))
  let &modified = 0
endfunction"}}}
function! vimshell#view#_print_secondary_prompt() abort "{{{
  if &filetype !=# 'vimshell' || line('.') != line('$')
    return
  endif

  " Insert secondary prompt line.
  call append('$', vimshell#get_secondary_prompt())
  call cursor(line('$'), 0)
  call cursor(0, col('$'))
  let &modified = 0
endfunction"}}}
function! vimshell#view#_start_insert(...) abort "{{{
  if &filetype !=# 'vimshell'
    return
  endif

  let is_insert = (a:0 == 0)? 1 : a:1

  call cursor(line('$'), 0)
  call cursor(0, col('$'))

  if is_insert
    " Enter insert mode.
    call vimshell#view#_simple_insert()

    call vimshell#helpers#imdisable()
  endif
endfunction"}}}
function! vimshell#view#_simple_insert(...) abort "{{{
  let is_insert = (a:0 == 0)? 1 : a:1

  if is_insert && g:vimshell_enable_stay_insert
    startinsert!
    startinsert!
  else
    stopinsert
    call cursor(0, col('$'))
  endif
endfunction"}}}
function! vimshell#view#_cd(directory) abort "{{{
  let directory = fnameescape(a:directory)
  if vimshell#util#is_windows()
    " Substitute path sepatator.
    let directory = substitute(directory, '/', '\\', 'g')
  endif
  execute g:vimshell_cd_command directory

  if exists('*unite#sources#directory_mru#_append')
    " Append directory.
    call unite#sources#directory_mru#_append()
  endif
endfunction"}}}
function! vimshell#view#_next_prompt(context, ...) abort "{{{
  if &filetype !=# 'vimshell'
    return
  endif

  let is_insert = get(a:000, 0, get(a:context, 'is_insert', 1))

  if line('.') == line('$')
    call vimshell#print_prompt(a:context)

    if b:vimshell.context.quit
      if winnr('$') != 1
        if b:vimshell.context.popup
          wincmd p
        else
          close
        endif
      else
        call vimshell#util#alternate_buffer()
      endif

      " It is dirty hack.
      " But :stopinsert does not work..
      call feedkeys("\<ESC>", 'n')
    else
      call vimshell#start_insert(is_insert)
    endif

    return
  endif

  " Search prompt.
  let pos = searchpos(vimshell#get_context().prompt_pattern.'.\?', 'We')
  call cursor(pos[0], pos[1])
  if is_insert
    if vimshell#view#_get_prompt_command() == ''
      startinsert!
    else
      call cursor(0, col('.')+1)
    endif
  endif

  stopinsert
endfunction"}}}

function! s:insert_user_and_right_prompt() abort "{{{
  let user_prompt = vimshell#get_user_prompt()
  if user_prompt != ''
    for user in split(eval(user_prompt), "\\n", 1)
      try
        let secondary = '[%] ' . user
      catch
        let message = v:exception . ' ' . v:throwpoint
        echohl WarningMsg | echomsg message | echohl None

        let secondary = '[%] '
      endtry

      if getline('$') == ''
        call setline('$', secondary)
      else
        call append('$', secondary)
      endif
    endfor
  endif

  " Insert right prompt line.
  if vimshell#get_right_prompt() == ''
    return
  endif

  try
    let right_prompt = eval(vimshell#get_right_prompt())
    let b:vimshell.right_prompt = right_prompt
  catch
    let message = v:exception . ' ' . v:throwpoint
    echohl WarningMsg | echomsg message | echohl None

    let right_prompt = ''
  endtry

  if right_prompt == ''
    return
  endif

  let user_prompt_last = (user_prompt != '') ?
        \   getline('$') : '[%] '
  let winwidth = (vimshell#helpers#get_winwidth()+1)/2*2
  let padding_len =
        \ (len(user_prompt_last)+len(vimshell#get_right_prompt())+1
        \          > winwidth) ?
        \ 1 : winwidth - (len(user_prompt_last)+len(right_prompt))
  let secondary = printf('%s%s%s', user_prompt_last,
        \ repeat(' ', padding_len), right_prompt)
  if getline('$') == '' || vimshell#get_user_prompt() != ''
    call setline('$', secondary)
  else
    call append('$', secondary)
  endif

  let prompts_save = {}
  let prompts_save.right_prompt = right_prompt
  let prompts_save.user_prompt_last = user_prompt_last
  let prompts_save.winwidth = winwidth
  let b:vimshell.prompts_save[line('$')] = prompts_save
endfunction"}}}

function! vimshell#view#_check_prompt(...) abort "{{{
  if &filetype !=# 'vimshell' || !empty(b:vimshell.continuation)
    return call('vimshell#get_prompt', a:000) != ''
  endif

  let line = a:0 == 0 ? getline('.') : getline(a:1)
  return line =~# vimshell#get_context().prompt_pattern
endfunction"}}}
function! vimshell#view#_check_secondary_prompt(...) abort "{{{
  let line = a:0 == 0 ? getline('.') : getline(a:1)
  return vimshell#util#head_match(line, vimshell#get_secondary_prompt())
endfunction"}}}
function! vimshell#view#_check_user_prompt(...) abort "{{{
  let line = a:0 == 0 ? line('.') : a:1
  if !vimshell#util#head_match(getline(line-1), '[%] ')
    " Not found.
    return 0
  endif

  while 1
    let line -= 1

    if !vimshell#util#head_match(getline(line-1), '[%] ')
      break
    endif
  endwhile

  return line
endfunction"}}}

function! s:get_prompt_linenr() abort "{{{
  if b:interactive.type !=# 'interactive'
        \ && b:interactive.type !=# 'vimshell'
    return 0
  endif

  return searchpos(vimshell#get_context().prompt_pattern, 'nbcW')[0]
endfunction"}}}

let &cpo = s:save_cpo
unlet s:save_cpo

" vim: foldmethod=marker