"======================================================================
"
" utils.vim - 
"
" Created by skywind on 2019/12/19
" Last Modified: 2023/07/23 18:44
"
"======================================================================

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


"----------------------------------------------------------------------
" parse description into item object
"----------------------------------------------------------------------
function! quickui#utils#item_parse(description)
	let obj = {'text':'', 'key_pos':-1, 'key_char':'', 'is_sep':0, 'help':''}
	let text = ''
	let child = 0
	if type(a:description) == v:t_string
		let text = a:description
		let obj.help = ''
		let obj.cmd = ''
	elseif type(a:description) == v:t_list
		let size = len(a:description)
		let text = (size >= 1)? a:description[0] : ''
		let obj.help = (size >= 3)? a:description[2] : ''
		let obj.cmd = ''
		if size >= 2
			if type(a:description[1]) == v:t_string
				let obj.cmd = a:description[1]
			elseif type(a:description[1]) == v:t_list
				let obj.cmd = ''
				let obj.child = a:description[1]
				let child = 1
			endif
		endif
	endif
	if text =~ '^-\+$'
		let obj.is_sep = 1
		let obj.text = ""
		let obj.desc = ""
		let obj.text_width = 0
		let obj.desc_width = 0
		let obj.enable = 0
	else
		let text = quickui#core#expand_text(text)
		let obj.enable = 1
		if strpart(text, 0, 1) == '~'
			let text = strpart(text, 1)
			let obj.enable = 0
		endif
		let pos = stridx(text, "\t")
		let sep = ">"
		if pos < 0 
			let obj.text = text
			let obj.desc = ""
		else
			let obj.text = strpart(text, 0, pos)
			let obj.desc = strpart(text, pos + 1)
			let obj.desc = substitute(obj.desc, "\t", " ", "g")
		endif
		let obj.desc = (child == 0)? obj.desc : sep
		let text = obj.text
		let rest = ''
		let start = 0
		while 1
			let pos = stridx(text, "&", start)
			if pos < 0 
				let rest .= strpart(text, start)
				break
			end
			let rest .= strpart(text, start, pos - start)
			let key = strpart(text, pos + 1, 1)
			let start = pos + 2
			if key == '&'
				let rest .= '&'
			elseif key == '~'
				let rest .= '~'
			else
				let obj.key_char = key
				let obj.key_pos = strwidth(rest)
				let rest .= key
			endif
		endwhile
		let obj.text = rest
		let obj.text_width = strwidth(obj.text)
		let obj.desc_width = strwidth(obj.desc)
	end
	return obj
endfunc


"----------------------------------------------------------------------
" alignment
"----------------------------------------------------------------------
function! quickui#utils#context_align(item, left_size, right_size)
	let obj = a:item
	let middle = (a:right_size > 0)? 2 : 0
	let size = a:left_size + a:right_size + middle
	if obj.is_sep
		let obj.content = repeat('-', size)
	else
		if obj.text_width < a:left_size
			let delta = a:left_size - obj.text_width
			let obj.text_left = obj.text . repeat(' ', delta)
		else
			let obj.text_left = obj.text
		endif
		if obj.desc_width < a:right_size
			let delta = a:right_size - obj.desc_width
			let obj.text_right = repeat(' ', delta) . obj.desc
		else
			let obj.text_right = obj.desc
		endif
		if a:right_size > 0
			let obj.content = obj.text_left . '  ' . obj.text_right
		else
			let obj.content = obj.text_left
		endif
	endif
	return obj
endfunc


"----------------------------------------------------------------------
" style: default
"----------------------------------------------------------------------
function! quickui#utils#highlight(style)
	let style = (type(a:style) == v:t_number)? (''. a:style) : a:style
	let style = tolower(style)
	if style == '' || style == '0' || style == 'default' || style == 'Pmenu'
		hi! link TVisionBG Pmenu
		hi! link TVisionKey Keyword
		hi! link TVisionOff Comment
		hi! link TVisionSel PmenuSel
		hi! link TVisionHelp Title
	elseif style == 'borland'
	endif
