" LaTeX Box completion

setlocal omnifunc=LatexBox_Complete

" <SID> Wrap {{{
function! s:GetSID()
	return matchstr(expand('<sfile>'), '\zs<SNR>\d\+_\ze.*$')
endfunction
let s:SID = s:GetSID()
function! s:SIDWrap(func)
	return s:SID . a:func
endfunction
" }}}

" Completion {{{
if !exists('g:LatexBox_completion_close_braces')
	let g:LatexBox_completion_close_braces = 1
endif
if !exists('g:LatexBox_bibtex_wild_spaces')
	let g:LatexBox_bibtex_wild_spaces = 1
endif

if !exists('g:LatexBox_cite_pattern')
	let g:LatexBox_cite_pattern = '\C\\\a*cite\a*\*\?\(\[[^\]]*\]\)*\_\s*{'
endif
if !exists('g:LatexBox_ref_pattern')
	let g:LatexBox_ref_pattern = '\C\\v\?\(eq\|page\|[cC]\|labelc\|name\|auto\)\?ref\*\?\_\s*{'
endif

if !exists('g:LatexBox_completion_environments')
	let g:LatexBox_completion_environments = [
		\ {'word': 'itemize',		'menu': 'bullet list' },
		\ {'word': 'enumerate',		'menu': 'numbered list' },
		\ {'word': 'description',	'menu': 'description' },
		\ {'word': 'center',		'menu': 'centered text' },
		\ {'word': 'figure',		'menu': 'floating figure' },
		\ {'word': 'table',			'menu': 'floating table' },
		\ {'word': 'equation',		'menu': 'equation (numbered)' },
		\ {'word': 'align',			'menu': 'aligned equations (numbered)' },
		\ {'word': 'align*',		'menu': 'aligned equations' },
		\ {'word': 'document' },
		\ {'word': 'abstract' },
		\ ]
endif

if !exists('g:LatexBox_completion_commands')
	let g:LatexBox_completion_commands = [
		\ {'word': '\begin{' },
		\ {'word': '\end{' },
		\ {'word': '\item' },
		\ {'word': '\label{' },
		\ {'word': '\ref{' },
		\ {'word': '\eqref{eq:' },
		\ {'word': '\cite{' },
		\ {'word': '\chapter{' },
		\ {'word': '\section{' },
		\ {'word': '\subsection{' },
		\ {'word': '\subsubsection{' },
		\ {'word': '\paragraph{' },
		\ {'word': '\nonumber' },
		\ {'word': '\bibliography' },
		\ {'word': '\bibliographystyle' },
		\ ]
endif

if !exists('g:LatexBox_complete_inlineMath')
	let g:LatexBox_complete_inlineMath = 0
endif

if !exists('g:LatexBox_eq_env_patterns')
	let g:LatexBox_eq_env_patterns = 'equation\|gather\|multiline\|align\|flalign\|alignat\|eqnarray'
endif

" }}}

"LatexBox_kpsewhich {{{
function! LatexBox_kpsewhich(file)
	let old_dir = getcwd()
	execute 'lcd ' . fnameescape(LatexBox_GetTexRoot())
	let out = system('kpsewhich "' . a:file . '"')

	" If kpsewhich has found something, it returns a non-empty string with a
	" newline at the end; otherwise the string is empty
	if len(out)
		" Remove the trailing newline
		let out = fnamemodify(out[:-2], ':p')
	endif

	execute 'lcd ' . fnameescape(old_dir)

	return out
endfunction
"}}}

" Omni Completion {{{

let s:completion_type = ''

