"=============================================================================
"        File: winmanager.vim
"      Author: Srinath Avadhanula (srinath@eecs.berkeley.edu)
" Last Change: Wed Apr 03 05:00 PM 2002 PST
"        Help: winmanager.vim is a plugin which implements a classical windows
"              type IDE in Vim-6.0.  When you open up a new file, simply type
"              in :WMToggle. This will start up the file explorer.
"
"
" NOTE:  Starting from winmanager-2.x you can add new plugins to winmanager
" and also customize the window layout in your .vimrc
"
" See ":help winmanager" for additional details.
" ============================================================================

" quit if the user doesnt want us or if we are already loaded.
if exists("loaded_winmanager")
	finish
end
let loaded_winmanager = 1

" width of the explorer windows 
if !exists("g:winManagerWidth")
	let g:winManagerWidth = 25
end

" whether to close winmanager if only explorer windows are visible.
if !exists("g:persistentBehaviour")
	let g:persistentBehaviour = 1
end

" default window layout
if !exists("g:winManagerWindowLayout")
	let g:winManagerWindowLayout = "FileExplorer,TagsExplorer|BufExplorer"
end

" use default explorer plugin which ships with vim.
if !exists("g:defaultExplorer")
	let g:defaultExplorer = 1
end

" commands
" toggling between the windows manager open or closed. this can also be used
" to start win manager.
if !exists(':WMToggle')
	command -nargs=0 WMToggle :silent call <SID>ToggleWindowsManager()
end

" WManager and WMclose still exist for backward compatibility, but their use
" is deprecated because WMToggle has the functionality of both of them.
if !exists(':WManager')
	command -nargs=0 WManager :silent call <SID>StartWindowsManager()
end
if !exists(':WMClose')
	command -nargs=0 WMClose :silent call <SID>CloseWindowsManager()
end
" command to go to either the first explorer window visible
if !exists(':FirstExplorerWindow')
	command -nargs=0 FirstExplorerWindow :silent call <SID>GotoExplorerWindow('1')
end
" command to go to either the last explorer window visible
if !exists(':BottomExplorerWindow')
	command -nargs=0 BottomExplorerWindow :silent call <SID>GotoExplorerWindow('$')
end

" this command is used internally by winmanager. shouldnt be of concern to the
" user.
if !exists(':WinManagerGotoNextInGroup')
	command -nargs=1 WinManagerGotoNextInGroup :silent call <SID>GotoNextExplorerInGroup(<args>)
end	
if !exists(':WinManagerGotoPrevInGroup')
	command -nargs=1 WinManagerGotoPrevInGroup :silent call <SID>GotoNextExplorerInGroup(<args>,-1)
end	

" nifty command for debugging. SVarValueWinManager 'MRUList' will echo the
" value of 's:MRUList' for instance. to be used for debugging winmanager.
" shouldn't be of interest to the user.
if !exists(':SVarValueWinManager')
	command -nargs=* SVarValueWinManager :call <SID>ShowVariableValue(<args>)
end

" characters that must be escaped for filenames
if has("dos16") || has("dos32") || has("win16") || has("win32") || has("os2")
	let s:escfilename = ' %#'
else
	let s:escfilename = ' \%#'
endif

" a quick way to "uncomment" all the debug print statements.
let g:debugWinManager = 1
let g:numRefs = 0

" initialization.
let s:numExplorerGroups = 0
let s:numExplorers = 0

" Line continuation used here
let s:cpo_save = &cpo
set cpo&vim

"---
" this function creates a variable 
" s:explorerGroup_i
" for the i^th time it is called. This variable will be of the form
" s:explorerGroup_i = ",member1,member2,member3,"
"
" this provides a way to "group" various explorers into common groups, so that
" one of them will be visible at a time.
"
function! <SID>RegisterExplorerGroup()
	" g:winManagerWindowLayout is of the form
	" 'FileExplorer,TagsExplorer|BufExplorer'
	
	" begin extracting groups from the layout variable.
	let groupNum = 1
	while 1
		" if no more groups then break.	
		let curGroup = s:Strntok(g:winManagerWindowLayout, '|', groupNum)
		if curGroup == ''
			break
		end
		
		" otherwise extract the explorers belonging to this group and the
		" explorer ID's etc. also protect against the same explorer being put
		" in 2 groups.
		let grplist = ','
		let numlist = ','
		let curgn = s:numExplorerGroups + 1

		let i = 1
		while 1
			let name = s:Strntok(curGroup, ',', i)
			if name == ''
				break
			end
			" refuse to register an explorer twice, or if the explorer's title
			" doesnt exist.
			if exists('s:'.name.'_numberID') || !exists('g:'.name.'_title')
				if  !exists('g:'.name.'_title')
					if has('gui_running')
						call confirm(name." is registered as a plugin, but I cannot seem to find it anywhere.\n"
							\.'Make sure you have downloaded the relevant plugin or change the g:winManagerWindowLayout variable',
							\"&ok", 1, 'Warning')
					else
						echohl Error
						echomsg name." is registered as a plugin, but I cannot seem to find it anywhere."
							\.'Please make sure you have downloaded the relevant plugin'
						echohl None
					endif
				endif
				let i = i + 1
				continue
			end

			let s:numExplorers = s:numExplorers + 1
			let num = s:numExplorers

			exe 'let s:explorerName_'.num.' = name'

			let grplist = grplist.name.","
			let numlist = numlist.''.num.','

			" create variables of the form ExplorerName_<group/member/number>ID
			" which contains which group the explorer belongs to and its member
			" number within that group and also its number
			" this will create a variable of the form
			exe 'let s:'.name.'_groupID  = "'.curgn.'"'
			exe 'let s:'.name.'_memberID = "'.i.'"'
			exe 'let s:'.name.'_numberID = "'.num.'"'

			let i = i + 1
		endwhile
		if grplist == ','
			call PrintError('no explorers registered in this run')
			return
		end

		let s:numExplorerGroups = s:numExplorerGroups + 1

		exe 'let s:explorerGroup_'.curgn.' = grplist'
		exe 'let s:explorerGroupNums_'.curgn.' = numlist'
		exe 'let s:numMembers_'.curgn.' = a:0'

		let groupNum = groupNum + 1
	endwhile

endfunction


"---
" initializes the window manager. sets the initial layout. as of now, the
" layout of the explorer windows (i.e, which plugin appears above or below the
" other) depends on the order in which the plugins are sourced. 
" TODO: make this easily user customizable later.
"       Done! See comments about registration.
"
" this function opens each "registered" plugin in its appropriate position. it
" also starts off the autocommand which makes dynamic updating of buffers
" possible.
"
function! <SID>StartWindowsManager()
	" for the first few versions of winmanager, if no registration is done,
	" assume the following default configuration of the windows:
	"    (FileExplorer, TagsExplorer)
	"    (BufExplorer)
	" This allows for an "easy" distribution. i.e, the installation will not
	" break if the user is careless with his .vimrc
	let oldRep=&report
	let save_sc = &sc
	set report=10000 nosc
	if s:numExplorers == 0
		call s:RegisterExplorerGroup()
	end
	let nothingShown = 1
	let s:commandRunning = 1

	if !exists("s:MRUList")
		call s:InitializeMRUList()
	end
	let currentWindowNumber = winnr()

	if !exists("s:gotExplorerTitles")
		let s:gotExplorerTitles = 1
		let i = 1
		while i <= s:numExplorers
			exe 'let name = s:explorerName_'.i
			exe 'let s:explorerTitle_'.i.' = g:'.name.'_title'
			let i = i + 1
		endwhile
	endif

	" focus on the first visible explorer window.
	let gotvisible = 0
	let i = 1
	while i <= s:numExplorerGroups
		" check if the ith explorer is visible.
		let windownum = s:IsExplorerGroupVisible(i)
		if windownum != -1
			call s:GotoWindow(windownum)
			let gotvisible = 1
			" cen is the "current explorer number". used while restoring the
			" layout.
			let cen = i
			let nothingShown = 0
			break
		end
		let i = i + 1
	endwhile

	" split the current window or vsplit a new window for the explorers if
	" none of the explorers is visible.
	if !gotvisible
		if exists('s:lastMemberDisplayed_1')
			let lastmem = s:lastMemberDisplayed_1
		else
			let lastmem = 1
		end
		let somethingDisplayed = s:EditNextVisibleExplorer(1, lastmem-1, 1, 'vsplit')
		" if nothing was displayed this time, there is a possiblity it could
		" happen later during one of the refresh cycles. remember this for
		" then.
		call PrintError('something displayed on '.lastmem.' of 1 :'.somethingDisplayed)
		if !somethingDisplayed
			let s:tryGroupAgain_1 = 1
			q
		else
			let s:tryGroupAgain_1 = 0
			let nothingShown = 0
			let currentWindowNumber = currentWindowNumber + 1
		end
		let cen = 1
		" for now assume that the explorer windows always stay on the left.
		" TODO: make this optional later
		wincmd H
		" set up the correct width
		exe g:winManagerWidth.'wincmd |'
	end

	" now we are on one of the explorers. time to redo the original layout.
	let _split = &splitbelow
	let i = 1
	while i <= s:numExplorerGroups
		" for each group, see if any member of it is visible.
		let windownum = s:IsExplorerGroupVisible(i)
		
		" if this explorer group is not visible, then open the first plugin
		" belonging to this group
		if windownum == -1

			" if this explorer group is "before" the cen, then split above, else
			" below. except for the first time when this could possibly be
			" true, it always evaluates to the else.
			if i < cen
				set nosplitbelow
			else
				set splitbelow
			end
			" find the last plugin belonging to this "group" which was
			" displayed.
			if exists('s:lastMemberDisplayed_'.i)
				exe 'let lastmem = s:lastMemberDisplayed_'.i
			else
				let lastmem = 1
			end
			" try to display either that plugin or the one after it.
			let somethingDisplayed = s:EditNextVisibleExplorer(i, lastmem-1,1,"split")
			if !somethingDisplayed
				exe 'let s:tryGroupAgain_'.i.' = 1'
				q
			else
				exe 'let s:tryGroupAgain_'.i.' = 0'
				" if this is the first explorer shown, need to push it to the
				" right.
				if nothingShown
					wincmd H
					" set up the correct width
					exe g:winManagerWidth.'wincmd |'
				end
				let nothingShown = 0
				let currentWindowNumber = currentWindowNumber + 1
			end
			let cen = i

		" the group is visible, go to it so we can split the one after that
		" from it.
		else
			call s:GotoWindow(windownum)
		endif

		let i = i + 1
		" cen: current explorer (group) number which was visited.
		let cen = i
	endwhile
	call PrintError('done with start while loop')
	
	" now make the run for resizing. 
	let i = 1
	while i <= s:numExplorerGroups
		" find if its visible and the explorer of this group which is
		" currently displayed.
		let windownum = s:IsExplorerGroupVisible(i)
		" need to check because some of the explorer groups might not have
		" been displayed, if all their members were unable to display
		" anything.
		if windownum == -1
			let i = i + 1
			continue
		end
		let numexp = s:WhichMemberVisible(i)
		
		" visible, goto that window. 
		call s:GotoWindow(windownum)
		exe 'let name = s:explorerName_'.numexp
		" if this is not occupying the entire height of the window, then call
		" its ReSize() function (if it exists).
		if exists('*'.name.'_ReSize') && !s:IsOnlyVertical()
			exe 'call '.name.'_ReSize()'
		end
		let i = i + 1
	endwhile
	call PrintError('done with refresh while loop')

	let &splitbelow = _split

	augroup WinManagerRefresh
		au!
		au BufEnter * call <SID>RefreshWinManager()
		au BufDelete * call <SID>RefreshWinManager("BufDelete")
	augroup END

	call s:GotoWindow(currentWindowNumber)
	" RepairAltRegister needs to be called here as well, because 
	" 1. when winmanager is re-started, we need to restore the @# register to
	"    what it was.
	" 2. if winmanager is started for the first time, then we need to ensure
	"    that @# is at least not one of the explorer windows.
	if buflisted(bufnr('%'))
		call s:RepairAltRegister()
	end
	let s:commandRunning = 0
	let &report=oldRep
	let &sc = save_sc
	if nothingShown
		echomsg "[ no valid explorers available. winmanager will start when next possible ]"
	end
endfunction

"---
" if this window occupies the entire height of the screen, return 1, else
" return 0. i.e return 1 if there is no window above or below this window.
"
function! <SID>IsOnlyVertical()
	let curwin = winnr()
	wincmd k
	if curwin != winnr()
		wincmd j
		return 0
	else
		wincmd j
		if winnr() != curwin
			wincmd k
			return 0
		end
	end
	return 1
endfunction

"---
" this function first takes focus to the last listed file being edited and
" then depending on the users action and modified, etc opens the file bufName
" either on it or splits a new window etc.
"
function! WinManagerFileEdit(bufName, split)
	" this function is usually _not_ triggered from an autocommand, so the
	" movement commands in this function will trigger RefreshWinManager().
	" make that do nothing with this flag.
	let s:commandRunning = 1
	let oldRep=&report
	let save_sc = &sc
	set report=10000 nosc
	
	" if the file is already visible somewhere just go there.
	" a:bufName is a fully qualified filename of the form
	"    e:/path/to/file
	" now bufnr('e:/path/to/file') != -1 even in the case where a file called
	" e:/path/to/file/other/name is opened. (this is bufnr()'s behavior).
	" therefore make an additional check so were protected against false
	" matches.
	if bufwinnr(bufnr(a:bufName)) != -1 &&
		\ a:bufName == expand('#'.bufnr(a:bufName).':p')

		call s:GotoWindow(bufwinnr(a:bufName))
		" however, we still have to repair the @# register
		call s:RepairAltRegister()

	" otherwise goto the last listed buffer being edited.
	else 

		" if we had already opened this file, then use the #n notation instead
		" of opening by file name. this preserves cursor position.
		if bufnr(a:bufName) != -1 &&
			\ a:bufName == expand('#'.bufnr(a:bufName).':p')
			let bufcall = '#'.bufnr(a:bufName)
		else
			let bufcall = a:bufName
		end

		let lastBufferNumber = s:MRUGet(1)
		" if the last accessed buffer is visible, then goto it.
		if bufwinnr(lastBufferNumber) != -1
			" the fact that we go to the last listed buffer and then open this
			" buffer automatically protects the @# register.
			call s:GotoWindow(bufwinnr(bufnr(lastBufferNumber)))
			" now split it or not depending on stuff.
			if (&modified && !&hidden) || a:split
				exe 'silent! split '.bufcall
			else
				exe 'silent! e '.bufcall
			end
		else
			" the last accessed buffer is not visible. this most probably
			" means that the explorer buffers are the only windows visible.
			" this means that the layout has to be redone by v-splitting a new
			" window for this file.
			" first open the alternate file just to retain @# if its still
			" listed. 
			if buflisted(lastBufferNumber)
				exe 'silent! vsplit #'.lastBufferNumber
				exe 'silent! e '.bufcall
			" the last accessed buffer has dissapeared. just edit this file.
			else
				exe 'silent! vsplit '.bufcall
			end
			" now push this to the very right
			wincmd L
			" calculate the width of this window and reset it.
			exe &columns-g:winManagerWidth.' wincmd |'
		end
	end

	let s:commandRunning = 0

	" call Refresh incase this fileopen made some displays invalid.
	call s:RefreshWinManager()
	let &report=oldRep
	let &sc = save_sc
endfunction


"---
" function to repair the @# register.
"
" quickly edit the alternate buffer previously being edited in the
" FileExplorer area so that the % and # registers are not screwed with.
" This function must be called while focus is on a listed buffer which needs
" to be made @%.
"
function! <SID>RepairAltRegister()
	" setting hidden while going back and forth is very wise because sometimes
	" this function is used from within an autocommand. in such cases,
	" switching back and forth between buffers makes the syntax highlighting
	" dissapear.
	let _hidden = &l:bufhidden
	setlocal bufhidden=hide
	let oldRep=&report
	let save_sc = &sc
	set report=10000 nosc

	let currentBufferNumber = bufnr('%')
	let currentBufferName = expand('%:p')
	let alternateBufferNumber = s:MRUGet(2)

	" if the required alternatebuffer exists, then first edit it to preserve @#
 	if alternateBufferNumber != bufnr("#") 
		\ && alternateBufferNumber != -1
		\ && buflisted(alternateBufferNumber)
 		exec 'silent! b! '.alternateBufferNumber
	elseif alternateBufferNumber == -1
	" if the alternate buffer doesnt exist, do some randomness so that the @#
	" register is at least not some explorer buffer number. ideally, at this
	" stage, something would have been done to ensure that @# = -1, however,
	" for now, edit a temporary file.
		exe "e ".tempname()
		setlocal nobuflisted
		setlocal nomodifiable
		setlocal bufhidden=delete
		setlocal buftype=nofile
		let tmpBufNum = bufnr('%')
		exe 'silent! b! '.currentBufferNumber
		exe 'silent! bwipeout '.tmpBufNum
		let &l:bufhidden = _hidden
		return
	end

 	" now edit the current file (to preserve @% :-) )
	" it seems that using ":b !" is _very_ important to preserve syntax
	" highlighting. if ":e #" or ":b " is used, then syntax highlighting is
	" lost and the ugly hack thing keeps getting called everytime. 
	" still dont know exactly why this is. it has something to do with
	" abandoned buffers being kept and also nested autocommands, but its not
	" very clear to me what it is.
	exec('silent! b! '.currentBufferNumber)

	" a totally ugly hack to restore syntax highlighting... i have NO idea why
	" this has to be here... somehow mixing opening files with autocommands
	" has always been very very problematic.
	" NOTE: the problem seems to have gone away now... see above comment.
	if has("syntax") && exists("g:syntax_on") && !has("syntax_items")
		call PrintError('needing to reset syntax!')
		do syntax
	else
		call PrintError('fugly hack not needed!')
	end
	" end fugly hack.

	let &l:bufhidden = _hidden
	let &report=oldRep
	let &sc = save_sc
endfunction

"---
" the main function. this is responsible for updating plugins dynamically.
" this function is triggered on the BufEnter and BufDelete events. every time
" it is called, it makes a pass through all visible plugins and if their
" display is not valid, it calls their Start() function.
"
" if this function is called with no arguments, it is assumed to be triggered
" from a BufEnter even or due to a forcible refresh. If it is called with one
" argument called "BufDelete", then it is assumed that it is triggered from
" the BufDelete event.
"
function! <SID>RefreshWinManager(...)
	" refreshes the window layout and the displayes of windows which trigger
	" on autocommands.
	
	" make a note of whether this refresh was triggered by the BufDelete event
	" or not.
	let _split = &splitbelow
	if a:0 > 0 && a:1 == "BufDelete"
		let BufDelete = 1
	else
		let BufDelete = 0
	end
	" do the push pop thing irrespective of whether we do the rest of the
	" stuff or not.
	if BufDelete
		call s:MRUPop()
	else
		call s:MRUPush()
	end
	" if this autocommand was triggered because of internal movements/commands
	" due to other winmanager commands, then quit.
	if exists("s:commandRunning") && s:commandRunning
		return
	end
	" check if only explorer windows are visible and if so quit if we dont
	" want persistent behavior.
	if !g:persistentBehaviour && s:OnlyExplorerWindowsOpen()
		qa
	end
	
	" this magic statement is curing the syntax losing problem. WHY?
	let s:commandRunning = 1
	let g:numRefs = g:numRefs + 1

	" remember this window number because we will return to it after
	" refreshing the buffer listing.
	let currentWindowNumber = winnr()
	let curBufListed = buflisted(bufnr('%'))
	let cfn = s:Path(expand("%:p"))

	" now cycle through all the visible explorers and and for each "invalid"ly
	" displayed explorer call its corresponding refresh and resize functions.
	let i = 1
	while i <= s:numExplorerGroups  && curBufListed
		" find if its visible and the explorer of this group which is
		" currently displayed.
		let windownum = s:IsExplorerGroupVisible(i)
		" if this explorer is visible, then call its _IsValid() function, etc.
		if windownum == -1
			let i = i + 1
			continue
		end
		let numexp = s:WhichMemberVisible(i)
		" visible, goto that window.
		call s:GotoWindow(windownum)
		exe 'let name = s:explorerName_'.numexp

		exe 'let explorerName = s:explorerName_'.numexp
		exe 'let isvalid = '.explorerName.'_IsValid()'
		" ... and if it isnt then update it.
		if !isvalid
			call <SID>GotoWindow(windownum)
			exe 'call '.explorerName.'_Start()'
			if exists('*'.explorerName.'_ReSize') && !s:IsOnlyVertical()
				exe 'call '.explorerName.'_ReSize()'
			end
		end
		let i = i + 1
	endwhile

	" this while loop handles the case where a group of explorers are was not
	" valid at some point and therefore didnt occupy a window, but became
	" valid after some point and therefore need to obtain a seperate window.
	let i = 1
	while i <= s:numExplorerGroups && curBufListed
		exe 'let retry = s:tryGroupAgain_'.i
		" only do this if we need to retry opening this buffer. we should not
		" keep opening a group which the user has closed using a ":quit"
		" command.
		if retry
			call PrintError('retrying group '.i)
			" find the 'nearest' group which is open.
			let nearestGroup = 'inf'
			let nearestWindow = 'inf'
			" TODO: possible bug: what if there are more than a million
			" plugins being used? :-)
			let nearestGroupDist = 1000000
			let j = 1
			while j <= s:numExplorerGroups
				
				let windownum = s:IsExplorerGroupVisible(j)
				if windownum != -1
					let dist = ( (j-i) < 0 ? (i-j) : (j-i) )
					if dist < nearestGroupDist
						let nearestGroupDist = dist
						let nearestGroup = j
						let nearestWindow = windownum
					end
				end
				let j = j + 1
			endwhile

			call PrintError('nearestWindow = '.nearestWindow)
			" if nearestWindow is 'inf', it means no other explorer plugins
			" are open. which means that this thing needs to go the very
			" right.
			if nearestWindow == 'inf'
				let ecmd = 'vsplit'
			else
				let ecmd = 'split'
				if nearestGroup > i
					setlocal nosplitbelow
				else
					setlocal splitbelow
				end
			end
			let somethingDisplayed = s:EditNextVisibleExplorer(i, 0, 1, ecmd)
			" if nothing was displayed this time, there is a possiblity it could
			" happen later during one of the refresh cycles. remember this for
			" then.
			if !somethingDisplayed
				exe 'let s:tryGroupAgain_'.i.' = 1'
				q
			else
				exe 'let s:tryGroupAgain_'.i.' = 0'
				let currentWindowNumber = currentWindowNumber + 1
				exe 'let name = s:explorerName_'.somethingDisplayed
				if exists('*'.name.'_ReSize') && !s:IsOnlyVertical()
					exe 'call '.name.'_ReSize()'
				end
				if nearestWindow == 'inf'
					wincmd H
					" set up the correct width
					" set width only if we are creating a new window...
					exe g:winManagerWidth.'wincmd |'
				end
				call PrintError('doing the funky open thing')
			end
		end
		let i = i + 1
	endwhile

	call s:ResizeAllExplorers()

	" refreshing done, now return back to where we were originally.
	call <SID>GotoWindow(currentWindowNumber)
	
	" however, we still have to "repair" the actual @% and @# registers, in
	" case we are returning to a listed buffer.  also should do this only for
	" a BufEnter event. For a BufDelete event, the do this only if the current
	" buffer is not the buffer being deleted.
	call PrintError('refresh: abuf = '.expand('<abuf>'))
	if buflisted(bufnr("%")) && !isdirectory(bufname("%")) && 
	\	( !BufDelete || ( bufnr('%') != expand('<abuf>') ) )
		call <SID>RepairAltRegister()
	end

	let s:commandRunning = 0
	let &splitbelow = _split
endfunction

function! <SID>ResizeAllExplorers()
	let i = 1
	while i <= s:numExplorers
		let explorerWinNum = s:IsExplorerVisible(i)
		if explorerWinNum != -1
			exe 'let explorerName = s:explorerName_'.i
			if exists('*'.explorerName.'_ReSize') && !s:IsOnlyVertical()
			" if a resize() function exists for this explorer and there
			" is some window above and/or below this window, then call its
			" resize function. this allows for dynamic resizing.
				call s:GotoWindow(explorerWinNum)
				exe 'call '.explorerName.'_ReSize()'
				call PrintError('calling resize for '.explorerName)
			end
		end
		let i = i + 1
	endwhile
endfunction

"---
" Make sure a path has proper form. 
" this function forces every path to take the following form
" dir1/dir2/file    OR
" dir1/dir2/dir/ 
" i.e, it replaces \ with / and stuff.
"
function! <SID>Path(p)
	let _p = a:p
	if a:p =~ '//$'
		return ""
	end
	if isdirectory(_p)
		let origdir= getcwd()
		exe "chdir" _p
		let _p = getcwd()
		exe "chdir" origdir
	end
	if has("dos16") || has("dos32") || has("win16") || has("win32") || has("os2")
		let _p = substitute(_p,'\\','/','g')
	endif
	if _p !~ '/$' && isdirectory(_p)
		let _p = _p.'/'
	endif
	return _p
endfunction

" goto the reqdWinNum^th window. returns 0 on failure otherwise 1.
function! <SID>GotoWindow(reqdWinNum)
	let startWinNum = winnr()
	if startWinNum == a:reqdWinNum
		return 1
	end
	if winbufnr(a:reqdWinNum) == -1
		return 0
	else
		exe a:reqdWinNum.' wincmd w'
		return 1
	end

endfunction

" returns the window number of the ith explorer if its visible, else -1
function! <SID>IsExplorerVisible(i)
	if exists('s:explorerBufNum_'.a:i)
		exe 'let explorerBufNum = s:explorerBufNum_'.a:i
	else
		let explorerBufNum = -1
	end
	return bufwinnr(explorerBufNum)
endfunction

" returns the window number of the first explorer of the ith explorer group if
" its visible, else -1
"
" if called with 2 arguments with the second being 'member', then returns the
" member number which is visible instead of its window number
"
function! <SID>IsExplorerGroupVisible(i, ...)
	" numList : the list of explorer numbers belonging to this group
	exe 'let numList = s:explorerGroupNums_'.a:i
	" ncl : next comma location
	" pcl : previous comma location
	let pcl = 0
	let ncl	= match(numList, ',', pcl + 1)
	while ncl != -1
		exe 'let num = '.strpart(numList, pcl + 1, ncl - pcl - 1)
		if s:IsExplorerVisible(num) != -1
			if a:0 == 1 && a:1 == 'mem'
				return num
			else
				return s:IsExplorerVisible(num)
			end
		end
		let pcl = ncl
		let ncl	= match(numList, ',', pcl + 1)
	endwhile
	return -1
endfunction

" returns the member number of the first explorer of the ith explorer group if
" its visible, else -1
function! <SID>WhichMemberVisible(i)
	return s:IsExplorerGroupVisible(a:i, 'mem')
endfunction

" a handy little function for debugging.
function! PrintError(eline)
	if !g:debugWinManager
		return
	end
	if !exists("g:myerror")
		let g:myerror = ""
	end
	let g:myerror = g:myerror . "\n" . a:eline
endfunction

"---
" find the memn^th member's explorer number of the groupn^th explorer group
" i.e, if s:explorerGroup_2 = ",3,4,5,"
" then FindExplorerInGroup(2,3) = 5
" 
" returs -1 if its not possible.
"
function! <SID>FindExplorerInGroup(groupn, memn)
	" numList : the list of explorer numbers belonging to this group
	exe 'let numList = s:explorerGroupNums_'.a:groupn

	let num = s:Strntok2(numList, ',', a:memn)
	if num == ''
		return -1
	end
	exe 'return '.num
endfunction

"---
" goto the next explorer in the group which this one belongs to.
" if called with 2 arguments, goto the previous explorer.
"
function! <SID>GotoNextExplorerInGroup(name, ...)
	let s:commandRunning = 1
	" go forward or back?
	if a:0 > 1
		let dir = -1
	else
		let dir = 1
	end

	" first extract the ID variable from the name
	exe 'let grpn = s:'.a:name.'_groupID'
	exe 'let memn = s:'.a:name.'_memberID'
	exe 'let numn = s:'.a:name.'_numberID'

	" find the number of members of this group.
	exe 'let nummems = s:numMembers_'.grpn
	if nummems == 1
		return 0
	end

	if exists('*'.a:name.'_WrapUp')
		exe 'call '.a:name.'_WrapUp()'
	end

	let curbufnum = bufnr('%')
	let somethingDisplayed = s:EditNextVisibleExplorer(grpn, memn, dir, 'e')
	if !somethingDisplayed && curbufnum != bufnr('%')
	   	" now start the next explorer using its title
	   	exe 'let title = s:explorerTitle_'.numn
	   	exe 'silent! e '.title
		setlocal nobuflisted
		setlocal bufhidden=delete
		setlocal buftype=nofile
		setlocal noswapfile

		" call the Start() function for the next explorer ...
		exe 'call '.a:name.'_Start()'
		exe 'nnoremap <buffer> <C-n> :WinManagerGotoNextInGroup "'.a:name.'"<cr>'
		exe 'nnoremap <buffer> <C-p> :WinManagerGotoPrevInGroup "'.a:name.'"<cr>'
		setlocal nomodifiable
		call WinManagerForceReSize(a:name)
	end

	let s:commandRunning = 0
endfunction

" edit the first possible explorer after memn belonging to groun. use editcmd
" to form the new window.
function! <SID>EditNextVisibleExplorer(grpn, memn, dir, editcmd)
	
	call PrintError('EditNext: grpn = '.a:grpn.', memn = '.a:memn.', dir = '.a:dir.' editcmd = '.a:editcmd)
	" then try to find the number of the next member.
	let startmn = (a:memn ? a:memn : 1)
	let nextmn = a:memn + a:dir
	let editcmd = a:editcmd

	let somethingDisplayed = 0

	let once = 0
	" enter this loop at least once
	while nextmn != startmn || !once
	" cycle through the next explorers in this group finding out the next
	" explorer which says its able to display anything at all.
		let once = 1
	
		let nextEN = s:FindExplorerInGroup(a:grpn, nextmn)
		" if the next member doesnt exist wrap around.
		if nextEN == -1 
			if a:dir == 1
				let nextEN = s:FindExplorerInGroup(a:grpn, 1)
				let nextmn = 1
				continue
			else
				let nextEN = s:FindExplorerInGroup(a:grpn, nummems)
				let nextmn = nummems
				continue
			end
		end

		" if we have come back to the same explorer with every other group
		" member not able to display anything, then return.
		call PrintError('nextmn = '.nextmn.' a:memn = '.a:memn)

		exe 'let name = s:explorerName_'.nextEN
		" if the _IsPossible() function doesn't exist, assume its always
		" possible to display stuff.
		let isposs = 1
		if exists('*'.name.'_IsPossible')
			exe 'let isposs = '.name.'_IsPossible()'
		end
		if isposs
			" now start the next explorer using its title
			exe 'let title = s:explorerTitle_'.nextEN
			exe 'let name = s:explorerName_'.nextEN
			exe 'silent! '.editcmd.' '.title
			" use vsplitting etc only the first time things are opened.
			if editcmd != 'e'
				let editcmd = 'e'
			end
			" these are a few setting which most well-made explorers
			" already set, but just to be on the safe side.
			setlocal nobuflisted
			setlocal bufhidden=delete
			setlocal buftype=nofile
			setlocal noswapfile

			" call the Start() function for the next explorer ...
			exe 'call '.name.'_Start()'
			setlocal nomodifiable
			" and remember its buffer number for later.
			exe 'let s:explorerBufNum_'.nextEN.' = bufnr("%")'
			" also remember that this was the last explorer of this group which was
			" displayed.
			exe 'let s:lastMemberDisplayed_'.a:grpn.' = nextmn'

			" if this explorer has actually not put anything in the buffer
			" then quit and forget.
			if line('$') > 0 && getline('$') != ''
				let somethingDisplayed = nextEN
				break
			end
		end
		" goto the next explorer of the group.
		let nextmn = nextmn + a:dir
	endwhile

	if somethingDisplayed
		" and then add this mapping to switch to the next/previous
		" explorer in this group
		exe 'nnoremap <buffer> <C-n> :WinManagerGotoNextInGroup "'.name.'"<cr>'
		exe 'nnoremap <buffer> <C-p> :WinManagerGotoPrevInGroup "'.name.'"<cr>'
	end
	return somethingDisplayed