endfunc



"----------------------------------------------------------------------
" build map
"----------------------------------------------------------------------
let s:maps = {}
let s:maps["\<ESC>"] = 'ESC'
let s:maps["\<CR>"] = 'ENTER'
let s:maps["\<SPACE>"] = 'ENTER'
let s:maps["\<UP>"] = 'UP'
let s:maps["\<DOWN>"] = 'DOWN'
let s:maps["\<LEFT>"] = 'LEFT'
let s:maps["\<RIGHT>"] = 'RIGHT'
let s:maps["\<HOME>"] = 'HOME'
let s:maps["\<END>"] = 'END'
let s:maps["\<c-j>"] = 'DOWN'
let s:maps["\<c-k>"] = 'UP'
let s:maps["\<c-h>"] = 'LEFT'
let s:maps["\<c-l>"] = 'RIGHT'
let s:maps["\<c-n>"] = 'NEXT'
let s:maps["\<c-p>"] = 'PREV'
let s:maps["\<c-b>"] = 'PAGEUP'
let s:maps["\<c-f>"] = 'PAGEDOWN'
let s:maps["\<c-u>"] = 'HALFUP'
let s:maps["\<c-d>"] = 'HALFDOWN'
let s:maps["\<PageUp>"] = 'PAGEUP'
let s:maps["\<PageDown>"] = 'PAGEDOWN'
let s:maps["\<c-g>"] = 'NOHL'
let s:maps['j'] = 'DOWN'
let s:maps['k'] = 'UP'
let s:maps['h'] = 'LEFT'
let s:maps['l'] = 'RIGHT'
let s:maps['J'] = 'HALFDOWN'
let s:maps['K'] = 'HALFUP'
let s:maps['H'] = 'PAGEUP'
let s:maps['L'] = 'PAGEDOWN'
let s:maps["g"] = 'TOP'
let s:maps["G"] = 'BOTTOM'
let s:maps['q'] = 'ESC'
let s:maps['n'] = 'NEXT'
let s:maps['N'] = 'PREV'


function! quickui#utils#keymap()
	return deepcopy(s:maps)
endfunc


"----------------------------------------------------------------------
" python simulate system() on window to prevent temporary window
"----------------------------------------------------------------------
function! s:python_system(cmd, version)
	if has('nvim')
		let hr = system(a:cmd)
	elseif has('win32') || has('win64') || has('win95') || has('win16')
		if a:version < 0 || (has('python3') == 0 && has('python2') == 0)
			let hr = system(a:cmd)
			let s:shell_error = v:shell_error
			return hr
		elseif a:version == 3
			let pyx = 'py3 '
			let python_eval = 'py3eval'
		elseif a:version == 2
			let pyx = 'py2 '
			let python_eval = 'pyeval'
		else
			let pyx = 'pyx '
			let python_eval = 'pyxeval'
		endif
		exec pyx . 'import subprocess, vim'
		exec pyx . '__argv = {"args":vim.eval("a:cmd"), "shell":True}'
		exec pyx . '__argv["stdout"] = subprocess.PIPE'
		exec pyx . '__argv["stderr"] = subprocess.STDOUT'
		exec pyx . '__pp = subprocess.Popen(**__argv)'
		exec pyx . '__return_text = __pp.stdout.read()'
		exec pyx . '__pp.stdout.close()'
		exec pyx . '__return_code = __pp.wait()'
		exec 'let l:hr = '. python_eval .'("__return_text")'
		exec 'let l:pc = '. python_eval .'("__return_code")'
		let s:shell_error = l:pc
		return l:hr
	else
		let hr = system(a:cmd)
	endif
	let s:shell_error = v:shell_error
	return hr
endfunc


"----------------------------------------------------------------------
" execute external program and return its output
"----------------------------------------------------------------------
function! quickui#utils#system(cmd)
	let hr = s:python_system(a:cmd, get(g:, 'quickui_python', 0))
	let g:quickui#utils#shell_error = s:shell_error
	return hr