function! LatexBox_Complete(findstart, base)
	if a:findstart
		" return the starting position of the word
		let line = getline('.')
		let pos = col('.') - 1
		while pos > 0 && line[pos - 1] !~ '\\\|{'
			let pos -= 1
		endwhile

		let line_start = line[:pos-1]
		if line_start =~ '\m\C\\begin\_\s*{$'
			let s:completion_type = 'begin'
		elseif line_start =~ '\m\C\\end\_\s*{$'
			let s:completion_type = 'end'
		elseif line_start =~ '\m' . g:LatexBox_ref_pattern . '$'
			let s:completion_type = 'ref'
		elseif line_start =~ '\m' . g:LatexBox_cite_pattern . '$'
			let s:completion_type = 'bib'
			" check for multiple citations
			let pos = col('.') - 1
			while pos > 0 && line[pos - 1] !~ '{\|,'
				let pos -= 1
			endwhile
		elseif s:LatexBox_complete_inlineMath_or_not()
			let s:completion_type = 'inlineMath'
			let pos = s:eq_pos
		else
			let s:completion_type = 'command'
			if line[pos - 1] == '\'
				let pos -= 1
			endif
		endif
		return pos
	else
		" return suggestions in an array
		let suggestions = []

		if s:completion_type == 'begin'
			" suggest known environments
			for entry in g:LatexBox_completion_environments
				if entry.word =~ '^' . escape(a:base, '\')
					if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^}')
						" add trailing '}'
						let entry = copy(entry)
						let entry.abbr = entry.word
						let entry.word = entry.word . '}'
					endif
					call add(suggestions, entry)
				endif
			endfor
		elseif s:completion_type == 'end'
			" suggest known environments
			let env = LatexBox_GetCurrentEnvironment()
			if env != ''
				if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^\s*[,}]')
					call add(suggestions, {'word': env . '}', 'abbr': env})
				else
					call add(suggestions, env)
				endif
			endif
		elseif s:completion_type == 'command'
			" suggest known commands
			for entry in g:LatexBox_completion_commands
				if entry.word =~ '^' . escape(a:base, '\')
					" do not display trailing '{'
					if entry.word =~ '{'
						let entry.abbr = entry.word[0:-2]
					endif
					call add(suggestions, entry)
				endif
			endfor
		elseif s:completion_type == 'ref'
			let suggestions = s:CompleteLabels(a:base)
		elseif s:completion_type == 'bib'
			" suggest BibTeX entries
			let suggestions = LatexBox_BibComplete(a:base)
		elseif s:completion_type == 'inlineMath'
			let suggestions = s:LatexBox_inlineMath_completion(a:base)
		endif
		if !has('gui_running')
			redraw!
		endif
		return suggestions
	endif
endfunction
" }}}

" BibTeX search {{{

" find the \bibliography{...} commands
" the optional argument is the file name to be searched

function! s:FindBibData(...)
	if a:0 == 0
		let file = LatexBox_GetMainTexFile()
	else
		let file = a:1
	endif

	if !filereadable(file)
		return []
	endif
	let lines = readfile(file)
	let bibdata_list = []

	"
	" Search for added bibliographies
	"
	let bibliography_cmds = [
				\ '\\bibliography',
				\ '\\addbibresource',
				\ '\\addglobalbib',
				\ '\\addsectionbib',
				\ ]
	for cmd in bibliography_cmds
		let filtered = filter(copy(lines),
					\ 'v:val =~ ''\C' . cmd . '\s*{[^}]\+}''')
		let files = map(filtered,
					\ 'matchstr(v:val, ''\C' . cmd . '\s*{\zs[^}]\+\ze}'')')
		for file in files
			let bibdata_list += map(split(file, ','),
						\ 'fnamemodify(v:val, '':r'')')
		endfor
	endfor

	"
	" Also search included files
	"
	for input in filter(lines,
				\ 'v:val =~ ''\C\\\%(input\|include\)\s*{[^}]\+}''')
		let bibdata_list += s:FindBibData(LatexBox_kpsewhich(
					\ matchstr(input,
						\ '\C\\\%(input\|include\)\s*{\zs[^}]\+\ze}')))
	endfor

	return bibdata_list
endfunction

let s:bstfile = expand('<sfile>:p:h') . '/vimcomplete'

function! LatexBox_BibSearch(regexp)
	let res = []

	" Find data from bib files
	let bibdata = join(s:FindBibData(), ',')
	if bibdata != ''

		" write temporary aux file
		let tmpbase = LatexBox_GetTexRoot() . '/_LatexBox_BibComplete'
		let auxfile = tmpbase . '.aux'
		let bblfile = tmpbase . '.bbl'
		let blgfile = tmpbase . '.blg'

		call writefile(['\citation{*}', '\bibstyle{' . s:bstfile . '}',
					\ '\bibdata{' . bibdata . '}'], auxfile)

		if has('win32')
			let l:old_shellslash = &l:shellslash
			setlocal noshellslash
			call system('cd ' . shellescape(LatexBox_GetTexRoot()) .
						\ ' & bibtex -terse '
						\ . fnamemodify(auxfile, ':t') . ' >nul')
			let &l:shellslash = l:old_shellslash
		else
			call system('cd ' . shellescape(LatexBox_GetTexRoot()) .
						\ ' ; bibtex -terse '
						\ . fnamemodify(auxfile, ':t') . ' >/dev/null')
		endif

		let lines = split(substitute(join(readfile(bblfile), "\n"),
					\ '\n\n\@!\(\s\=\)\s*\|{\|}', '\1', 'g'), "\n")

		for line in filter(lines, 'v:val =~ a:regexp')
			let matches = matchlist(line,
						\ '^\(.*\)||\(.*\)||\(.*\)||\(.*\)||\(.*\)')
			if !empty(matches) && !empty(matches[1])
				let s:type_length = max([s:type_length,
							\ len(matches[2]) + 3])
				call add(res, {
							\ 'key': matches[1],
							\ 'type': matches[2],
							\ 'author': matches[3],
							\ 'year': matches[4],
							\ 'title': matches[5],
							\ })
			endif
		endfor

		call delete(auxfile)
		call delete(bblfile)
		call delete(blgfile)
	endif

	" Find data from 'thebibliography' environments
	let lines = readfile(LatexBox_GetMainTexFile())
	if match(lines, '\C\\begin{thebibliography}') >= 0
		for line in filter(filter(lines, 'v:val =~ ''\C\\bibitem'''),
					\ 'v:val =~ a:regexp')
			let match = matchlist(line, '\\bibitem{\([^}]*\)')[1]
			call add(res, {
						\ 'key': match,
						\ 'type': '',
						\ 'author': '',
						\ 'year': '',
						\ 'title': match,
						\ })
		endfor
	endif

	return res
endfunction
" }}}

" BibTeX completion {{{
let s:type_length=0
function! LatexBox_BibComplete(regexp)

	" treat spaces as '.*' if needed
	if g:LatexBox_bibtex_wild_spaces
		"let regexp = substitute(a:regexp, '\s\+', '.*', 'g')
		let regexp = '.*' . substitute(a:regexp, '\s\+', '\\\&.*', 'g')
	else
		let regexp = a:regexp
	endif

	let res = []
	let s:type_length = 4
	for m in LatexBox_BibSearch(regexp)
		let type = m['type']   == '' ? '[-]' : '[' . m['type']   . '] '
		let type = printf('%-' . s:type_length . 's', type)
		let auth = m['author'] == '' ? ''    :       m['author'][:20] . ' '
		let auth = substitute(auth, '\~', ' ', 'g')
		let auth = substitute(auth, ',.*\ze', ' et al. ', '')
		let year = m['year']   == '' ? ''    : '(' . m['year']   . ')'
		let w = { 'word': m['key'],
				\ 'abbr': type . auth . year,
				\ 'menu': m['title'] }

		" close braces if needed
		if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^\s*[,}]')
			let w.word = w.word . '}'
		endif

		call add(res, w)
	endfor
	return res
