" ============================================================================
" CLASS: Creator
"
" This class is responsible for creating NERDTree instances.  The new NERDTree
" may be a tab tree, a window tree, or a mirrored tree.  In the process of
" creating a NERDTree, it sets up all of the window and buffer options and key
" mappings etc.
" ============================================================================


let s:Creator = {}
let g:NERDTreeCreator = s:Creator

" FUNCTION: s:Creator._bindMappings() {{{1
function! s:Creator._bindMappings()
    call g:NERDTreeKeyMap.BindAll()

    command! -buffer -nargs=? Bookmark :call nerdtree#ui_glue#bookmarkNode('<args>')
    command! -buffer -complete=customlist,nerdtree#completeBookmarks -nargs=1 RevealBookmark :call nerdtree#ui_glue#revealBookmark('<args>')
    command! -buffer -complete=customlist,nerdtree#completeBookmarks -nargs=1 OpenBookmark call nerdtree#ui_glue#openBookmark('<args>')
    command! -buffer -complete=customlist,nerdtree#completeBookmarks -nargs=* ClearBookmarks call nerdtree#ui_glue#clearBookmarks('<args>')
    command! -buffer -complete=customlist,nerdtree#completeBookmarks -nargs=+ BookmarkToRoot call g:NERDTreeBookmark.ToRoot('<args>', b:NERDTree)
    command! -buffer -nargs=0 ClearAllBookmarks call g:NERDTreeBookmark.ClearAll() <bar> call b:NERDTree.render()
    command! -buffer -nargs=0 ReadBookmarks call g:NERDTreeBookmark.CacheBookmarks(0) <bar> call b:NERDTree.render()
    command! -buffer -nargs=0 WriteBookmarks call g:NERDTreeBookmark.Write()
    command! -buffer -nargs=0 EditBookmarks call g:NERDTreeBookmark.Edit()
endfunction

" FUNCTION: s:Creator._broadcastInitEvent() {{{1
function! s:Creator._broadcastInitEvent()
    if exists('#User#NERDTreeInit')
        doautocmd User NERDTreeInit
    endif
endfunction

" FUNCTION: s:Creator.BufNamePrefix() {{{1
function! s:Creator.BufNamePrefix()
    return 'NERD_tree_'
endfunction

" FUNCTION: s:Creator.CreateExploreTree(dir) {{{1
function! s:Creator.CreateExploreTree(dir)
    try
        let path = g:NERDTreePath.New(a:dir)
    catch /^NERDTree.InvalidArgumentsError/
        call nerdtree#echo('Invalid directory name:' . a:dir)
        return
    endtry

    let creator = s:Creator.New()
    if getbufinfo('%')[0].changed && !&hidden && !&autowriteall
        let l:splitLocation = g:NERDTreeWinPos ==# 'left' || g:NERDTreeWinPos ==# 'top' ? 'topleft ' : 'botright '
        let l:splitDirection = g:NERDTreeWinPos ==# 'left' || g:NERDTreeWinPos ==# 'right' ? 'vertical' : ''
        silent! execute l:splitLocation . l:splitDirection . ' new'
    else
        silent! execute 'enew'
    endif

    call creator.createWindowTree(a:dir)
    "we want windowTree buffer to disappear after moving to any other buffer
    setlocal bufhidden=wipe
endfunction

" FUNCTION: s:Creator.CreateTabTree(a:name) {{{1
function! s:Creator.CreateTabTree(name)
    let creator = s:Creator.New()
    call creator.createTabTree(a:name)
endfunction

" FUNCTION: s:Creator.createTabTree(a:name) {{{1
" name: the name of a bookmark or a directory
function! s:Creator.createTabTree(name)
    let l:path = self._pathForString(a:name)

    " Abort if an exception was thrown (i.e., if the bookmark or directory
    " does not exist).
    if empty(l:path)
        return
    endif

    " Obey the user's preferences for changing the working directory.
    if g:NERDTreeChDirMode != 0
        call l:path.changeToDir()
    endif

    if g:NERDTree.ExistsForTab()
        call g:NERDTree.Close()
        call self._removeTreeBufForTab()
    endif

    call self._createTreeWin()
    call self._createNERDTree(l:path, 'tab')
    call b:NERDTree.render()
    call b:NERDTree.root.putCursorHere(0, 0)

    call self._broadcastInitEvent()
endfunction

" FUNCTION: s:Creator.CreateWindowTree(dir) {{{1
function! s:Creator.CreateWindowTree(dir)
    let creator = s:Creator.New()
    call creator.createWindowTree(a:dir)
endfunction

" FUNCTION: s:Creator.createWindowTree(dir) {{{1
function! s:Creator.createWindowTree(dir)
    try
        let path = g:NERDTreePath.New(a:dir)
    catch /^NERDTree.InvalidArgumentsError/
        call nerdtree#echo('Invalid directory name:' . a:dir)
        return
    endtry

    "we want the directory buffer to disappear when we do the :edit below
    setlocal bufhidden=wipe

    let previousBuf = expand('#')

    "we need a unique name for each window tree buffer to ensure they are
    "all independent
    exec g:NERDTreeCreatePrefix . ' edit ' . self._nextBufferName('win')

    call self._createNERDTree(path, 'window')
    let b:NERDTree._previousBuf = bufnr(previousBuf)
    call self._setCommonBufOptions()

    call b:NERDTree.render()

    call self._broadcastInitEvent()