endfunc


"----------------------------------------------------------------------
" display a error msg
"----------------------------------------------------------------------
function! quickui#utils#errmsg(what)
	redraw
	echohl ErrorMsg
	echom a:what
	echohl None
endfunc


"----------------------------------------------------------------------
" safe print
"----------------------------------------------------------------------
function! quickui#utils#print(content, highlight, ...)
	let saveshow = &showmode
	set noshowmode
    let wincols = &columns
    let statusline = (&laststatus==1 && winnr('$')>1) || (&laststatus==2)
    let reqspaces_lastline = (statusline || !&ruler) ? 12 : 29
    let width = len(a:content)
    let limit = wincols - reqspaces_lastline
	let l:content = a:content
	if width + 1 > limit
		let l:content = strpart(l:content, 0, limit - 1)
		let width = len(l:content)
	endif
	" prevent scrolling caused by multiple echo
	let needredraw = (a:0 >= 1)? a:1 : 1
	if needredraw != 0
		redraw 
	endif
	if a:highlight != 0
		echohl Type
		echo l:content
		echohl NONE
	else
		echo l:content
	endif
	if saveshow != 0
		set showmode
	endif
endfunc


"----------------------------------------------------------------------
" max height
"----------------------------------------------------------------------
function! quickui#utils#max_height(percentage)
	return (&lines) * a:percentage / 100
endfunc


"----------------------------------------------------------------------
" cursor movement
"----------------------------------------------------------------------
function! quickui#utils#movement(offset)
	let height = winheight(0)
	let winline = winline()
	let curline = line('.')
	let topline = curline - winline + 1
	let topline = (topline < 1)? 1 : topline
	let botline = topline + height - 1
	let offset = 0
	if type(a:offset) == v:t_number
		let offset = a:offset
	elseif type(a:offset) == v:t_string
		if a:offset == 'PAGEUP'
			let offset = -height
		elseif a:offset == 'PAGEDOWN'
			let offset = height
		elseif a:offset == 'HALFUP' || a:offset == 'LEFT'
			let offset = -(height / 2)
		elseif a:offset == 'HALFDOWN' || a:offset == 'RIGHT'
			let offset = height / 2
		elseif a:offset == 'UP'
			let offset = -1
		elseif a:offset == 'DOWN'
			let offset = 1
		elseif a:offset == 'TOP'
			exec "noautocmd normal gg"
			return
		elseif a:offset == 'BOTTOM'
			exec "noautocmd normal G"
			return
		endif
	endif
	" echom "offset: ". offset
	if offset > 0
		exec "noautocmd normal ". offset . "\<C-E>"
	elseif offset < 0
		exec "noautocmd normal ". (-offset) . "\<C-Y>"
	endif
endfunc


"----------------------------------------------------------------------
" cursor scroll
"----------------------------------------------------------------------
function! quickui#utils#scroll(winid, offset)
	if type(a:offset) == v:t_number
		let cmd = 'call quickui#utils#movement(' . a:offset . ')'
		call quickui#core#win_execute(a:winid, cmd)
	else
		let cmd = 'call quickui#utils#movement("' . a:offset . '")'
		call quickui#core#win_execute(a:winid, cmd)
	endif
endfunc



"----------------------------------------------------------------------
" centerize
"----------------------------------------------------------------------
function! quickui#utils#center(winid, ...)
	if g:quickui#core#has_nvim == 0
		let pos = popup_getpos(a:winid)
	else
		let pos = {}
		let pos.width = nvim_win_get_width(a:winid)
		let pos.height = nvim_win_get_height(a:winid)
	endif
	let h = pos.height
	let w = pos.width
	let mode = (a:0 < 1)? 0 : (a:1)
	let scale = (mode == 0)? 80 : 68
	let limit1 = (&lines - 2) * scale / 100
	let limit2 = (&lines - 2)
	let opts = {}
	if h + 4 < limit1
		let opts.line = (limit1 - h) / 2
	else
		let opts.line = (limit2 - h) / 2
	endif
	let opts.col = (&columns - w) / 2
	let opts.col = (opts.col < 1)? 1 : (opts.col)
	let hr = quickui#core#screen_fit(opts.line, opts.col, w, h)
	let opts.col = hr[1]
	let opts.line = hr[0]
	if g:quickui#core#has_nvim == 0
		call popup_move(a:winid, opts)
	else
		let no = {'col': opts.col - 1, 'row': opts.line - 1}
		let no.relative = 'editor'
		call nvim_win_set_config(a:winid, no)
	endif
