diff options
Diffstat (limited to 'vim/bundle/vim-addon-mw-utils/autoload/tovl/ui')
| -rw-r--r-- | vim/bundle/vim-addon-mw-utils/autoload/tovl/ui/filter_list.vim | 473 | 
1 files changed, 473 insertions, 0 deletions
diff --git a/vim/bundle/vim-addon-mw-utils/autoload/tovl/ui/filter_list.vim b/vim/bundle/vim-addon-mw-utils/autoload/tovl/ui/filter_list.vim new file mode 100644 index 0000000..74b2ab4 --- /dev/null +++ b/vim/bundle/vim-addon-mw-utils/autoload/tovl/ui/filter_list.vim @@ -0,0 +1,473 @@ +" OLD CODE ! +" I should contribute the multiple filter feature to tlib + +" filter list displays a list of items +" you can white / black filter them by regular expressions (similar to the +" tlib TToC command +" However you can edit the filters afterwards and select the cols which should +" be shown + +fun! tovl#ui#filter_list#ListTest() +  call tovl#ui#filter_list#ListView({ +	\ 'aligned' : 1, +	\ 'Continuation' : funcref#Function('echo string(ARGS[0])'), +	\ 'items' : [ {"aa" : "a\nAAAAAAAAAAA", 'bb' : "bbbbbbbbbbbbb\nB" },  +		   \  {"aa" : "2a\n2AAAAAAAAAAAA", "bb" : "2 bbbbbbbbbbbbb\n2B"}, +		   \  {"aa" : "XXX", "bb" : "YY"} ], +	\ }) + +endfun + +fun! s:Intersection(a, b) +  return filter(copy(a:a), 'index(a:b, v:val) >= 0') +endf + +fun! tovl#ui#filter_list#ListTestGotoLineCurrentBuf() +  let nr=1 +  let lines = [] +  for l in getline(0,line('$')) +    call add(lines, {'nr': nr, 'line' :l}) +    let nr = nr +1 +  endfor +  call tovl#ui#filter_list#ListView({ +	\ 'aligned' : 0, +	\ 'keys' : ['nr','line'], +	\ 'number' : 1, +	\ 'selectByIdOrFilter' : 1, +	\ 'Continuation' : funcref#Function('exec ARGS[0]["nr"]'), +	\ 'items' : lines, +	\ }) +endfun + +" opens a new filtered list +" keys of opts parameters: +" Continuation: This function will be called with the selected items +" items: { key : (string or dict) } +"        items willl be modified. use copy(youritems) as argument to prevent +"        this. An item is either a string or a dict  +"        (eg {'file' : .., 'line': ... , 'msg' : .. ) +" keys: list of keys to be shown (optional) +" filter: list of inital filters which must be applied +" contains [ { filter: .. , keep : .. }, ] see FilterItems() below +" aligned: default 0 +" sp_cmd: the command to be used to create the new buffer (default ':e') +" init : 0 / 1 (default 1): wether to show the view right now +" number: 0 /1 (default 1): number items ? +" selectByIdOrFilter: 1: start in getchar() loop so that the user can select +"                        the item even faster +"                     auto: only do this if all items fit on screen +"                     (recommend) +" cmds: extra cmds to be run +" cursorAt : at which item to put the cursor? +" +" If you don't like the default view you can override UpdateDisplay +" +" Usage examples of this list control: +" - db results +" - replacement of the quickfix window +" - select a buffer etc +fun! tovl#ui#filter_list#ListView(opts) +  " ActivateAddons theonevimlib +  let d = {} +  let d.items = a:opts.items +  let d.cursorAt = get(a:opts, 'cursorAt', 0) +  let d.aligned = get(a:opts, 'aligned', 0) +  let d.sep = '  ' +  let d.filter = get(a:opts, 'filter', []) +  " using sp because of bd! (FIXME) +  let d.sp_cmd = get(a:opts, 'sp_cmd', 'sp') +  let d.allKeys = {} +  let d.closeOnContinuation = get(a:opts,'closeOnContinuation',1) +  " don't recommend OnSingleMatch, use OnSingleMatchCR instead +  let d.continueOnSingleMatch = get(a:opts, 'continueOnSingleMatch',0) +  let d.continueOnSingleMatchCR = get(a:opts, 'continueOnSingleMatchCR',1) +  let d.selectByIdOrFilter = get(a:opts, 'selectByIdOrFilter', 0) +  let d.linesToItems = {} +  let d.number = get(a:opts, 'number', 1) +  let d.cmds = get(a:opts, 'cmds', []) +  let d.syn_cmds = get(a:opts, 'syn_cmds', []) + +  if has_key(a:opts,'keys') | let d.keys = a:opts.keys | endif +  if has_key(a:opts,'Continuation') | let d.Continuation = a:opts.Continuation | endif + +  " cache already filtered items in case we want to view really long results +  " contains [ { filter : { regex: .. , keep : .. } , items : .. , cursorAt :},  +  "            { filter : { ... } , items: .. , cursorAt : } +  let d.cached = [] +  " id of buffer +  let d.buffer = -1 +  let d.modeText = '' + +  fun d.HelpText() +    return [ "you've entered the the help of the powerful filtered view buffer", +	   \ "", +	   \ "type f to start filtering items by regex", +	   \ "type F to start dropping items by regex", +	   \ "k / K will ask you for the key to apply the filter to first", +	   \ "apply the filter by <cr> and press <cr> again to select item", +	   \ "", +	   \ "use :ShowAppliedFilters to list active filters", +	   \ "use :ToggleAlignment to toggle alignment", +	   \ "", +	   \ "TODO: Implement sorting, implement interface to change keys (displayed columns)" +	   \ ] +  endfun + +  " create new scratch buffer +  " preprocess items calculating line count and maxwidth for all items +  fun d.NewBufferAndInit() +    let self.bufferId = bufnr(bufname('%')) +    for idx in range(0,len(self.items)-1) +      if type(self.items[idx]) != 4 +	" no dict yet, make it one +	let self.items[idx] = {'string_line' : self.items[idx]} +      endif +      let new = {} +      for [k,v] in items(self.items[idx]) +	let lines = split(v,"\n") +	let self.items[idx][k] = { 'text' : v, 'rows' : len(lines), 'cols' : max(map(copy(lines),'len(v:val)')), 'lines' : lines } +	let self.allKeys[k] = 1 +        unlet k v +      endfor +    endfor +    call tovl#scratch_buffer#ScratchBuffer({ +	  \ 'help' : funcref#Function(self.HelpText,{ 'self' : self }), +	  \ 'sp_cmd' : self.sp_cmd, +	  \ 'cmds' : self.cmds +	  \ }) +    " I assume we have some kind of formatting anyway. Thus breaking lines is bad! +    set nowrap +    setlocal cursorline +    let b:filtered_view = self +    command! -buffer -nargs=0 ToggleAlignment call b:filtered_view.ToggleAlignment() +    command! -buffer -nargs=0 ShowAppliedFilters call b:filtered_view.ShowAppliedFilters() +    command! -buffer -nargs=0 RemoveFilters call b:filtered_view.RemoveFilters() +    noremap <buffer> f :call b:filtered_view.FilterFromKeyboard(1,'')<cr> +    " noremap <buffer> f :call b:filtered_view.FilterFromKeyboard(1)<cr> +    noremap <buffer> F :call b:filtered_view.FilterFromKeyboard(0,'')<cr> +    if has_key(self,'Continuation') +      nnoremap <buffer> <cr> :call b:filtered_view.Continue()<cr> +    endif +    "noremap <buffer> k +    "noremap <buffer> K + +    let [items, cursorAt] = self.FilteredItems() +    " len(items) is an approximation because one item can have multiple +    " lines.. However adding the lines first to check takes too much time +    if self.selectByIdOrFilter == 1 || (self.selectByIdOrFilter == 'auto' && winheight('%') > len(items) ) +      call self.SelectByIdOrFilter() +    else +      " user should choose how to proceed +      call self.UpdateDisplay() +    endif +  endfun + +  " user interface +  fun d.ToggleAlignment() +    let self.aligned = !self.aligned +    call self.UpdateDisplay() +  endfun +  fun d.ShowAppliedFilters() +    for i in self.filter | echo string(i) | endfor +  endfun +  fun d.RemoveFilters() +    let self.filter = [] +    call self.UpdateDisplay() +  endfun +  fun d.Continue() +    let item = self.CurrentItem() +    call self.DoContinue(item) +  endfun +  fun d.DoContinue(v) +    if self.closeOnContinuation | bw! | endif +    call funcref#Call(self.Continuation,[a:v]) +  endfun + +  fun d.MapToOriginal(v) +    if has_key(a:v, 'string_line') +      return a:v.string_line.text +    else +      let d = {} +      for [k,v] in items(a:v) +	let d[k] = v.text +	unlet k v +      endfor +      return d +    endif +  endfun + +  fun d.CurrentItem() +    let idx=line('.')-len(self.headerLines) +    while idx >= 0 +      if has_key(self.linesToItems, idx) +	return self.MapToOriginal(self.FilteredItems()[0][self.linesToItems[idx]]) +      else +	let idx = idx -1 +      endif +    endwhile +    throw "internal error, couldn't determine selected item!" +  endfun + +  " updates the filter cache and returns the final filtered items +  fun d.FilteredItems() +    " update cache +    let idx = 0 +    let [items, cursorAt] = [self.items, self.cursorAt] +    for idx in range(0, len(self.filter)-1) +      if idx +1 > len(self.cached) || self.cached[idx]['filter'] != self.filter[idx] +	let self.cached = self.cached[:idx-1] +	let [items, cursorAt] = self.FilterItem(copy(items), self.filter[idx], cursorAt) +	call add(self.cached, { 'cursorAt' : cursorAt, 'items' : items, 'filter' : self.filter[idx]}) +      else +        let ci = self.cached[idx] +	let [items, cursorAt] = [ci['items'], ci['cursorAt']] +      endif +    endfor +    return [items, cursorAt] +  endfun + +  " calling this will return a set of lines which are expected to be the new +  " buffer contents. The self.linesToItems dict is updated +  fun d.UpdateDisplay() + +    if empty(self.filter) +      let self.statusline= 'no filter applied, :Help for help' +    else +      let self.statusline = len(self.filter).' '.string(self.filter[-1]) +    endif + +    let self.linesToItems = {} +    let [items, cursorAt] = self.FilteredItems() +    "let num_width = printf('%.0f', trunc(log10(len(items))+1)) +    let num_width = 4 +    if self.aligned +      " get column width.. (probably will not work with unicde characters.. I +      " don't have a better solution) +      let maxlens={} +      for i in items +	for [k,v] in items(i) +	  if get(maxlens,k,0) < v.cols +	    let maxlens[k] = v.cols +	  endif +	endfor +      endfor +    endif + +    " format lines +    let self.headerLines = [self.modeText] +    let lines = copy(self.headerLines) +    let lines_count = 0 +    if self.number +      let fmt_startA = '%'.num_width.'s)' +      let fmt_startB = '%'.num_width.'s' +    else +      let fmt_startA = '' | let fmt_startB = '' +    endif +    let cursorAtLine = 1 " sane default +    for idx in range(0,len(items)-1) +      let self.linesToItems[lines_count + 1] = idx +      let i = items[idx] +      let keys = has_key(self,'keys') +	    \ ? s:Intersection(self.keys, keys(i)) +	    \ : keys(i) +      let fmt = '' +      let args =  [i] +      let cols = [] +      for k in keys +	let fmt .= self.sep.'%-'.(self.aligned ? maxlens[k] : i[k]['cols']).'s' +	call add(cols, i[k]) +      endfor +      for row in range(0, max([1] + map(copy(cols),'v:val["rows"]'))-1) +	let fmt_args = row == 0 ? [fmt_startA.fmt] :  [fmt_startB.fmt] +	if self.number +	  call add(fmt_args, row == 0 ? idx : '') +	endif +	for c in cols +	  call add(fmt_args, c.rows <= row ? '' : c.lines[row]) +	endfor +	call add(lines, call('printf', fmt_args)) +	let lines_count += 1 +      endfor +      if idx == cursorAt +        let cursorAtLine = lines_count +      endif +    endfor +    " update stauts line to show last applied filter +    " disabled cause it causes trouble on :wincmd w +    " setlocal statusline=%!b:filtered_view.statusline + +    " syntax +    syn clear +    for s in self.syn_cmds | exec s | endfor +    let id = 0 +    " highlight filter regex in buffer as well +    let syn_ids = [ 'Underlined', 'Todo', 'Error', 'Type', 'Statement' ] +    for f in self.filter +      if !f.keep || !has_key(f, 'regex') | continue | endif +      if f.regex != '' +        try +	exec 'syn match '.syn_ids[id % len(syn_ids)].' '.string(f.regex) +        catch /.*/ +          " ignore errors such as \ without following characters. Thus just +          " ignore and wait for the next character +        endtry +      endif +      let id = id +1 +    endfor +    if len(lines) > winheight('%') +      call extend(lines, self.headerLines) +    endif +    normal ggdG +    call append(0, lines) +    " place cursor +    exec (cursorAtLine+1) +    " move cursor into the middle of the window +    normal zz +  endf + +  " filter = keys : +  "  filter = string to be executed containing Val +  "  keep = 1  keep on match  +  "       = 0  drop on match +  "  key (optional) +  "  cursorAt: at which item to put the cursor +  "            if that item is deleted it will be placed at the item above +  " optional: key of dict if dict +  fun d.FilterItem(items, filter, cursorAt) +    let filter = 'Val =~ '.string(a:filter.regex) +    let keep = a:filter.keep +    let cursorAt = a:cursorAt + +    for idx in reverse(range(0, len(a:items)-1)) +      let i = a:items[idx] +      if has_key(a:filter,'key') +	let key = a:filter.key +	if has_key(i, key) +	  " key given, only filter by this column +	  let Val = i[key]['text'] +	  exec 'let any = '.filter +	else +	  let any = 0 +	endif +      else +	let any = 0 +	" no key given, try all +	for x in values(i) +	  let Val = x['text'] +	  exec 'let any =  '.filter +	  if any | break | endif +	endfor +      endif +      if any != keep +	call remove(a:items, idx) +        if idx <= cursorAt +          let cursorAt = cursorAt -1 +        endif +      endif +    endfor +    return [a:items, cursorAt] +  endfun + +  " if the user enters a number select by index else start filtering.. +  fun d.SelectByIdOrFilter() +    let idx='' +    let items = self.FilteredItems()[0] +    try +      let self.modeText = '[0-9]* : select by index| <esc>: escape getchar() loop, any char: start filtering' +      call self.UpdateDisplay() | redraw +      while 1 +	let c=getchar() +	if index([13,10],c) >= 0 +	  return self.DoContinue(self.MapToOriginal(items[idx])) +	elseif index([27], c) >=0 +	  " esc, abort +	  return +	else +	  if type(c) == 0 +	    let c = nr2char(c) +	  endif +	  if c == "\<bs>" || index(map(range(0,10),'v:val.""'),c) >= 0 +	    if c == "\<bs>" +	      let idx = idx[:-2] +	    else +	      let idx .= c +	    endif +	    if idx < len(items) && idx.'0' > len(items) || idx == 0 && len(items) < 10 +	      " only match +	      return self.DoContinue(self.MapToOriginal(items[idx])) +	    endif +	  else +	    return self.FilterFromKeyboard(1,c) +	  endif +	endif +      endwhile +    finally +      let self.modeText = '' +    endtry +  endfun + +  " gets a regular expresion filter by keybaord and updates the display while +  " you're typing. The regex ist shown in the statusline +  fun d.FilterFromKeyboard(keep, start, ...) +    let self.modeText = 'press ESC to exit getchar() loop' +    call self.UpdateDisplay() | redraw + +    try +      let key_text = a:0 > 0 ? 'key : '.a:1 : '' +      let filter_bak = self.filter +      let filter = copy(self.filter) +      let start = a:start +      let filter_new = '' +      while 1 +	if start != '' +	  " use c= last char to force updating display etc +	  let filter_new = start[:-2] +	  let c = start[-1:] +	  let start = '' +	else +	  let c=getchar() +	endif  +	if index([13,10],c) >= 0 +	  " c-j or return, accept new filter +	  let items = self.FilteredItems() +	  if len(items) == 1 && has_key(self, 'Continuation') && self.continueOnSingleMatchCR +	    call self.DoContinue(self.MapToOriginal(items[0])) +	  endif +	  return +	elseif index([27], c) >=0 +	  " esc, abort +	  let self.filter = filter_bak +	  call self.UpdateDisplay() +	  return +	else +	  if type(c) == 0 +	    let c = nr2char(c) +	  endif +	  if c == "\<bs>" +	    let filter_new = filter_new[:-2] +	  else +	    let filter_new .= c +	  endif +	  let d = {'keep' : a:keep, 'regex' : filter_new } +	  if a:0 > 0 +	    let d['key'] = a:1 +	  endif +	  let self.filter = copy(filter_bak) +	  call add(self.filter, d) +	  let items = self.FilteredItems() +	  if len(items) == 1 && has_key(self, 'Continuation') && self.continueOnSingleMatch +	    call self.DoContinue(self.MapToOriginal(items[0])) +	    return +	  endif +	  call self.UpdateDisplay() | redraw +	endif +      endwhile +    finally +      let self.modeText = '' +    endtry +  endfun + +  if get(a:opts,'init',1) +    call d.NewBufferAndInit() +  endif +endfun  | 