endfunction


" goes to either the first explorer window or the last explorer window
" visible.
function! <SID>GotoExplorerWindow(which)
	let s:commandRunning = 1
	" first go to either the top left or the bottom right window.
	if a:which == '1'
		" goto to the top left and move in the bottom/right direction.
		wincmd t
		let winmovecmd = 'wincmd w'
	else
		wincmd b
		let winmovecmd = 'wincmd W'
	end
	" remember the window we started from.
	let startWin = winnr()
	let firstTime = 1
	" then begin cycling through all the windows either going in the
	" bottom/right direction or the top/left direction.
	while 1
		" if we are on an explorer window quit.
		if s:IsExplorerBuffer(bufnr('%'))
			let s:commandRunning = 0
			return
		end
		" if we have cycled through one complete time without hitting pay
		" dirt, quit.
		if winnr() == startWin && !firstTime
			" TODO: this will screw the @% and @# register.
			break
		end
		let firstTime = 0
		exe winmovecmd
	endwhile
	let s:commandRunning = 0
endfunction

" returns the explorer number if an explorer plugin exists with the specified
" buffer number
function! <SID>IsExplorerBuffer(num)
	let i = 1
	while i <= s:numExplorers
		if exists('s:explorerBufNum_'.i)
			exe 'let bufnum = s:explorerBufNum_'.i
			if bufnum == a:num
				return i
			end
		end
		let i = i + 1
	endwhile
	return 0
endfunction

" toggle showing the explorer plugins.
function! <SID>ToggleWindowsManager()
	if IsWinManagerVisible()
		call s:CloseWindowsManager()
	else
		call s:StartWindowsManager()
	end
endfunction

" exported function. returns the buffer number of the last file being edited
" in the file editing area.
function! WinManagerGetLastEditedFile(...)
	if a:0 == 0
		return s:MRUGet(1)
	else
		let ret = s:MRUGet(a:1)
		if ret == ''
			return matchstr(s:MRUList, ',\zs[0-9]\+\ze,$')
		else
			return ret
		end
endfunction


" exported function. returns 1 if any of the explorer windows are open,
" otherwise returns 0.
function! IsWinManagerVisible()
	let i = 1
	while i <= s:numExplorers
		if s:IsExplorerVisible(i) != -1
			return 1
		end
		let i = i + 1
	endwhile
	return 0
endfunction


" close all the visible explorer windows.
function! <SID>CloseWindowsManager()
	let s:commandRunning = 1

	let i = 1
	while i <= bufnr('$')
		let explNum = s:IsExplorerBuffer(i)
		if explNum > 0 && bufwinnr(i) != -1
			exe 'bd '.i
		end
		let i = i + 1
	endwhile

	let s:commandRunning = 0
endfunction

" provides a way to examine script local variables from outside the script.
" very handy for debugging.
function! <SID>ShowVariableValue(...)
	let i = 1
	while i <= a:0
		exe 'let arg = a:'.i
		if exists('s:'.arg) ||
		\  exists('*s:'.arg)
			exe 'let val = s:'.arg
			echomsg 's:'.arg.' = '.val
		end
		let i = i + 1
	endwhile
endfunction

" the following functions are hooks provided by winmanager to external plugins
" as a way to get winmanager to stop getting triggered on AUs. This is useful
" when an explorer plugin triggers a BufEnter or BufDelete *internally*. For
" example, bufexplorer.vim's "delete buffer" function triggers a BufDelete
" function.
"
function! WinManagerSuspendAUs()
	let s:commandRunning = 1
endfunction
function! WinManagerResumeAUs()
	let s:commandRunning = 0
endfunction

" Another hook provided by winmanager. Normally winmanager will call the
" plugins resize function every time the BufEnter or BufDelete event is
" triggered. However, sometimes a plugin might change the number of lines
" *internally*. In this case, the plugin could make a call to this function
" which will make a safety check and then call its resize function.
"
function! WinManagerForceReSize(explName)
	if !exists('s:'.a:explName.'_numberID') || !exists('*'.a:explName.'_ReSize')
		call PrintError('resize quitting because resize function not found or explorer not registered')
		return
	end
	exe 'let explNum = s:'.a:explName.'_numberID'
	let s:commandRunning = 1
	let windowNum = s:IsExplorerVisible(explNum)
	if windowNum == -1
		call PrintError('resize quitting because window not visible')
		return
	end
	call s:GotoWindow(windowNum)
	if s:IsOnlyVertical()
		call PrintError('resize quitting because its illegal')
		return
	end
	exe 'call '.a:explName.'_ReSize()'
	let s:commandRunning = 0