endfunc


"----------------------------------------------------------------------
" show cursorline in textbox
"----------------------------------------------------------------------
function! quickui#utils#show_cursor(winid, row)
	let height = winheight(0)
	let winline = winline()
	let curline = line('.')
	let topline = curline - winline + 1
	let topline = (topline < 1)? 1 : topline
	let botline = topline + height - 1
	let w:__quickui_line__ = get(w:, '__quickui_line__', -1)
	if a:row >= topline && a:row <= botline
		exec ":" . a:row
		if w:__quickui_line__ != 1
			if g:quickui#core#has_nvim == 0
				call popup_setoptions(a:winid, {'cursorline': 1})
			else
				call quickui#core#win_execute(a:winid, 'setl cursorline')
				if exists('+cursorlineopt')
					call quickui#core#win_execute(a:winid, 'setl cursorlineopt=both')
				endif
			endif
		endif
		let w:__quickui_line__ = 1
	else
		if w:__quickui_line__ != 0
			if g:quickui#core#has_nvim == 0
				call popup_setoptions(a:winid, {'cursorline': 0})
			else
				call quickui#core#win_execute(a:winid, 'setl nocursorline')
			endif
		endif
		let w:__quickui_line__ = 0
	endif
endfunc


"----------------------------------------------------------------------
" update cursor line
"----------------------------------------------------------------------
function! quickui#utils#update_cursor(winid)
	let bid = winbufnr(a:winid)
	let row = getbufvar(bid, '__quickui_cursor__', -1)
	let cmd = 'call quickui#utils#show_cursor('. a:winid .', '.row.')'
	call quickui#core#win_execute(a:winid, cmd)
endfunc


"----------------------------------------------------------------------
" get window line
"----------------------------------------------------------------------
function! quickui#utils#get_cursor(winid)
	let g:quickui#utils#__cursor_index__ = -1
	let cmd = 'let g:quickui#utils#__cursor_index__ = line(".")'
	noautocmd call quickui#core#win_execute(a:winid, cmd)
	return g:quickui#utils#__cursor_index__
endfunc


"----------------------------------------------------------------------
" get topline in current window
"----------------------------------------------------------------------
function! quickui#utils#current_topline()
	let height = winheight(0)
	let winline = winline()
	let curline = line('.')
	let topline = curline - winline + 1
	return topline
endfunc


"----------------------------------------------------------------------
" get first cursorline
"----------------------------------------------------------------------
function! quickui#utils#get_topline(winid)
	let g:quickui#utils#__cursor_topline__ = -1
	let cmd = 'let g:quickui#utils#__cursor_topline__ = '
	let cmd = cmd . 'quickui#utils#current_topline()'
	call quickui#core#win_execute(a:winid, cmd)
	return g:quickui#utils#__cursor_topline__
endfunc


"----------------------------------------------------------------------
" make border
"----------------------------------------------------------------------
function! quickui#utils#make_border(width, height, border, title, ...)
	let pattern = quickui#core#border_get(a:border)
	let image = []
	let w = a:width
	let h = a:height
	let text = pattern[0] . repeat(pattern[1], w) . pattern[2]
	let image += [text]
	let index = 0
	while index < h
		let text = pattern[3] . repeat(' ', w) . pattern[5]
		let image += [text]
		let index += 1
	endwhile
	let text = pattern[6] . repeat(pattern[7], w) . pattern[8]
	let image += [text]
	let button = (a:0 > 0)? (a:1) : 0
	let align = (a:0 > 1)? (a:2) : ''
	let text = image[0]
	let title = quickui#core#string_fit(a:title, w)
	if align == '' || align == 'l'
		let text = quickui#core#string_compose(text, 1, title)
	elseif align == 'm'
		let left = (w + 2 - len(title)) / 2
		let text = quickui#core#string_compose(text, left, title)
	elseif align == 'r'
		let left = w + 2 - len(title) - 1
		let text = quickui#core#string_compose(text, left, title)
	endif
	if button != 0
		let text = quickui#core#string_compose(text, w + 1, 'X')
	endif
	let image[0] = text
	return image
endfunc


"----------------------------------------------------------------------
" search or jump
"----------------------------------------------------------------------
function! quickui#utils#search_or_jump(winid, cmd)
	if a:cmd == '/' || a:cmd == '?'
		let prompt = (a:cmd == '/')? '/' : '?'
		" let prompt = (a:cmd == '/')? '(search): ' : '(search backwards): '
		let t = quickui#core#input(prompt, '')
		if t != '' && t != "\<c-c>"
			try
				silent call quickui#core#win_execute(a:winid, a:cmd . t)
			catch /^Vim\%((\a\+)\)\=:E486:/
				call quickui#utils#errmsg('E486: Pattern not find: '. t)
			endtry
			silent! call quickui#core#win_execute(a:winid, 'nohl')
			call setwinvar(a:winid, '__quickui_search_cmd', a:cmd)
			call setwinvar(a:winid, '__quickui_search_key', t)
		endif
	elseif a:cmd == ':'
		let prompt = ':'
		" let prompt = '(goto): '
		let t = quickui#core#input(prompt, '')
		if t != ''
			call quickui#core#win_execute(a:winid, ':' . t)	
		endif
	endif
endfunc


"----------------------------------------------------------------------
" search next
"----------------------------------------------------------------------
function! quickui#utils#search_next(winid, cmd)
	let prev_cmd = getwinvar(a:winid, '__quickui_search_cmd', '')
	let prev_key = getwinvar(a:winid, '__quickui_search_key', '')
	if prev_key != ''
		if a:cmd ==# 'n' || a:cmd == 'NEXT'
			let cmd = (prev_cmd == '/')? '/' : '?'
		else
			let cmd = (prev_cmd == '/')? '?' : '/'
		endif
		try
			silent call quickui#core#win_execute(a:winid, cmd . prev_key)
		catch /^Vim\%((\a\+)\)\=:E486:/
		endtry
		noautocmd call quickui#core#win_execute(a:winid, 'nohl')
	endif
	let cmds = ['exec line(".")']
	let cmds += ['normal! 0']
	silent call quickui#core#win_execute(a:winid, cmds)
endfunc


"----------------------------------------------------------------------
" size can be in '24' or '24%' or '0.25'
"----------------------------------------------------------------------
function! quickui#utils#read_size(text, maxsize)
	if type(a:text) == v:t_number
		return a:text
	elseif type(a:text) == v:t_string
		let text = trim(a:text)
		if text =~ '%$'
			let text = strpart(text, 0, len(text) - 1)
			let ratio = str2nr(text)
			let num = (a:maxsize) * ratio / 100
			return (num < a:maxsize)? num : a:maxsize
		else
			let fsize = str2float(a:text)
			if fsize <= 1.0
				return float2nr(fsize * a:maxsize)
			endif
			let size = float2nr(fsize)
			return (size > a:maxsize)? a:maxsize : size
		endif
	elseif type(a:text) == v:t_float
		let fsize = a:text
		if fsize <= 1.0
			return float2nr(fsize * a:maxsize)
		endif
		let size = float2nr(fsize)
		return (size > a:maxsize)? a:maxsize : size
	endif
endfunc



"----------------------------------------------------------------------
" get default tools width
"----------------------------------------------------------------------
function! quickui#utils#tools_width()
	let width = get(g:, 'quickui_tools_width', '60%')
	let size = quickui#utils#read_size(width, &columns)
	let minimal = (60 < &columns)? 60 : &columns
	let size = (size < minimal)? minimal : size
	return (size > &columns)? &columns : size