endfunction

" FUNCTION: s:Creator._createNERDTree(path) {{{1
function! s:Creator._createNERDTree(path, type)
    let b:NERDTree = g:NERDTree.New(a:path, a:type)

    " TODO: This assignment is kept for compatibility reasons.  Many other
    " plugins use b:NERDTreeRoot instead of b:NERDTree.root.  Remove this
    " assignment in the future.
    let b:NERDTreeRoot = b:NERDTree.root

    call b:NERDTree.root.open()
endfunction

" FUNCTION: s:Creator.CreateMirror() {{{1
function! s:Creator.CreateMirror()
    let creator = s:Creator.New()
    call creator.createMirror()
endfunction

" FUNCTION: s:Creator.createMirror() {{{1
function! s:Creator.createMirror()
    "get the names off all the nerd tree buffers
    let treeBufNames = []
    for i in range(1, tabpagenr('$'))
        let nextName = self._tabpagevar(i, 'NERDTreeBufName')
        if nextName != -1 && (!exists('t:NERDTreeBufName') || nextName != t:NERDTreeBufName)
            call add(treeBufNames, nextName)
        endif
    endfor
    let treeBufNames = self._uniq(treeBufNames)

    "map the option names (that the user will be prompted with) to the nerd
    "tree buffer names
    let options = {}
    let i = 0
    while i < len(treeBufNames)
        let bufName = treeBufNames[i]
        let treeRoot = getbufvar(bufName, 'NERDTree').root
        let options[i+1 . '. ' . treeRoot.path.str() . '  (buf name: ' . bufName . ')'] = bufName
        let i = i + 1
    endwhile

    "work out which tree to mirror, if there is more than 1 then ask the user
    let bufferName = ''
    if len(keys(options)) > 1
        let choices = ['Choose a tree to mirror']
        let choices = extend(choices, sort(keys(options)))
        let choice = inputlist(choices)
        if choice < 1 || choice > len(options) || choice ==# ''
            return
        endif

        let bufferName = options[sort(keys(options))[choice-1]]
    elseif len(keys(options)) ==# 1
        let bufferName = values(options)[0]
    else
        call nerdtree#echo('No trees to mirror')
        return
    endif

    if g:NERDTree.ExistsForTab() && g:NERDTree.IsOpen()
        call g:NERDTree.Close()
    endif

    let t:NERDTreeBufName = bufferName
    call self._createTreeWin()
    exec 'buffer ' .  bufferName
    call b:NERDTree.ui.restoreScreenState()
    if !&hidden
        call b:NERDTree.render()
    endif
endfunction

" FUNCTION: s:Creator._createTreeWin() {{{1
" Initialize the NERDTree window.  Open the window, size it properly, set all
" local options, etc.
function! s:Creator._createTreeWin()
    let l:splitLocation = g:NERDTreeWinPos ==# 'left' || g:NERDTreeWinPos ==# 'top' ? 'topleft ' : 'botright '
    let l:splitDirection = g:NERDTreeWinPos ==# 'left' || g:NERDTreeWinPos ==# 'right' ? 'vertical' : ''
    let l:splitSize = g:NERDTreeWinSize

    if !g:NERDTree.ExistsForTab()
        let t:NERDTreeBufName = self._nextBufferName('tab')
        silent! execute l:splitLocation . l:splitDirection . ' ' . l:splitSize . ' new'
        silent! execute 'edit ' . t:NERDTreeBufName
        silent! execute l:splitDirection . ' resize '. l:splitSize
    else
        silent! execute l:splitLocation . l:splitDirection . ' ' . l:splitSize . ' split'
        silent! execute 'buffer ' . t:NERDTreeBufName
    endif

    setlocal winfixwidth

    call self._setCommonBufOptions()

    if has('patch-7.4.1925')
        clearjumps
    endif

endfunction

" FUNCTION: s:Creator._isBufHidden(nr) {{{1
function! s:Creator._isBufHidden(nr)
    redir => bufs
    silent ls!
    redir END

    return bufs =~ a:nr . '..h'
endfunction

" FUNCTION: s:Creator.New() {{{1
function! s:Creator.New()
    let newCreator = copy(self)
    return newCreator
endfunction

" FUNCTION: s:Creator._nextBufferName(type='') {{{1
" gets an optional buffer type of either 'tab' or 'win'.
" returns the buffer name for the next nerd tree of such type.
function! s:Creator._nextBufferName(...)
    if a:0 > 0
        let type = a:1
    else
        let type = ''
    end
    let name = s:Creator.BufNamePrefix()
    if type ==# 'tab'
        let name = name . 'tab_'
    elseif type ==# 'win'
        let name = name . 'win_'
    endif
    let name = name . self._nextBufferNumber()
    return name