endfunction
" }}}

" ExtractLabels {{{
" Generate list of \newlabel commands in current buffer.
"
" Searches the current buffer for commands of the form
"	\newlabel{name}{{number}{page}.*
" and returns list of [ name, number, page ] tuples.
function! s:ExtractLabels()
	call cursor(1,1)

	let matches = []
	let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )

	while [lblline, lblbegin] != [0,0]
		let [nln, nameend] = searchpairpos( '{', '', '}', 'W' )
		if nln != lblline
			let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
			continue
		endif
		let curname = strpart( getline( lblline ), lblbegin, nameend - lblbegin - 1 )

		" Ignore cref entries (because they are duplicates)
		if curname =~# "@cref$"
		    let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
			continue
		endif

		if 0 == search( '\m{\w*{', 'ce', lblline )
		    let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
		    continue
		endif

		let numberbegin = getpos('.')[2]
		let [nln, numberend]  = searchpairpos( '{', '', '}', 'W' )
		if nln != lblline
			let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
			continue
		endif
		let curnumber = strpart( getline( lblline ), numberbegin, numberend - numberbegin - 1 )

		if 0 == search( '\m\w*{', 'ce', lblline )
		    let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
		    continue
		endif

		let pagebegin = getpos('.')[2]
		let [nln, pageend]  = searchpairpos( '{', '', '}', 'W' )
		if nln != lblline
			let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
			continue
		endif
		let curpage = strpart( getline( lblline ), pagebegin, pageend - pagebegin - 1 )

		let matches += [ [ curname, curnumber, curpage ] ]

		let [lblline, lblbegin] = searchpos( '\\newlabel{', 'ecW' )
	endwhile

	return matches
endfunction
"}}}

" ExtractInputs {{{
" Generate list of \@input commands in current buffer.
"
" Searches the current buffer for \@input{file} entries and
" returns list of all files.
function! s:ExtractInputs()
	call cursor(1,1)

	let matches = []
	let [inline, inbegin] = searchpos( '\\@input{', 'ecW' )

	while [inline, inbegin] != [0,0]
		let [nln, inend] = searchpairpos( '{', '', '}', 'W' )
		if nln != inline
			let [inline, inbegin] = searchpos( '\\@input{', 'ecW' )
			continue
		endif
		let matches += [ LatexBox_kpsewhich(strpart( getline( inline ), inbegin, inend - inbegin - 1 )) ]

		let [inline, inbegin] = searchpos( '\\@input{', 'ecW' )
	endwhile

	" Remove empty strings for nonexistant .aux files
	return filter(matches, 'v:val != ""')
endfunction
"}}}

" LabelCache {{{
" Cache of all labels.
"
" LabelCache is a dictionary mapping filenames to tuples
" [ time, labels, inputs ]
" where
" * time is modification time of the cache entry
" * labels is a list like returned by ExtractLabels
" * inputs is a list like returned by ExtractInputs
let s:LabelCache = {}
"}}}

" GetLabelCache {{{
" Extract labels from LabelCache and update it.
"
" Compares modification time of each entry in the label
" cache and updates it, if necessary. During traversal of
" the LabelCache, all current labels are collected and
" returned.
function! s:GetLabelCache(file)
	if !filereadable(a:file)
		return []
	endif

	if !has_key(s:LabelCache , a:file) || s:LabelCache[a:file][0] != getftime(a:file)
		" Open file in temporary split window for label extraction.
		let main_tex_file = LatexBox_GetMainTexFile()
		silent execute '1sp +let\ b:main_tex_file=main_tex_file|let\ labels=s:ExtractLabels()|let\ inputs=s:ExtractInputs()|quit! ' . fnameescape(a:file)
		let s:LabelCache[a:file] = [ getftime(a:file), labels, inputs ]
	endif

	" We need to create a copy of s:LabelCache[fid][1], otherwise all inputs'
	" labels would be added to the current file's label cache upon each
	" completion call, leading to duplicates/triplicates/etc. and decreased
	" performance.
	" Also, because we don't anything with the list besides matching copies,
	" we can get away with a shallow copy for now.
	let labels = copy(s:LabelCache[a:file][1])

	for input in s:LabelCache[a:file][2]
		let labels += s:GetLabelCache(input)
	endfor

	return labels
endfunction
"}}}

" Complete Labels {{{
function! s:CompleteLabels(regex)
	let labels = s:GetLabelCache(LatexBox_GetAuxFile())

	let matches = filter( copy(labels), 'match(v:val[0], "' . a:regex . '") != -1' )
	if empty(matches)
		" also try to match label and number
		let regex_split = split(a:regex)
		if len(regex_split) > 1
			let base = regex_split[0]
			let number = escape(join(regex_split[1:], ' '), '.')
			let matches = filter( copy(labels), 'match(v:val[0], "' . base . '") != -1 && match(v:val[1], "' . number . '") != -1' )
		endif
	endif
	if empty(matches)
		" also try to match number
		let matches = filter( copy(labels), 'match(v:val[1], "' . a:regex . '") != -1' )
	endif

	let suggestions = []
	for m in matches
		let entry = {'word': m[0], 'menu': printf("%7s [p. %s]", '('.m[1].')', m[2])}
		if g:LatexBox_completion_close_braces && !s:NextCharsMatch('^\s*[,}]')
			" add trailing '}'
			let entry = copy(entry)
			let entry.abbr = entry.word
			let entry.word = entry.word . '}'
		endif
		call add(suggestions, entry)
	endfor

	return suggestions