endfunc


"----------------------------------------------------------------------
" read from register or evaluation
"----------------------------------------------------------------------
function! quickui#utils#read_eval(...)
	let opts = (a:0 == 0)? {} : (a:1)
	try
		let code = getchar()
	catch /^Vim:Interrupt$/
		let code = "\<C-C>"
	endtry
	let ch = (type(code) == v:t_number)? nr2char(code) : code
	if ch == "\<c-c>"
		return ''
	elseif ch == "\<esc>" || ch == "\<cr>"
		return ''
	elseif ch == "="
		let e = input('=')
		if e == ''
			return ''
		endif
		return eval(e)
	elseif ch == "\<c-w>"
		let x = expand('<cword>')
		return x
	elseif len(ch) == 1
		let x = eval('@' . ch)
		return x
	endif
	return ''
endfunc


"----------------------------------------------------------------------
" normalize textlist
"----------------------------------------------------------------------
function! quickui#utils#text_list_normalize(textlist)
	if type(a:textlist) == v:t_list
		let textlist = a:textlist
	else
		let textlist = split('' . a:textlist, '\n', 1)
	endif
	let out = []
	for text in textlist
		let text = substitute(text, '[\r\n\t]', ' ', 'g')
		let out += [text]
	endfor
	return out
endfunc



"----------------------------------------------------------------------
" getchar
"----------------------------------------------------------------------
function! quickui#utils#getchar(wait)
	try
		if a:wait != 0
			let code = getchar()
		else
			let code = getchar(0)
		endif
	catch /^Vim:Interrupt$/
		let code = "\<C-C>"
	endtry
	if type(code) == v:t_number && code == 0
		try
			exec 'sleep 15m'
			continue
		catch /^Vim:Interrupt$/
			let code = "\<c-c>"
		endtry
	endif
	let ch = (type(code) == v:t_number)? nr2char(code) : code
	return ch
endfunc



"----------------------------------------------------------------------
" switch buffer
"----------------------------------------------------------------------
function! quickui#utils#switch(filename, opts)
	let switch = get(g:, 'quickui_switch_mode', &switchbuf)
	let switch = get(a:opts, 'switch', switch)
	let method = split(switch, ',')
	let goto = get(a:opts, 'goto', -1)
	let ft = get(a:opts, 'ft', '')
	let cmds = get(a:opts, 'command', [])
	if type(a:filename) == type('')
		let filename = expand(a:filename)
		if filereadable(filename) == 0
			if get(a:opts, 'exist', 0) != 0
				echohl ErrorMsg
				echom "E484: Can't open file " . (a:filename)
				echohl None
				return 0
			endif
		endif
		let bid = bufnr(filename)
	else
		let bid = a:filename
		if bid < 0
			return 0
		endif
	endif
	if index(method, 'useopen') >= 0
		for wid in range(winnr('$'))
			let b = winbufnr(wid + 1)
			if b == bid
				silent exec ''. (wid + 1) . 'wincmd w'
				if goto > 0
					silent exec ':' . goto
				endif
				for cmd in cmds
					exec cmd
				endfor
				return 1
			endif
		endfor
	endif
	if index(method, 'usetab') >= 0
		for tid in range(tabpagenr('$'))
			let buflist = tabpagebuflist(tid + 1)
			for wid in range(len(buflist))
				if bid == buflist[wid]
					silent exec 'tabn ' . (tid + 1)
					silent exec '' . (wid + 1) . 'wincmd w'
					if goto > 0
						silent exec ':' . goto
					endif
					for cmd in cmds
						exec cmd
					endfor
					return 1
				endif
			endfor
		endfor
	endif
	if index(method, 'newtab') >= 0
		silent exec 'tab split'
	elseif index(method, 'uselast') >= 0
		silent exec 'wincmd p'
	elseif index(method, 'edit') >= 0
		silent exec ''
	elseif index(method, 'drop') >= 0
	else
		if &buftype != ''
			silent exec 'wincmd p'
		endif
		for i in range(winnr('$'))
			if &buftype == ''
				break
			endif
			silent exec 'wincmd w'
		endfor
		let mods = get(a:opts, 'mods', '')
		if index(method, 'auto') >= 0
			if winwidth(0) >= 160
				exec mods . ' vsplit'
			else
				exec mods . ' split'
			endif
		elseif index(method, 'split') >= 0
			exec mods . ' split'
		elseif index(method, 'vsplit') >= 0
			exec mods . ' vsplit'
		endif
	endif
	try
		let force = ((get(a:opts, 'force', 0) != 0)? '!' : '')
		if bid >= 0
			exec 'b' . force . ' ' . bid
		else
			exec 'edit' . force . ' ' . fnameescape(expand(a:filename))
		endif
	catch /^Vim\%((\a\+)\)\=:E37:/ 
		echohl ErrorMsg
		echo 'E37: No write since last change (set force=1 to override)'
		echohl None
		return 0
	endtry
	if goto > 0
		exec ':' . goto
	endif
	if ft != ''
		exec 'setlocal ft=' . fnameescape(ft)
	endif
	for cmd in cmds
		exec cmd
	endfor
	return 1