endfunction

" FUNCTION: s:Creator._nextBufferNumber() {{{1
" the number to add to the nerd tree buffer name to make the buf name unique
function! s:Creator._nextBufferNumber()
    if !exists('s:Creator._NextBufNum')
        let s:Creator._NextBufNum = 1
    else
        let s:Creator._NextBufNum += 1
    endif

    return s:Creator._NextBufNum
endfunction

" FUNCTION: s:Creator._pathForString(str) {{{1
" find a bookmark or adirectory for the given string
function! s:Creator._pathForString(str)
    let path = {}
    if g:NERDTreeBookmark.BookmarkExistsFor(a:str)
        let path = g:NERDTreeBookmark.BookmarkFor(a:str).path
    else
        let dir = a:str ==# '' ? getcwd() : a:str

        "hack to get an absolute path if a relative path is given
        if dir =~# '^\.'
            let dir = getcwd() . nerdtree#slash() . dir
        endif

        "hack to prevent removing slash if dir is the root of the file system.
        if dir !=# '/'
            let dir = g:NERDTreePath.Resolve(dir)
        endif

        try
            let path = g:NERDTreePath.New(dir)
        catch /^NERDTree.InvalidArgumentsError/
            call nerdtree#echo('No bookmark or directory found for: ' . a:str)
            return {}
        endtry
    endif
    if !path.isDirectory
        let path = path.getParent()
    endif

    return path
endfunction

" Function: s:Creator._removeTreeBufForTab()   {{{1
function! s:Creator._removeTreeBufForTab()
    let buf = bufnr(t:NERDTreeBufName)

    "if &hidden is not set then it will already be gone
    if buf != -1

        "nerdtree buf may be mirrored/displayed elsewhere
        if self._isBufHidden(buf)
            exec 'bwipeout ' . buf
        endif

    endif

    unlet t:NERDTreeBufName
endfunction

" FUNCTION: s:Creator._setCommonBufOptions() {{{1
function! s:Creator._setCommonBufOptions()

    " Options for a non-file/control buffer.
    setlocal bufhidden=hide
    setlocal buftype=nofile
    setlocal noswapfile

    " Options for controlling buffer/window appearance.
    setlocal foldcolumn=0
    setlocal foldmethod=manual
    setlocal nobuflisted
    setlocal nofoldenable
    setlocal nolist
    setlocal nospell
    setlocal nowrap

    if g:NERDTreeShowLineNumbers
        setlocal number
    else
        setlocal nonumber
        if v:version >= 703
            setlocal norelativenumber
        endif
    endif

    iabc <buffer>

    if g:NERDTreeHighlightCursorline
        setlocal cursorline
    endif

    call self._setupStatusline()
    call self._bindMappings()

    setlocal filetype=nerdtree
endfunction

" FUNCTION: s:Creator._setupStatusline() {{{1
function! s:Creator._setupStatusline()
    if g:NERDTreeStatusline != -1
        let &l:statusline = g:NERDTreeStatusline
    endif
endfunction

" FUNCTION: s:Creator._tabpagevar(tabnr, var) {{{1
function! s:Creator._tabpagevar(tabnr, var)
    let currentTab = tabpagenr()
    let old_ei = &eventignore
    set eventignore=all

    try
        exec 'tabnext ' . a:tabnr
        let v = -1
        if exists('t:' . a:var)
            exec 'let v = t:' . a:var
        endif
        exec 'tabnext ' . currentTab

    finally
        let &eventignore = old_ei
    endtry

    return v
endfunction

" FUNCTION: s:Creator.ToggleTabTree(dir) {{{1
function! s:Creator.ToggleTabTree(dir)
    let creator = s:Creator.New()
    call creator.toggleTabTree(a:dir)
endfunction

" FUNCTION: s:Creator.toggleTabTree(dir) {{{1
" Toggles the NERD tree. I.e if the NERD tree is open, it is closed. If it is
" closed, it is restored or initialized. If dir is not empty, it will be set
" as the new root.
"
" Args:
" dir: the full path for the root node (is used if the NERD tree is being
" initialized, or to change the root to a new dir.)
function! s:Creator.toggleTabTree(dir)
    if g:NERDTree.ExistsForTab()
        if !g:NERDTree.IsOpen()
            call self._createTreeWin()
            if !empty(a:dir) && a:dir !=# b:NERDTree.root.path.str()
                call self.createTabTree(a:dir)
            elseif !&hidden
                call b:NERDTree.render()
            endif
            call b:NERDTree.ui.restoreScreenState()
        else
            call g:NERDTree.Close()
        endif
    else
        call self.createTabTree(a:dir)
    endif
endfunction

" Function: s:Creator._uniq(list)   {{{1
" returns a:list without duplicates
function! s:Creator._uniq(list)
  let uniqlist = []
  for elem in a:list
    if index(uniqlist, elem) ==# -1
      let uniqlist += [elem]
    endif
  endfor
  return uniqlist
endfunction

" vim: set sw=4 sts=4 et fdm=marker:
