"======================================================================
"
" context.vim - 
"
" Created by skywind on 2019/12/19
" Last Modified: 2022/08/24 20:05
"
"======================================================================

" vim: set noet fenc=utf-8 ff=unix sts=4 sw=4 ts=4 :

"----------------------------------------------------------------------
" stats
"----------------------------------------------------------------------

" last position
let g:quickui#context#cursor = -1



"----------------------------------------------------------------------
" compile
"----------------------------------------------------------------------
function! quickui#context#compile(items, border)
	let menu = {'border':a:border}
	let items = []
	let helps = []
	let size_l = 0
	let size_r = 0
	let index = 0
	let helps = 0
	for item in a:items
		let ni = quickui#utils#item_parse(item)
		let ni.index = index
		let items += [ni]	
		let index += 1
		if ni.help != '' 
			let helps += 1
		endif
		" echo ni
		if ni.text_width > size_l 
			let size_l = ni.text_width
		endif
		if ni.desc_width > size_r
			let size_r = ni.desc_width
		endif
	endfor
	let stride = size_l + size_r + ((size_r > 0)? 2 : 0)
	for item in items
		call quickui#utils#context_align(item, size_l, size_r)
	endfor
	let menu.items = items
	let menu.helps = helps
	let image = []
	if a:border <= 0
		for item in items
			let image += [item.content]
		endfor
	else
		let pattern = quickui#core#border_get(a:border)
		let text = pattern[0] . repeat(pattern[1], stride + 2) . pattern[2]
		let image += [text]
		for item in items
			if item.is_sep
				let text = pattern[9] . repeat(pattern[4], stride + 2) . pattern[10]
				let image += [text]
			else
				let text = pattern[3] . ' ' . item.content . ' ' . pattern[5]
				let image += [text]
			endif
		endfor
		let text = pattern[6] . repeat(pattern[7], stride + 2) . pattern[8]
		let image += [text]
	endif
	let menu.image = image
	let menu.border = a:border
	let menu.height = len(image)
	let menu.width = (menu.height > 0)? strwidth(image[0]) : 0
	let menu.selected = -1
	let menu.size = len(items)
	let menu.exiting = 0
	let menu.state = 0
	let selection = []
	for item in menu.items
		if item.is_sep == 0
			let selection += [item.index]
		endif
	endfor
	let menu.selection = selection
	return menu
endfunc