endfunction
" }}}

" Complete Inline Math Or Not {{{
" Return 1, when cursor is in a math env:
" 	1, there is a single $ in the current line on the left of cursor
" 	2, there is an open-eq-env on/above the current line
" 		(open-eq-env : \(, \[, and \begin{eq-env} )
" Return 0, when cursor is not in a math env
function! s:LatexBox_complete_inlineMath_or_not()

	" switch of inline math completion feature
	if g:LatexBox_complete_inlineMath == 0
		return 0
	endif

    " env names that can't appear in an eq env
	if !exists('s:LatexBox_doc_structure_patterns')
		let s:LatexBox_doc_structure_patterns = '\%(' .  '\\begin\s*{document}\|' .
					\ '\\\%(chapter\|section\|subsection\|subsubsection\)\*\?\s*{' . '\)'
	endif

	if !exists('s:LatexBox_eq_env_open_patterns')
		let s:LatexBox_eq_env_open_patterns = ['\\(','\\\[']
	endif
	if !exists('s:LatexBox_eq_env_close_patterns')
		let s:LatexBox_eq_env_close_patterns = ['\\)','\\\]']
	endif

	let notcomment = '\%(\%(\\\@<!\%(\\\\\)*\)\@<=%.*\)\@<!'

	let lnum_saved = line('.')
    let cnum_saved = col('.') -1

    let line = getline('.')
	let line_start_2_cnum_saved = line[:cnum_saved]

	" determine whether there is a single $ before cursor
	let cursor_dollar_pair = 0
	while matchend(line_start_2_cnum_saved, '\$[^$]\+\$', cursor_dollar_pair) >= 0
		" find the end of dollar pair
		let cursor_dollar_pair = matchend(line_start_2_cnum_saved, '\$[^$]\+\$', cursor_dollar_pair)
	endwhile
	" find single $ after cursor_dollar_pair
	let cursor_single_dollar = matchend(line_start_2_cnum_saved, '\$', cursor_dollar_pair)

	" if single $ is found
	if cursor_single_dollar >= 0
		" check whether $ is in \(...\), \[...\], or \begin{eq}...\end{eq}

		" check current line,
		" search for LatexBox_eq_env_close_patterns: \[ and \(
		let lnum = line('.')
		for i in range(0, (len(s:LatexBox_eq_env_open_patterns)-1))
			call cursor(lnum_saved, cnum_saved)
			let cnum_close = searchpos(''. s:LatexBox_eq_env_close_patterns[i].'', 'cbW', lnum_saved)[1]
			let cnum_open = matchend(line_start_2_cnum_saved, s:LatexBox_eq_env_open_patterns[i], cnum_close)
			if cnum_open >= 0
				let s:eq_dollar_parenthesis_bracket_empty = ''
				let s:eq_pos = cursor_single_dollar - 1
				return 1
			end
		endfor

		" check the lines above
		" search for s:LatexBox_doc_structure_patterns, and end-of-math-env
		let lnum -= 1
		while lnum > 0
			let line = getline(lnum)
			if line =~ notcomment . '\(' . s:LatexBox_doc_structure_patterns .
						\ '\|' . '\\end\s*{\(' . g:LatexBox_eq_env_patterns . '\)\*\?}\)'
				" when s:LatexBox_doc_structure_patterns or g:LatexBox_eq_env_patterns
				" are found first, complete math, leave with $ at both sides
				let s:eq_dollar_parenthesis_bracket_empty = '$'
				let s:eq_pos = cursor_single_dollar
				break
			elseif line =~ notcomment . '\\begin\s*{\(' . g:LatexBox_eq_env_patterns . '\)\*\?}'
				" g:LatexBox_eq_env_patterns is found, complete math, remove $
				let s:eq_dollar_parenthesis_bracket_empty = ''
				let s:eq_pos = cursor_single_dollar - 1
				break
			endif
			let lnum -= 1
		endwhile

		return 1
	else
		" no $ is found, then search for \( or \[ in current line
		" 1, whether there is \(
		call cursor(lnum_saved, cnum_saved)
		let cnum_parenthesis_close = searchpos('\\)', 'cbW', lnum_saved)[1]
		let cnum_parenthesis_open = matchend(line_start_2_cnum_saved, '\\(', cnum_parenthesis_close)
		if cnum_parenthesis_open >= 0
			let s:eq_dollar_parenthesis_bracket_empty = '\)'
			let s:eq_pos = cnum_parenthesis_open
			return 1
		end

		" 2, whether there is \[
		call cursor(lnum_saved, cnum_saved)
		let cnum_bracket_close = searchpos('\\\]', 'cbW', lnum_saved)[1]
		let cnum_bracket_open = matchend(line_start_2_cnum_saved, '\\\[', cnum_bracket_close)
		if cnum_bracket_open >= 0
			let s:eq_dollar_parenthesis_bracket_empty = '\]'
			let s:eq_pos = cnum_bracket_open
			return 1
		end

		" not inline math completion
		return 0
	endif

endfunction
" }}}

" Complete inline euqation{{{
function! s:LatexBox_inlineMath_completion(regex, ...)

	if a:0 == 0
		let file = LatexBox_GetMainTexFile()
	else
		let file = a:1
	endif

	if empty(glob(file, 1))
		return ''
	endif

	if empty(s:eq_dollar_parenthesis_bracket_empty)
		let inline_pattern1 = '\$\s*\(' . escape(substitute(a:regex[1:], '^\s\+', '', ""), '\.*^') . '[^$]*\)\s*\$'
		let inline_pattern2 = '\\(\s*\(' . escape(substitute(a:regex[1:], '^\s\+', '', ""), '\.*^') . '.*\)\s*\\)'
	else
		let inline_pattern1 = '\$\s*\(' . escape(substitute(a:regex, '^\s\+', '', ""), '\.*^') . '[^$]*\)\s*\$'
		let inline_pattern2 = '\\(\s*\(' . escape(substitute(a:regex, '^\s\+', '', ""), '\.*^') . '.*\)\s*\\)'
	endif


	let suggestions = []
	let line_num = 0
	for line in readfile(file)
		let line_num = line_num + 1

		let suggestions += s:LatexBox_inlineMath_mathlist(line,inline_pattern1 , line_num) +  s:LatexBox_inlineMath_mathlist( line,inline_pattern2, line_num)

 		" search for included files
 		let included_file = matchstr(line, '^\\@input{\zs[^}]*\ze}')
 		if included_file != ''
 			let included_file = LatexBox_kpsewhich(included_file)
 			call extend(suggestions, s:LatexBox_inlineMath_completion(a:regex, included_file))
 		endif
 	endfor

	return suggestions
endfunction
" }}}

" Search for inline maths {{{
" search for $ ... $ and \( ... \) in each line
function! s:LatexBox_inlineMath_mathlist(line,inline_pattern, line_num)
	let col_start = 0
	let suggestions = []
	while 1
		let matches = matchlist(a:line, a:inline_pattern, col_start)
		if !empty(matches)

			" show line number of inline math
			let entry = {'word': matches[1], 'menu': '[' . a:line_num . ']'}

            if  s:eq_dollar_parenthesis_bracket_empty != ''
                let entry = copy(entry)
                let entry.abbr = entry.word
                let entry.word = entry.word . s:eq_dollar_parenthesis_bracket_empty
            endif
			call add(suggestions, entry)

			" update col_start
			let col_start = matchend(a:line, a:inline_pattern, col_start)
		else
			break
		endif
	endwhile

	return suggestions
endfunction
" }}}

" Close Current Environment {{{
function! s:CloseCurEnv()
	" first, try with \left/\right pairs
	let [lnum, cnum] = searchpairpos('\C\\left\>', '', '\C\\right\>', 'bnW', 'LatexBox_InComment()')
	if lnum
		let line = strpart(getline(lnum), cnum - 1)
		let bracket = matchstr(line, '^\\left\zs\((\|\[\|\\{\||\|\.\)\ze')
		for [open, close] in [['(', ')'], ['\[', '\]'], ['\\{', '\\}'], ['|', '|'], ['\.', '|']]
			let bracket = substitute(bracket, open, close, 'g')
		endfor
		return '\right' . bracket
	endif

	" second, try with environments
	let env = LatexBox_GetCurrentEnvironment()
	if env == '\['
		return '\]'
	elseif env == '\('
		return '\)'
	elseif env != ''
		return '\end{' . env . '}'
	endif
	return ''
endfunction
" }}}

" Wrap Selection {{{
function! s:WrapSelection(wrapper)
	keepjumps normal! `>a}
	execute 'keepjumps normal! `<i\' . a:wrapper . '{'
endfunction
" }}}

" Wrap Selection in Environment with Prompt {{{
function! s:PromptEnvWrapSelection(...)
	let env = input('environment: ', '', 'customlist,' . s:SIDWrap('GetEnvironmentList'))
	if empty(env)
		return
	endif
	" LaTeXBox's custom indentation can interfere with environment
	" insertion when environments are indented (common for nested
	" environments).  Temporarily disable it for this operation:
	let ieOld = &indentexpr
	setlocal indentexpr=""
	if visualmode() ==# 'V'
		execute 'keepjumps normal! `>o\end{' . env . '}'
		execute 'keepjumps normal! `<O\begin{' . env . '}'
		" indent and format, if requested.
		if a:0 && a:1
			normal! gv>
			normal! gvgq
		endif
	else
		execute 'keepjumps normal! `>a\end{' . env . '}'
		execute 'keepjumps normal! `<i\begin{' . env . '}'
	endif
	exe "setlocal indentexpr=" . ieOld
endfunction
" }}}

" List Labels with Prompt {{{
function! s:PromptLabelList(...)
	" Check if window already exists
	let winnr = bufwinnr(bufnr('LaTeX Labels'))
	if winnr >= 0
		if a:0 == 0
			silent execute winnr . 'wincmd w'
		else
			" Supplying an argument to this function causes toggling instead
			" of jumping to the labels window
			if g:LatexBox_split_resize
				silent exe "set columns-=" . g:LatexBox_split_width
			endif
			silent execute 'bwipeout' . bufnr('LaTeX Labels')
		endif
		return
	endif

	" Get label suggestions
	let regexp = input('filter labels with regexp: ', '')
	let labels = s:CompleteLabels(regexp)

	let calling_buf = bufnr('%')

	" Create labels window and set local settings
	if g:LatexBox_split_resize
		silent exe "set columns+=" . g:LatexBox_split_width
	endif
	silent exe g:LatexBox_split_side g:LatexBox_split_width . 'vnew LaTeX\ Labels'
	let b:toc = []
	let b:toc_numbers = 1
	let b:calling_win = bufwinnr(calling_buf)
	setlocal filetype=latextoc

	" Add label entries and jump to the closest section
	for entry in labels
		let number = matchstr(entry['menu'], '^\s*(\zs[^)]\+\ze)')
		let page = matchstr(entry['menu'], '^[^)]*)\s*\[\zs[^]]\+\ze\]')
		let e = {'file': bufname(calling_buf),
					\ 'level': 'label',
					\ 'number': number,
					\ 'text': entry['abbr'],
					\ 'page': page}
		call add(b:toc, e)
		if b:toc_numbers
			call append('$', e['number'] . "\t" . e['text'])
		else
			call append('$', e['text'])
		endif
	endfor
	if !g:LatexBox_toc_hidehelp
		call append('$', "")
		call append('$', "<Esc>/q: close")
		call append('$', "<Space>: jump")
		call append('$', "<Enter>: jump and close")
		call append('$', "s:       hide numbering")
	endif
	0delete _

	" Lock buffer
	setlocal nomodifiable
endfunction
" }}}

" Change Environment {{{
function! s:ChangeEnvPrompt()

	let [env, lnum, cnum, lnum2, cnum2] = LatexBox_GetCurrentEnvironment(1)

	let new_env = input('change ' . env . ' for: ', '', 'customlist,' . s:SIDWrap('GetEnvironmentList'))
	if empty(new_env)
		return
	endif

	if new_env == '\[' || new_env == '['
		let begin = '\['
		let end = '\]'
	elseif new_env == '\(' || new_env == '('
		let begin = '\('
		let end = '\)'
	else
		let l:begin = '\begin{' . new_env . '}'
		let l:end = '\end{' . new_env . '}'
	endif

	if env == '\[' || env == '\('
		let line = getline(lnum2)
		let line = strpart(line, 0, cnum2 - 1) . l:end . strpart(line, cnum2 + 1)
		call setline(lnum2, line)

		let line = getline(lnum)
		let line = strpart(line, 0, cnum - 1) . l:begin . strpart(line, cnum + 1)
		call setline(lnum, line)
	else
		let line = getline(lnum2)
		let line = strpart(line, 0, cnum2 - 1) . l:end . strpart(line, cnum2 + len(env) + 5)
		call setline(lnum2, line)

		let line = getline(lnum)
		let line = strpart(line, 0, cnum - 1) . l:begin . strpart(line, cnum + len(env) + 7)
		call setline(lnum, line)
	endif
endfunction

function! s:GetEnvironmentList(lead, cmdline, pos)
	let suggestions = []
	for entry in g:LatexBox_completion_environments
		let env = entry.word
		if env =~ '^' . a:lead
			call add(suggestions, env)
		endif
	endfor
	return suggestions
endfunction

function! s:LatexToggleStarEnv()
	let [env, lnum, cnum, lnum2, cnum2] = LatexBox_GetCurrentEnvironment(1)

	if env == '\('
		return
	elseif env == '\['
		let begin = '\begin{equation}'
		let end = '\end{equation}'
	elseif env[-1:] == '*'
		let begin = '\begin{' . env[:-2] . '}'
		let end   = '\end{'   . env[:-2] . '}'
	else
		let begin = '\begin{' . env . '*}'
		let end   = '\end{'   . env . '*}'
	endif

	if env == '\['
		let line = getline(lnum2)
		let line = strpart(line, 0, cnum2 - 1) . l:end . strpart(line, cnum2 + 1)
		call setline(lnum2, line)

		let line = getline(lnum)
		let line = strpart(line, 0, cnum - 1) . l:begin . strpart(line, cnum + 1)
		call setline(lnum, line)
	else
		let line = getline(lnum2)
		let line = strpart(line, 0, cnum2 - 1) . l:end . strpart(line, cnum2 + len(env) + 5)
		call setline(lnum2, line)

		let line = getline(lnum)
		let line = strpart(line, 0, cnum - 1) . l:begin . strpart(line, cnum + len(env) + 7)
		call setline(lnum, line)
	endif
endfunction
" }}}

" Next Charaters Match {{{
function! s:NextCharsMatch(regex)
	let rest_of_line = strpart(getline('.'), col('.') - 1)
	return rest_of_line =~ a:regex
endfunction
" }}}

" Mappings {{{
inoremap <silent> <Plug>LatexCloseCurEnv			<C-R>=<SID>CloseCurEnv()<CR>
vnoremap <silent> <Plug>LatexWrapSelection			:<c-u>call <SID>WrapSelection('')<CR>i
vnoremap <silent> <Plug>LatexEnvWrapSelection		:<c-u>call <SID>PromptEnvWrapSelection()<CR>
vnoremap <silent> <Plug>LatexEnvWrapFmtSelection	:<c-u>call <SID>PromptEnvWrapSelection(1)<CR>
nnoremap <silent> <Plug>LatexChangeEnv				:call <SID>ChangeEnvPrompt()<CR>
nnoremap <silent> <Plug>LatexToggleStarEnv			:call <SID>LatexToggleStarEnv()<CR>
" }}}

" Commands {{{
command! LatexLabels call <SID>PromptLabelList()
" }}}

" vim:fdm=marker:ff=unix:noet:ts=4:sw=4