endfunc



"----------------------------------------------------------------------
" returns 1 if filetype matches pattern, otherwise returns 0
"----------------------------------------------------------------------
function! quickui#utils#match_ft(filetype, pattern)
	let pattern = quickui#core#string_strip(a:pattern)
	let ft = a:filetype
	if pattern == ''
		return 0
	elseif pattern =~ '^/'
		let pattern = strpart(pattern, 1)
		if match(ft, pattern) >= 0
			return 1
		endif
	elseif pattern =~ '^!'
		let pattern = strpart(pattern, 1)
		if match(ft, pattern) < 0
			return 1
		endif
	endif
	let blacklist = []
	let whitelist = []
	for check in split(pattern, ',')
		if pattern[0] == '-'
			let blacklist += [check]
		else
			let whitelist += [check]
		endif
	endfor
	if index(blacklist, '-' . ft) >= 0
		return 0
	endif
	if empty(whitelist) || index(whitelist, ft) >= 0
		return 1
	endif
	return 0
endfunc


"----------------------------------------------------------------------
" format table
"----------------------------------------------------------------------
function! quickui#utils#tabulify(rows)
	let content = []
	let rows = []
	let nrows = len(a:rows)
	let ncols = 0
	for row in a:rows
		if len(row) > ncols
			let ncols = len(row)
		endif
	endfor
	if nrows == 0 || ncols == 0
		return content
	endif
	let sizes = repeat([0], ncols)
	let index = range(ncols)
	for row in a:rows
		let newrow = deepcopy(row)
		if len(newrow) < ncols
			let newrow += repeat([''], ncols - len(newrow))
		endif
		for i in index
			let size = strwidth(newrow[i])
			let sizes[i] = (sizes[i] < size)? size : sizes[i]
		endfor
		let rows += [newrow]
	endfor
	for row in rows
		let ni = []
		for i in index
			let x = row[i]
			let size = strwidth(x)
			if size < sizes[i]
				let x = x . repeat(' ', sizes[i] - size)
			endif
			let ni += [x]
		endfor
		let content += [ni]
	endfor
	return content
endfunc


"----------------------------------------------------------------------
" print table
"----------------------------------------------------------------------
function! quickui#utils#print_table(rows, highmap)
	let content = quickui#utils#tabulify(a:rows)
	let index = 0
	for line in content
		let col = 0
		echon (index == 0)? " " : "\n "
		for cell in line
			let key = index . ',' . col
			if !has_key(a:highmap, key)
				echohl None
			else
				exec 'echohl ' . a:highmap[key]
			endif
			echon cell . '  '
			let col += 1
		endfor
		let index += 1
	endfor
	echohl None
endfunc