"----------------------------------------------------------------------
" create menu object
"----------------------------------------------------------------------
function! s:vim_create_context(textlist, opts)
	let border = get(a:opts, 'border', g:quickui#style#border)
	let hwnd = quickui#context#compile(a:textlist, border)
	if 0
		let hwnd.bid = quickui#core#scratch_buffer('context', hwnd.image)
		let winid = popup_create(hwnd.bid, {'hidden':1, 'wrap':0})
	else
		let winid = popup_create(hwnd.image, {'hidden':1, 'wrap':0})
	endif
	let w = hwnd.width
	let h = hwnd.height
	let hwnd.winid = winid
	let hwnd.index = get(a:opts, 'index', -1)
	let hwnd.opts = deepcopy(a:opts)
	let ignore_case = get(a:opts, 'ignore_case', 1)
	let opts = {'minwidth':w, 'maxwidth':w, 'minheight':h, 'maxheight':h}
	if has_key(a:opts, 'line') && has_key(a:opts, 'col')
		let opts.line = a:opts.line
		let opts.col = a:opts.col
	else
		let pos = quickui#core#around_cursor(w, h)
		let opts.line = pos[0]
		let opts.col = pos[1]
	endif
	call popup_move(winid, opts)
	call setwinvar(winid, '&wincolor', get(a:opts, 'color', 'QuickBG'))
	let opts = {'cursorline':0, 'drag':0, 'mapping':0}
	let opts.border = [0,0,0,0,0,0,0,0,0]
	let opts.title = has_key(a:opts, 'title')? ' ' . a:opts.title . ' ' : ''
	let opts.padding = [0,0,0,0]
	let keymap = quickui#utils#keymap()
	let keymap['J'] = 'BOTTOM'
	let keymap['K'] = 'TOP'
	if has_key(a:opts, 'keymap')
		for key in keys(a:opts.keymap)
			let keymap[key] = a:opts.keymap[key]
		endfor
	endif
	let hwnd.code = 0
	let hwnd.state = 1
	let hwnd.keymap = keymap
	let hwnd.hotkey = {}
	for item in hwnd.items
		if item.enable != 0 && item.key_pos >= 0
			let key = ignore_case ? tolower(item.key_char) : item.key_char
			if get(a:opts, 'reserve', 0) == 0
				let hwnd.hotkey[key] = item.index
			elseif get(g:, 'quickui_protect_hjkl', 0) != 0
				let hwnd.hotkey[key] = item.index
			else
				if key != 'h' && key != 'j' && key != 'k' && key != 'l'
					let hwnd.hotkey[key] = item.index
				endif
			endif
		endif
	endfor
	let local = quickui#core#popup_local(winid)
	let local.hwnd = hwnd
	if get(a:opts, 'manual', 0) == 0
		let opts.callback = function('s:popup_exit')
		let opts.filter = function('s:popup_filter')
	endif
	if has_key(a:opts, 'zindex')
		let opts.zindex = a:opts.zindex
	endif
	call popup_setoptions(winid, opts)
	call quickui#context#update(hwnd)
	call popup_show(winid)
	return hwnd
endfunc


"----------------------------------------------------------------------
" render menu 
"----------------------------------------------------------------------
function! quickui#context#update(hwnd)
	let winid = a:hwnd.winid
	let size = len(a:hwnd.items)
	let w = a:hwnd.width
	let h = a:hwnd.height
	let cmdlist = ['syn clear']
	for item in a:hwnd.items
		let index = item.index
		if item.enable == 0 && item.is_sep == 0
			if a:hwnd.border == 0
				let py = index + 1
				let px = 1
				let ps = px + w
			else
				let py = index + 2
				let px = 3
				let ps = px + w - 4
			endif
			let cmd = quickui#core#high_region('QuickOff', py, px, py, ps, 1)
			let cmdlist += [cmd]
		elseif item.key_pos >= 0
			if a:hwnd.border == 0
				let px = item.key_pos + 1
				let py = index + 1
			else
				let px = item.key_pos + 3
				let py = index + 2
			endif
			let ps = px + 1
			let cmd = quickui#core#high_region('QuickKey', py, px, py, ps, 1)
			let cmdlist += [cmd]
		endif
		if index == a:hwnd.index
			if a:hwnd.border == 0
				let py = index + 1
				let px = 1
				let ps = px + w
			else
				let py = index + 2
				let px = 2
				let ps = px + w - 2
			endif
			let cmd = quickui#core#high_region('QuickSel', py, px, py, ps, 1)
			let cmdlist += [cmd]
		endif
	endfor
	call quickui#core#win_execute(winid, cmdlist)
	if a:hwnd.state != 0
		redraw
		if get(g:, 'quickui_show_tip', 0) != 0
			let help = ''
			if a:hwnd.index >= 0 && a:hwnd.index < len(a:hwnd.items)
				let help = a:hwnd.items[a:hwnd.index].help
				let head = g:quickui#style#tip_head
				if help != ''
					let help = quickui#core#expand_text(help)
					let help = '' . ((head != '')? (head . ' ') : '') . help
				endif
			endif
			echohl QuickHelp
			echo help
			echohl None
		endif
	endif
endfunc


"----------------------------------------------------------------------
" close context
"----------------------------------------------------------------------
function! quickui#context#close(hwnd)
	if a:hwnd.winid > 0
		call popup_close(a:hwnd.winid)
		call quickui#core#popup_clear(a:hwnd.winid)
		let a:hwnd.winid = -1
	endif
endfunc


"----------------------------------------------------------------------
" handle exit code
"----------------------------------------------------------------------
function! s:popup_exit(winid, code)
	let local = quickui#core#popup_local(a:winid)
	if !has_key(local, 'hwnd')
		return 0
	endif
	let hwnd = local.hwnd
	let code = a:code
	let hwnd.state = 0
	let hwnd.code = code
	call quickui#core#popup_clear(a:winid)
	if get(g:, 'quickui_show_tip', 0) != 0
		if get(hwnd.opts, 'lazyredraw', 0) == 0
			redraw
			echo ''
			redraw
		endif
	endif
	let g:quickui#context#code = code
	let g:quickui#context#current = hwnd
	let g:quickui#context#cursor = hwnd.index
	let redrawed = 0
	if has_key(hwnd.opts, 'callback')
		call call(hwnd.opts.callback, [code])
	endif
	silent! call popup_hide(a:winid)
	if code >= 0 && code < len(hwnd.items)
		let item = hwnd.items[code]
		if item.is_sep == 0 && item.enable != 0
			if item.cmd != ''
				redraw
				try
					exec item.cmd
				catch /.*/
					echohl Error
					echom v:exception
					echohl None
				endtry
			endif
		endif
	endif
	return 0
endfunc


"----------------------------------------------------------------------
" key processing
"----------------------------------------------------------------------
function! s:popup_filter(winid, key)
	let local = quickui#core#popup_local(a:winid)
	let hwnd = local.hwnd
	let winid = hwnd.winid
	if a:key == "\<ESC>" || a:key == "\<c-c>"
		call popup_close(a:winid, -1)
		return 1
	elseif a:key == "\<CR>" || a:key == "\<SPACE>"
		call s:on_confirm(hwnd)
		return 1
	elseif a:key == "\<LeftMouse>"
		return s:on_click(hwnd)
	elseif has_key(hwnd.hotkey, a:key)
		let key = hwnd.hotkey[a:key]
		if key >= 0 && key < len(hwnd.items)
			let item = hwnd.items[key]
			if item.is_sep == 0 && item.enable != 0
				let hwnd.index = key
				call quickui#context#update(hwnd)
				call popup_setoptions(winid, {})
				redraw
				call popup_close(winid, key)
				return 1
			endif
		endif
	elseif has_key(hwnd.keymap, a:key)
		let key = hwnd.keymap[a:key]
		if key == 'ESC'
			call popup_close(a:winid, -1)
			return 1
		elseif key == 'UP'
			let hwnd.index = s:cursor_move(hwnd, hwnd.index, -1)
		elseif key == 'DOWN'
			let hwnd.index = s:cursor_move(hwnd, hwnd.index, 1)
		elseif key == 'TOP'
			let hwnd.index = s:cursor_move(hwnd, hwnd.index, 'TOP')
		elseif key == 'BOTTOM'
			let hwnd.index = s:cursor_move(hwnd, hwnd.index, 'BOTTOM')
		endif
		if get(hwnd.opts, 'horizon', 0) != 0
			if key == 'LEFT'
				call popup_close(a:winid, -1000)
			elseif key == 'RIGHT'
				call popup_close(a:winid, -2000)
			elseif key == 'PAGEUP'
				call popup_close(a:winid, -1001)
			elseif key == 'PAGEDOWN'
				call popup_close(a:winid, -2001)
			endif
		endif
		call quickui#context#update(hwnd)
		return 1
	endif
	return 1
endfunc


"----------------------------------------------------------------------
" press enter or space
"----------------------------------------------------------------------
function! s:on_confirm(hwnd)
	let index = a:hwnd.index
	if index < 0 || index > len(a:hwnd.items)
		return 1
	endif
	let item = a:hwnd.items[index]
	if item.is_sep || item.enable == 0
		return 1
	endif
	call popup_close(a:hwnd.winid, index)
	return 1
endfunc


"----------------------------------------------------------------------
" mouse left click
"----------------------------------------------------------------------
function! s:on_click(hwnd)
	let hwnd = a:hwnd
	let winid = a:hwnd.winid
	if g:quickui#core#has_nvim == 0
		let pos = getmousepos()
		if pos.winid != winid
			call popup_close(winid, -2)
			return 0
		endif
		let index = -1
		if hwnd.border == 0
			let index = pos.line - 1
		else
			if pos.column > 1 && pos.column < hwnd.width
				if pos.line > 1 && pos.line < hwnd.height
					let index = pos.line - 2
				endif
			endif
		endif
		if index >= 0 && index < len(hwnd.items)
			let item = hwnd.items[index]
			if item.is_sep == 0 && item.enable != 0
				let hwnd.index = index
				call quickui#context#update(hwnd)
				call popup_setoptions(winid, {})
				redraw
				call popup_close(winid, index)
			endif
		endif
		return 1
	else
		if v:mouse_winid != winid
			return -2
		endif
		let index = -1
		if hwnd.border == 0
			let index = v:mouse_lnum - 1
		else
			if v:mouse_col > 1 && v:mouse_col < hwnd.width
				if v:mouse_lnum > 1 && v:mouse_lnum < hwnd.height
					let index = v:mouse_lnum - 2
				endif
			endif
		endif
		if index >= 0 && index < len(hwnd.items)
			let item = hwnd.items[index]
			if item.is_sep == 0 && item.enable != 0
				let hwnd.index = index
				call quickui#context#update(hwnd)
				redraw
				sleep 60m
				return index
			endif
		endif
		return -1
	endif
endfunc


"----------------------------------------------------------------------
" move cursor
"----------------------------------------------------------------------
function! s:cursor_move(menu, cursor, toward)
	let size = a:menu.size
	if type(a:toward) == v:t_number 
		if a:toward == 0 
			if size <= 0
				return -1
			elseif a:cursor < 0
				return a:cursor
			endif
			return (a:cursor >= size)? (size - 1) : a:cursor
		endif
	endif
	let selection = a:menu.selection
	if size == 0 || len(selection) == 0
		return -1
	endif
	let cursor = a:cursor + a:toward
	if type(a:toward) == v:t_number
		if a:toward < 0
			if a:cursor < 0
				return selection[0]
			endif
			let cursor = (cursor < 0)? 0 : cursor
			let pos = len(selection) - 1
			while pos >= 0
				if selection[pos] <= cursor
					return selection[pos]
				endif
				let pos -= 1
			endwhile
			return selection[0]
		else
			let pos = 0
			let limit = len(selection)
			while pos < limit
				if selection[pos] >= cursor
					return selection[pos]
				endif
				let pos += 1
			endwhile
			return selection[limit - 1]
		endif
	else
		if a:toward == 'TOP'
			return selection[0]
		elseif a:toward == 'BOTTOM'
			return selection[-1]
		endif
	endif
	return -1
endfunc


"----------------------------------------------------------------------
" open context menu in neovim and returns index
"----------------------------------------------------------------------
function! s:nvim_create_context(textlist, opts)
	let border = get(a:opts, 'border', g:quickui#style#border)
	let ignore_case = get(a:opts, 'ignore_case', 1)
	let hwnd = quickui#context#compile(a:textlist, border)
	let bid = quickui#core#scratch_buffer('context', hwnd.image)
	let hwnd.bid = bid
	let w = hwnd.width
	let h = hwnd.height
	let hwnd.index = get(a:opts, 'index', -1)
	let hwnd.opts = deepcopy(a:opts)
	let opts = {'width':w, 'height':h, 'focusable':1, 'style':'minimal'}
	let opts.relative = 'editor'
	if has_key(a:opts, 'line') && has_key(a:opts, 'col')
		let opts.row = a:opts.line - 1
		let opts.col = a:opts.col - 1
	else
		let pos = quickui#core#around_cursor(w, h)
		let opts.row = pos[0] - 1
		let opts.col = pos[1] - 1
	endif
	if has('nvim-0.6.0')
		let opts.noautocmd = 1
	endif
	let winid = nvim_open_win(bid, 0, opts)
	let hwnd.winid = winid
	let keymap = quickui#utils#keymap()
	let keymap['J'] = 'BOTTOM'
	let keymap['K'] = 'TOP'
	let hwnd.code = 0
	let hwnd.state = 1
	let hwnd.keymap = keymap
	let hwnd.hotkey = {}
	for item in hwnd.items
		if item.enable != 0 && item.key_pos >= 0
			let key = ignore_case ? tolower(item.key_char) : item.key_char
			if get(a:opts, 'reserve', 0) == 0
				let hwnd.hotkey[key] = item.index
			elseif get(g:, 'quickui_protect_hjkl', 0) != 0
				let hwnd.hotkey[key] = item.index
			else
				if key != 'h' && key != 'j' && key != 'k' && key != 'l'
					let hwnd.hotkey[key] = item.index
				endif
			endif
		endif
	endfor
	let hwnd.opts.color = get(a:opts, 'color', 'QuickBG')
    call nvim_win_set_option(winid, 'winhl', 'Normal:'. hwnd.opts.color)
	let retval = -1
	while 1
		noautocmd call quickui#context#update(hwnd)
		redraw
		try
			let code = getchar()
		catch /^Vim:Interrupt$/
			let code = "\<C-C>"
		endtry
		let ch = (type(code) == v:t_number)? nr2char(code) : code
		if ch == "\<ESC>" || ch == "\<c-c>"
			break
		elseif ch == " " || ch == "\<cr>"
			let index = hwnd.index
			if index >= 0 && index < len(hwnd.items)
				let item = hwnd.items[index]
				if item.is_sep == 0 && item.enable != 0
					let retval = index
					break
				endif
			endif
		elseif ch == "\<LeftMouse>"
			let hr = s:on_click(hwnd)
			if hr == -2 || hr >= 0
				let retval = hr
				break
			endif
		elseif has_key(hwnd.hotkey, ch)
			let hr = hwnd.hotkey[ch]
			if hr >= 0
				let hwnd.index = hr
				let retval = hr
				break
			endif
		elseif has_key(hwnd.keymap, ch)
			let key = hwnd.keymap[ch]
			if key == 'ESC'
				break
			elseif key == 'UP'
				let hwnd.index = s:cursor_move(hwnd, hwnd.index, -1)
			elseif key == 'DOWN'
				let hwnd.index = s:cursor_move(hwnd, hwnd.index, 1)
			elseif key == 'TOP'
				let hwnd.index = s:cursor_move(hwnd, hwnd.index, 'TOP')
			elseif key == 'BOTTOM'
				let hwnd.index = s:cursor_move(hwnd, hwnd.index, 'BOTTOM')
			endif
			if get(hwnd.opts, 'horizon', 0) != 0
				if key == 'LEFT'
					let retval = -1000
					break
				elseif key == 'RIGHT'
					let retval = -2000
					break
				elseif key == 'PAGEUP'
					let retval = -1001
					break
				elseif key == 'PAGEDOWN'
					let retval = -2001
					break
				endif
			endif
		endif
	endwhile
	call nvim_win_close(winid, 0)
	if get(a:opts, 'lazyredraw', 0) == 0
		redraw
	endif
	if get(g:, 'quickui_show_tip', 0) != 0
		if get(a:opts, 'lazyredraw', 0) == 0
			echo ''
			redraw
		endif
	endif
	let g:quickui#context#code = retval
	let g:quickui#context#current = hwnd
	let g:quickui#context#cursor = hwnd.index
	if has_key(hwnd.opts, 'callback')
		call call(hwnd.opts.callback, [retval])
	endif
	if retval >= 0 && retval < len(hwnd.items)
		let item = hwnd.items[retval]
		if item.is_sep == 0 && item.enable != 0
			if item.cmd != ''
				redraw
				try
					exec item.cmd
				catch /.*/
					echohl Error
					echom v:exception
					echohl None
				endtry
			endif
		endif
	endif
	return retval
endfunc


"----------------------------------------------------------------------
" reduce with file types
"----------------------------------------------------------------------
function! quickui#context#reduce_items(textlist)
	let output = []
	let state = 1
	let index = 0
	let limit = len(a:textlist)
	for item in a:textlist
		if len(item) > 0
			let issep = ((item[0]) =~ '^-\+$')? 1 : 0
			if issep != 0
				if state == 0
					if index + 1 < limit
						let output += [item]
						let state = 1
					endif
				endif
			else
				if type(item) == v:t_string
					let output += [item]
					let state = 0
				elseif len(item) < 4
					let output += [item]
					let state = 0
				else
					if quickui#utils#match_ft(&ft, item[3])
						let output += [item]
						let state = 0
					endif
				endif
			endif
		endif
		let index += 1
	endfor
	return output
endfunc


"----------------------------------------------------------------------
" create menu object
"----------------------------------------------------------------------
function! quickui#context#open(textlist, opts)
	let textlist = a:textlist
	if g:quickui#core#has_nvim == 0
		return s:vim_create_context(textlist, a:opts)
	else
		return s:nvim_create_context(textlist, a:opts)
	endif
endfunc


"----------------------------------------------------------------------
" open the context window and wait for selection
"----------------------------------------------------------------------
function! s:context_wait(textlist, opts) abort
	let border = get(a:opts, 'border', g:quickui#style#border)
	let ignore_case = get(a:opts, 'ignore_case', 1)
	let hwnd = quickui#context#compile(a:textlist, border)
	let cwnd = quickui#window#new()
	let w = hwnd.width
	let h = hwnd.height
	let hwnd.index = get(a:opts, 'index', -1)
	let hwnd.opts = a:opts
	let opts = {'w':w, 'h':h, 'border':0}
	if has_key(a:opts, 'x') && has_key(a:opts, 'y')
		let opts.x = a:opts.x
		let opts.y = a:opts.y
	else
		let pos = quickui#core#around_cursor(w, h)
		let opts.y = pos[0] - 1
		let opts.x = pos[1] - 1
	endif
	let opts.z = get(a:opts, 'z', 500)
	let opts.color = get(a:opts, 'color', 'QuickBG')
	let hwnd.direct = get(a:opts, 'direct', 0)
	if hwnd.direct == 0
		let hwnd.direct = (opts.x <= (&columns / 2))? 1 : -1
	endif
	call cwnd.open(hwnd.image, opts)
	call cwnd.show(1)
	let keymap = quickui#utils#keymap()
	let keymap['J'] = 'BOTTOM'
	let keymap['K'] = 'TOP'
	let hwnd.code = 0
	let hwnd.state = 1
	let hwnd.keymap = keymap
	let hwnd.hotkey = {}
	for item in hwnd.items
		if item.enable != 0 && item.key_pos >= 0
			let key = ignore_case ? tolower(item.key_char) : item.key_char
			if get(a:opts, 'reserve', 0) == 0
				let hwnd.hotkey[key] = item.index
			elseif get(g:, 'quickui_protect_hjkl', 0) != 0
				let hwnd.hotkey[key] = item.index
			else
				if key != 'h' && key != 'j' && key != 'k' && key != 'l'
					let hwnd.hotkey[key] = item.index
				endif
			endif
		endif
	endfor
	let hwnd.winid = cwnd.winid
	let retval = -1
	while 1
		noautocmd call quickui#context#update(hwnd)
		redraw
		try
			let code = getchar()
		catch /^Vim:Interrupt$/
			let code = "\<C-C>"
		endtry
		let ch = (type(code) == v:t_number)? nr2char(code) : code
		if ch == "\<ESC>" || ch == "\<c-c>"
			break
		elseif ch == " " || ch == "\<cr>"
			let index = hwnd.index
			if index >= 0 && index < len(hwnd.items)
				let item = hwnd.items[index]
				if item.is_sep == 0 && item.enable != 0
					let retval = index
					break
				endif
			endif
		elseif ch == "\<LeftMouse>"
			let pos = cwnd.mouse_click()
			if pos.x < 0
				break
			else
				let x1 = (hwnd.border == 0)? 0 : 1
				let x2 = (hwnd.border == 0)? w : (w - 1)
				let ii = (hwnd.border == 0)? pos.y : (pos.y - 1)
				if pos.x >= x1 && pos.x < x2
					if ii >= 0 && ii < len(hwnd.items)
						let item = hwnd.items[ii]
						if item.is_sep == 0 && item.enable != 0
							let hwnd.index = ii
							let retval = ii
							noautocmd call quickui#context#update(hwnd)
							redraw
							break
						endif
					endif
				endif
			endif
		elseif has_key(hwnd.hotkey, ch)
			let hr = hwnd.hotkey[ch]
			if hr >= 0
				let hwnd.index = hr
				let retval = hr
				break
			endif
		elseif has_key(hwnd.keymap, ch)
			let key = hwnd.keymap[ch]
			if key == 'ESC'
				break
			elseif key == 'UP'
				let hwnd.index = s:cursor_move(hwnd, hwnd.index, -1)
			elseif key == 'DOWN'
				let hwnd.index = s:cursor_move(hwnd, hwnd.index, 1)
			elseif key == 'TOP'
				let hwnd.index = s:cursor_move(hwnd, hwnd.index, 'TOP')
			elseif key == 'BOTTOM'
				let hwnd.index = s:cursor_move(hwnd, hwnd.index, 'BOTTOM')
			endif
		endif
	endwhile
	noautocmd call quickui#context#update(hwnd)
	let hr = ''
	if retval >= 0 && retval < len(hwnd.items)
		let item = hwnd.items[retval]
		if item.is_sep == 0 && item.enable != 0
			let hwnd.index = retval
			if !has_key(item, 'child')
				let hr = item.cmd
			else
				let child = item.child
				let cc = quickui#context#compile(child, border)
				let cw = cc.width
				let ch = cc.height
				unlet cc
				let op = {}
				let op.z = opts.z + 1
				let op.y = opts.y + retval
				let op.y = (op.y + ch > &lines)? (&lines - ch) : op.y
				let op.y = (op.y < 0)? 0 : op.y
				for i in range(5)
					if hwnd.direct >= 0
						let tx = opts.x + opts.w
						if tx + cw > &columns
							let hwnd.direct = -1
						else
							let op.x = tx
							break
						endif
					elseif hwnd.direct < 0
						let tx = opts.x - cw
						if tx < 0
							let hwnd.direct = 1
						else
							let op.x = tx
							break
						endif
					endif
				endfor
				if !has_key(op, 'x')
					let op.x = 1
				endif
				let op.direct = hwnd.direct
				let op.border = get(a:opts, 'border', border)
				let hr = s:context_wait(child, op)
			endif
		endif
	endif
	let g:quickui#context#cursor = hwnd.index
	let g:quickui#context#current = hwnd
	call cwnd.close()
	unlet cwnd
	return hr
endfunc


"----------------------------------------------------------------------
" open the context window and wait for selection
"----------------------------------------------------------------------
function! quickui#context#wait(textlist, opts) abort
	return s:context_wait(a:textlist, a:opts)
endfunc


"----------------------------------------------------------------------
" open context menu and execute commands
"----------------------------------------------------------------------
function! quickui#context#open_nested(textlist, opts) abort
	let cmd = s:context_wait(a:textlist, a:opts)
	if has_key(a:opts, 'callback')
		call a:opts.callback(0)
	endif
	if cmd != ''
		exec cmd
	endif
endfunc



"----------------------------------------------------------------------
" testing suit 
"----------------------------------------------------------------------
if 0
	call quickui#utils#highlight('default')
	let lines = [
				\ "&New File\tCtrl+n",
				\ "&Open File\tCtrl+o", 
				\ ["&Close", 'echo 1234', 'help 1'],
				\ "--",
				\ "&Save\tCtrl+s",
				\ "Save &As",
				\ "Save All",
				\ "--",
				\ "&User Menu\tF9",
				\ "&Dos Shell",
				\ "~&Time %{&undolevels? '+':'-'}",
				\ ["S&plit", 'help 1', '', 'vim2'],
				\ "--",
				\ "E&xit\tAlt+x",
				\ "&Help",
				\ ]
	" echo quickui#core#pattern_ascii
	" let menu = quickui#context#menu_compile(lines, 1)
	let opts = {'cursor': -1, 'line2':'cursor+1', 'col2': 'cursor', 'horizon':1}
	" let opts.index = 2
	let opts.savepos = 'f'
	let opts.callback = 'MyCallback'
	let opts.reduce = 1
	function! MyCallback(code)
		echom "callback: " . a:code
	endfunc
	if 1
		let menu = quickui#context#open(lines, opts)
		" echo menu
	else
		let item = quickui#utils#item_parse("你好吗f&aha\tAlt+x")	
		echo item
	endif
endif