endfunction

" returns 1 if the only visible windows are explorer windows.
function! <SID>OnlyExplorerWindowsOpen()
	let i = 1
	" loop over all open windows
	while 1
		" if we have checked all open windows and not returned yet, then it
		" means only explorers are visible.
		if winbufnr(i) == -1
			return 1
		end
		" if this is a non-explorer window then return 0
		if !s:IsExplorerBuffer(winbufnr(i))
			return 0
		end
		let i = i + 1
	endwhile
endfunction

" MRUPush
function! <SID>MRUPush()
	if buflisted(bufnr("%")) && !isdirectory(bufname("%"))
		let _bufNbr = bufnr('%')
		let _list = substitute(s:MRUList, ','._bufNbr.',', ',', '')
		let s:MRUList = ','._bufNbr._list
		unlet _bufNbr _list
	end
endfunction

" MRUPop
function! <SID>MRUPop()
	let _bufNbr = expand('<abuf>')
	let s:MRUList = substitute(s:MRUList, ''._bufNbr.',', '', '')
	unlet _bufNbr
endfunction

" MRUGet
function! <SID>MRUGet(slot)
	let ret = s:Strntok2(s:MRUList, ',', a:slot)
	if ret == ''
		return -1
	end
	exe 'return '.ret
endfunction

" Strntok:
" extract the n^th token from s seperated by tok. 
" example: Strntok('1,23,3', ',', 2) = 23
fun! <SID>Strntok(s, tok, n)
	return matchstr( a:s.a:tok[0], '\v(\zs([^'.a:tok.']*)\ze['.a:tok.']){'.a:n.'}')
endfun

" Strntok2
" same as Strntok except that s is delimited by the tok character at the
" beginning and end.
" example: Strntok2(',1,23,3,', ',', 2) = 23
fun! <SID>Strntok2(s, tok, n)
	return matchstr( a:s, '\v((['.a:tok.']\zs[^'.a:tok.']*)\ze){'.a:n.'}')
endfun

" InitializeMRUList 
"
" initialize the MRU list. initially this will be just the buffers in the
" order of their buffer numbers with the @% and @# leading. The MRU list
" consists of a string of the following form: ",1,2,3,4,"
" NOTE: there are commas at the beginning and the end. this is to make
" identifying the position of buffers in the list easier even if they occur in
" the beginning or end and in situations where one buffer number is part of
" another. i.e the string "9" is part of the string "19"
" 
function! <SID>InitializeMRUList()
	let nBufs = bufnr('$')
	let _i = 1

	" put the % and the # numbers at the beginning if they are listed.
	let s:MRUList = ''
	if buflisted(bufnr("%"))
		let s:MRUList = ','.bufnr("%")
	end
	if buflisted(bufnr("#"))
		let s:MRUList = s:MRUList.','.bufnr("#")
	end
	let s:MRUList = s:MRUList.','
	
	" then proceed with the rest of the buffers
	while _i <= nBufs
		" dont keep unlisted buffers in the MRU list.
		if buflisted(_i) && bufnr("%") != _i && bufnr("#") != _i
			let s:MRUList = s:MRUList._i.','
		end
		let _i = _i + 1
	endwhile
	" Doing this makes bufexplorer.vim display the first two listed buffers as
	" @% and @# which they actually are when winmanager starts up after doing
	" something like:
	"    vim *.vim
	"    :WMtoggle
	let g:MRUList = s:MRUList
endfunction

if !g:defaultExplorer
	let loaded_explorer = 1
	"---
	" Set up the autocommand to allow directories to be edited
	"
	augroup fileExplorer
		au!
		au VimEnter * call s:EditDir("VimEnter")
		au BufEnter * call s:EditDir("BufEnter")
	augroup end
end

" handles editing a directory via winmanager.
function! <SID>EditDir(event)
	" return immediately if this isn't a directory.
	let name = expand("%")
	if name == ""
		let name = expand("%:p")
	endif
	if !isdirectory(name)
		return
	endif
	
	" if it is, then call the modified explorer.vim's Explore command.
	if a:event != "VimEnter"
 		if exists(":Explore")
 			ExploreInCurrentWindow
 		end
	end
	" if we have entered vim while editing a directory, then remove the
	" directory buffer, and start the window layout.
	" Note that we only start up winmanager in a VimEnter event because we
	" want commands such as ":e /some/dir/" within vim to have the same effect
	" as with the standard explorer.vim plugin which ships with vim.
	"
	" NOTE: if the user has chosen a layout where the FileExplorer is not at
	" the top-left, this will be unintuitive.
	if a:event == "VimEnter"
		bwipeout
		
		call s:StartWindowsManager()
		call s:MRUPush()
		call s:GotoExplorerWindow('1')
 	end
endfunction

" restore 'cpo'
let &cpo = s:cpo_save
unlet s:cpo_save
" vim:ts=4:noet:sw=4
