" @Author:      Tom Link (micathom AT gmail com?subject=[vim])
" @Website:     http://www.vim.org/account/profile.php?user_id=4037
" @License:     GPL (see http://www.gnu.org/licenses/gpl.txt)
" @Revision:    1434

" :filedoc:
" A prototype used by |tlib#input#List|.
" Inherits from |tlib#Object#New|.


" Size of the input list window (in percent) from the main size (of &lines).
" See |tlib#input#List()|.
TLet g:tlib_inputlist_pct = 50

" Size of filename columns when listing filenames.
" See |tlib#input#List()|.
TLet g:tlib_inputlist_width_filename = '&co / 3'
" TLet g:tlib_inputlist_width_filename = 25

" If true, |tlib#input#List()| will show some indicators about the 
" status of a filename (e.g. buflisted(), bufloaded() etc.).
" This is disabled by default because vim checks also for the file on 
" disk when doing this.
TLet g:tlib_inputlist_filename_indicators = 0

" If not null, display only a short info about the filter.
TLet g:tlib_inputlist_shortmessage = 0



" Known keys & values:
"   scratch_split ... See |tlib#scratch#UseScratch()|
let s:prototype = tlib#Object#New({
            \ '_class': 'World',
            \ 'name': 'world',
            \ 'allow_suspend': 1,
            \ 'base': [], 
            \ 'bufnr': -1,
            \ 'buffer_local': 1,
            \ 'cache_var': '',
            \ 'display_format': '',
            \ 'fileencoding': &fileencoding,
            \ 'fmt_display': {},
            \ 'fmt_filter': {},
            \ 'fmt_options': {},
            \ 'filetype': '',
            \ 'filter': [['']],
            \ 'filter_format': '',
            \ 'filter_options': '',
            \ 'follow_cursor': '',
            \ 'has_menu': 0,
            \ 'help_extra': [],
            \ 'index_table': [],
            \ 'initial_filter': [['']],
            \ 'initial_index': 1,
            \ 'initial_display': 1,
            \ 'initialized': 0,
            \ 'key_handlers': [],
            \ 'list': [],
            \ 'matcher': {},
            \ 'next_agent': '',
            \ 'next_eval': '',
            \ 'next_state': '',
            \ 'numeric_chars': g:tlib#input#numeric_chars,
            \ 'offset': 1,
            \ 'offset_horizontal': 0,
            \ 'on_leave': [],
            \ 'pick_last_item': tlib#var#Get('tlib#input#pick_last_item', 'bg'),
            \ 'post_handlers': [],
            \ 'query': '',
            \ 'resize': 0,
            \ 'resize_vertical': 0,
            \ 'restore_from_cache': [],
            \ 'filtered_items': [],
            \ 'resume_state': '', 
            \ 'retrieve_eval': '',
            \ 'return_agent': '',
            \ 'rv': '',
            \ 'scratch': '__InputList__',
            \ 'scratch_filetype': 'tlibInputList',
            \ 'scratch_hidden': g:tlib#scratch#hidden,
            \ 'scratch_vertical': 0,
            \ 'scratch_split': 1,
            \ 'sel_idx': [],
            \ 'show_empty': 0,
            \ 'state': 'display', 
            \ 'state_handlers': [],
            \ 'sticky': 0,
            \ 'temp_lines': [],
            \ 'temp_prompt': [],
            \ 'timeout': 0,
            \ 'timeout_resolution': 2,
            \ 'tabpagenr': -1,
            \ 'type': '', 
            \ 'win_wnr': -1,
            \ 'win_height': -1,
            \ 'win_width': -1,
            \ 'win_pct': 25,
            \ })
            " \ 'handlers': [],
            " \ 'filter_options': '\c',

function! tlib#World#New(...)
    let object = s:prototype.New(a:0 >= 1 ? a:1 : {})
    call object.SetMatchMode(tlib#var#Get('tlib#input#filter_mode', 'g', 'cnf'))
    return object
endf


" :nodoc:
function! s:prototype.Set_display_format(value) dict "{{{3
    if a:value == 'filename'
        call self.Set_highlight_filename()
        let self.display_format = 'world.FormatFilename(%s)'
    else
        let self.display_format = a:value
    endif
endf


" :nodoc:
function! s:prototype.DisplayFormat(list) dict "{{{3
    let display_format = self.display_format
    if !empty(display_format)
        if has_key(self, 'InitFormatName')
            call self.InitFormatName()
        endif
        let cache = self.fmt_display
        " TLogVAR display_format, fmt_entries
        return map(copy(a:list), 'self.FormatName(cache, display_format, v:val)')
    else
        return a:list
    endif
endf


" :nodoc:
function! s:prototype.Set_highlight_filename() dict "{{{3
    let self.tlib_UseInputListScratch = 'call world.Highlight_filename()'
endf


if g:tlib#input#format_filename == 'r'

    " :nodoc:
    function! s:prototype.Highlight_filename() dict "{{{3
        syntax match TLibDir /\s\+\zs.\{-}[\/]\ze[^\/]\+$/
        hi def link TLibDir Directory
        syntax match TLibFilename /[^\/]\+$/
        hi def link TLibFilename Normal
    endf

    " :nodoc:
    function! s:prototype.FormatFilename(file) dict "{{{3
        if !has_key(self.fmt_options, 'maxlen')
            let maxco = &co - len(len(self.base)) - eval(g:tlib#input#filename_padding_r)
            let maxfi = max(map(copy(self.base), 'strwidth(v:val)'))
            let self.fmt_options.maxlen = min([maxco, maxfi])
            " TLogVAR maxco, maxfi, self.fmt_options.maxlen
        endif
        let max = self.fmt_options.maxlen
        if len(a:file) > max
            let filename = '...' . strpart(a:file, len(a:file) - max + 3)
        else
            let filename = printf('% '. max .'s', a:file)
        endif
        return filename
    endf

else

    " :nodoc:
    function! s:prototype.Highlight_filename() dict "{{{3
        " let self.width_filename = 1 + eval(g:tlib_inputlist_width_filename)
        " TLogVAR self.base
        let self.width_filename = min([
                    \ get(self, 'width_filename', &co),
                    \ empty(g:tlib#input#filename_max_width) ? &co : eval(g:tlib#input#filename_max_width),
                    \ max(map(copy(self.base), 'strwidth(matchstr(v:val, "[^\\/]*$"))'))
                    \ ])
       "  TLogVAR self.width_filename
         exec 'syntax match TLibDir /\%>'. (1 + self.width_filename) .'c \(|\|\[[^]]*\]\) \zs\(\(\a:\|\.\.\|\.\.\..\{-}\)\?[\/][^&<>*|]\{-}\)\?[^\/]\+$/ contained containedin=TLibMarker contains=TLibFilename'
         exec 'syntax match TLibMarker /\%>'. (1 + self.width_filename) .'c \(|\|\[[^]]*\]\) \S.*$/ contains=TLibDir'
       "  exec 'syntax match TLibDir /\(|\|\[.\{-}\]\) \zs\(\(\a:\|\.\.\|\.\.\..\{-}\)\?[\/][^&<>*|]\{-}\)\?[^\/]\+$/ contained containedin=TLibMarker contains=TLibFilename'
       "  exec 'syntax match TLibMarker /\(|\|\[.\{-}\]\) \S.*$/ contains=TLibDir'
        exec 'syntax match TLibFilename /[^\/]\+$/ contained containedin=TLibDir'
        hi def link TLibMarker Special
        hi def link TLibDir Directory
        hi def link TLibFilename NonText
        " :nodoc:
        function! self.Highlighter(rx) dict
            let rx = '/\c\%>'. (1 + self.width_filename) .'c \(|\|\[[^]]*\]\) .\{-}\zs'. escape(a:rx, '/') .'/'
            exec 'match' self.matcher.highlight rx
        endf
    endf


    " :nodoc:
    function! s:prototype.UseFilenameIndicators() dict "{{{3
        return g:tlib_inputlist_filename_indicators || has_key(self, 'filename_indicators')
    endf


    " :nodoc:
    function! s:prototype.InitFormatName() dict "{{{3 
        if self.UseFilenameIndicators()
            let self._buffers = {}
            for bufnr in range(1, bufnr('$'))
                let filename = fnamemodify(bufname(bufnr), ':p')
                " TLogVAR filename
                let bufdef = {
                            \ 'bufnr': bufnr,
                            \ }
                " '&buflisted'
                for opt in ['&modified', '&bufhidden']
                    let bufdef[opt] = getbufvar(bufnr, opt)
                endfor
                let self._buffers[filename] = bufdef
            endfor
        endif
    endf


    " :nodoc:
    function! s:prototype.FormatFilename(file) dict "{{{3
        " TLogVAR a:file
        let width = self.width_filename
        let split = match(a:file, '[/\\]\zs[^/\\]\+$')
        if split == -1
            let fname = a:file
            let dname = a:file
        else
            let fname = strpart(a:file, split)
            " let dname = strpart(a:file, 0, split - 1)
            let dname = a:file
        endif
        if strwidth(fname) > width
            let fname = strpart(fname, 0, width - 3) .'...'
        endif
        let dnmax = &co - max([width, strwidth(fname)]) - 8 - self.index_width - &fdc
        let use_indicators = self.UseFilenameIndicators()
        " TLogVAR use_indicators
        let marker = []
        if use_indicators
            call insert(marker, '[')
            if g:tlib_inputlist_filename_indicators
                let bufdef = get(self._buffers, a:file, {})
                " let bnr = bufnr(a:file)
                let bnr = get(bufdef, 'bufnr', -1)
                " TLogVAR a:file, bnr, self.bufnr
                if bnr != -1
                    if bnr == self.bufnr
                        call add(marker, '%')
                    else
                        call add(marker, bnr)
                    endif
                    if get(bufdef, '&modified', 0)
                        call add(marker, '+')
                    endif
                    if get(bufdef, '&bufhidden', '') == 'hide'
                        call add(marker, 'h')
                    endif
                    " if !get(bufdef, '&buflisted', 1)
                    "     call add(marker, 'u')
                    " endif
                    " echom "DBG" a:file string(get(self,'filename_indicators'))
                endif
            endif
            if has_key(self, 'filename_indicators') && has_key(self.filename_indicators, a:file)
                if len(marker) > 1
                    call add(marker, '|')
                endif
                call add(marker, self.filename_indicators[a:file])
            endif
            if len(marker) <= 1
                call add(marker, ' ')
            endif
            call add(marker, ']')
        else
            call add(marker, '|')
        endif
        let markers = join(marker, '')
        if !empty(markers)
            let dnmax -= len(markers)
        endif
        if strwidth(dname) > dnmax
            let dname = '...'. strpart(dname, len(dname) - dnmax)
        endif
        return printf("%-*s %s %s",
                    \ self.width_filename + len(fname) - strwidth(fname),
                    \ fname, markers, dname)
    endf

endif


" :nodoc:
function! s:prototype.GetSelectedItems(current) dict "{{{3
    " TLogVAR a:current
    if stridx(self.type, 'i') != -1
        let rv = copy(self.sel_idx)
    else
        let rv = map(copy(self.sel_idx), 'self.GetBaseItem(v:val)')
    endif
    if !empty(a:current)
        " TLogVAR a:current, rv, type(a:current)
        if tlib#type#IsNumber(a:current) || tlib#type#IsString(a:current)
            call s:InsertSelectedItems(rv, a:current)
        elseif tlib#type#IsList(a:current)
            for item in a:current
                call s:InsertSelectedItems(rv, item)
            endfor
        elseif tlib#type#IsDictionary(a:current)
            for [inum, item] in items(a:current)
                call s:InsertSelectedItems(rv, item)
            endfor
        endif
    endif
    " TAssert empty(rv) || rv[0] == a:current
    if stridx(self.type, 'i') != -1
        if !empty(self.index_table)
            " TLogVAR rv, self.index_table
            call map(rv, 'self.index_table[v:val - 1]')
            " TLogVAR rv
        endif
    endif
    return rv
endf


function! s:InsertSelectedItems(rv, current) "{{{3
    let ci = index(a:rv, a:current)
    if ci != -1
        call remove(a:rv, ci)
    endif
    call insert(a:rv, a:current)
endf


" :nodoc:
function! s:prototype.SelectItemsByNames(mode, items) dict "{{{3
    for item in a:items
        let bi = index(self.base, item) + 1
        " TLogVAR item, bi
        if bi > 0
            let si = index(self.sel_idx, bi)
            " TLogVAR self.sel_idx
            " TLogVAR si
            if si == -1
                call add(self.sel_idx, bi)
            elseif a:mode == 'toggle'
                call remove(self.sel_idx, si)
            endif
        endif
    endfor
    return 1
endf


" :nodoc:
function! s:prototype.SelectItem(mode, index) dict "{{{3
    " TLogVAR a:mode, a:index
    let bi = self.GetBaseIdx(a:index)
    " if self.RespondTo('MaySelectItem')
    "     if !self.MaySelectItem(bi)
    "         return 0
    "     endif
    " endif
    " TLogVAR bi
    let si = index(self.sel_idx, bi)
    " TLogVAR self.sel_idx
    " TLogVAR si
    if si == -1
        call add(self.sel_idx, bi)
    elseif a:mode == 'toggle'
        call remove(self.sel_idx, si)
    endif
    return 1
endf


" :nodoc:
function! s:prototype.FormatBaseFromData() abort dict "{{{3
    if has_key(self, 'format_data') && has_key(self, 'data')
        let self.base = map(copy(self.data), 'call(self.format_data, [v:val], self)')
    endif    
endf


" :nodoc:
function! s:prototype.FormatArgs(format_string, arg) dict "{{{3
    let nargs = len(substitute(a:format_string, '%%\|[^%]', '', 'g'))
    return [a:format_string] + repeat([string(a:arg)], nargs)
endf


" :nodoc:
function! s:prototype.GetRx(filter) dict "{{{3
    return '\('. join(filter(copy(a:filter), 'v:val[0] != "!"'), '\|') .'\)' 
endf


" :nodoc:
function! s:prototype.GetRx0(...) dict "{{{3
    exec tlib#arg#Let(['negative'])
    let rx0 = []
    for filter in self.filter
        " TLogVAR filter
        let rx = join(reverse(filter(copy(filter), '!empty(v:val)')), '\|')
        " TLogVAR rx
        if !empty(rx) && (negative ? rx[0] == g:tlib#input#not : rx[0] != g:tlib#input#not)
            call add(rx0, rx)
        endif
    endfor
    let rx0s = join(rx0, '\|')
    if empty(rx0s)
        return ''
    else
        return self.FilterRxPrefix() .'\('. rx0s .'\)'
    endif
endf


" :nodoc:
function! s:prototype.FormatName(cache, format, value) dict "{{{3
    " TLogVAR a:format, a:value
    " TLogDBG has_key(self.fmt_display, a:value)
    if has_key(a:cache, a:value)
        " TLogDBG "cached"
        return a:cache[a:value]
    else
        let world = self
        let ftpl = self.FormatArgs(a:format, a:value)
        let fn = call(function("printf"), ftpl)
        let fmt = eval(fn)
        " TLogVAR ftpl, fn, fmt
        let a:cache[a:value] = fmt
        return fmt
    endif
endf


" :nodoc:
function! s:prototype.GetItem(idx) dict "{{{3
    return self.list[a:idx - 1]
endf


" :nodoc:
function! s:prototype.GetListIdx(baseidx) dict "{{{3
    " if empty(self.index_table)
        let baseidx = a:baseidx
    " else
    "     let baseidx = 0 + self.index_table[a:baseidx - 1]
    "     " TLogVAR a:baseidx, baseidx, self.index_table 
    " endif
    let rv = index(self.table, baseidx)
    " TLogVAR rv, self.table
    return rv
endf


" :nodoc:
" The first index is 1.
function! s:prototype.GetBaseIdx(idx) dict "{{{3
    " TLogVAR a:idx, self.table, self.index_table
    if !empty(self.table) && a:idx > 0 && a:idx <= len(self.table)
        return self.table[a:idx - 1]
    else
        return 0
    endif
endf


" :nodoc:
function! s:prototype.GetBaseIdx0(idx) dict "{{{3
    let idx0 = self.GetBaseIdx(a:idx) - 1
    if idx0 < 0
        call tlib#notify#Echo('TLIB: Internal Error: GetBaseIdx0: idx0 < 0', 'WarningMsg')
    endif
    return idx0
endf


" :nodoc:
function! s:prototype.GetBaseItem(idx) dict "{{{3
    return self.base[a:idx - 1]
endf


" :nodoc:
function! s:prototype.SetBaseItem(idx, item) dict "{{{3
    let self.base[a:idx - 1] = a:item
endf


" :nodoc:
function! s:prototype.GetLineIdx(lnum) dict "{{{3
    let line = getline(a:lnum)
    let prefidx = substitute(matchstr(line, '^\d\+\ze[*:]'), '^0\+', '', '')
    return prefidx
endf


" :nodoc:
function! s:prototype.SetPrefIdx() dict "{{{3
    " let pref = sort(range(1, self.llen), 'self.SortPrefs')
    " let self.prefidx = get(pref, 0, self.initial_index)
    let pref_idx = -1
    let pref_weight = -1
    " TLogVAR self.filter_pos, self.filter_neg
    " let t0 = localtime() " DBG
    for idx in range(1, self.llen)
        let item = self.GetItem(idx)
        let weight = self.matcher.AssessName(self, item)
        " TLogVAR item, weight
        if weight > pref_weight
            let pref_idx = idx
            let pref_weight = weight
        endif
    endfor
    " TLogVAR localtime() - t0
    " TLogVAR pref_idx
    " TLogDBG self.GetItem(pref_idx)
    if pref_idx == -1
        let self.prefidx = self.initial_index
    else
        let self.prefidx = pref_idx
    endif
endf


" " :nodoc:
" function! s:prototype.GetCurrentItem() dict "{{{3
"     let idx = self.prefidx
"     " TLogVAR idx
"     if stridx(self.type, 'i') != -1
"         return idx
"     elseif !empty(self.list)
"         if len(self.list) >= idx
"             let idx1 = idx - 1
"             let rv = self.list[idx - 1]
"             " TLogVAR idx, idx1, rv, self.list
"             return rv
"         endif
"     else
"         return ''
"     endif
" endf


" :nodoc:
function! s:prototype.CurrentItem() dict "{{{3
    if stridx(self.type, 'i') != -1
        return self.GetBaseIdx(self.llen == 1 ? 1 : self.prefidx)
    else
        if self.llen == 1
            " TLogVAR self.llen
            return self.list[0]
        elseif self.prefidx > 0
            " TLogVAR self.prefidx
            " return self.GetCurrentItem()
            if len(self.list) >= self.prefidx
                let rv = self.list[self.prefidx - 1]
                " TLogVAR idx, rv, self.list
                return rv
            endif
        else
            return ''
        endif
    endif
endf


" :nodoc:
function! s:prototype.FilterRxPrefix() dict "{{{3
    return self.matcher.FilterRxPrefix()
endf


" :nodoc:
function! s:prototype.SetFilter() dict "{{{3
    " let mrx = '\V'. (a:0 >= 1 && a:1 ? '\C' : '')
    let mrx = self.FilterRxPrefix() . self.filter_options
    let self.filter_pos = []
    let self.filter_neg = []
    " TLogVAR mrx, self.filter
    for filter in self.filter
        " TLogVAR filter
        let rx = join(reverse(filter(copy(filter), '!empty(v:val)')), '\|')
        " TLogVAR rx
        if !empty(rx)
            if rx =~ '\u'
                let mrx1 = mrx .'\C'
            else
                let mrx1 = mrx
            endif
            " TLogVAR rx
            if rx[0] == g:tlib#input#not
                if len(rx) > 1
                    call add(self.filter_neg, mrx1 .'\('. rx[1:-1] .'\)')
                endif
            else
                call add(self.filter_pos, mrx1 .'\('. rx .'\)')
            endif
        endif
    endfor
    " TLogVAR self.filter_pos, self.filter_neg
endf


" :nodoc:
function! s:prototype.IsValidFilter() dict "{{{3
    let last = self.FilterRxPrefix() .'\('. self.filter[0][0] .'\)'
    Tlibtrace 'tlib', last
    " TLogVAR last
    try
        let a = match("", last)
        return 1
    catch
        Tlibtrace 'tlib', v:exception
        return 0
    endtry
endf


" :nodoc:
function! s:prototype.SetMatchMode(match_mode) dict "{{{3
    " TLogVAR a:match_mode
    if !empty(a:match_mode)
        unlet self.matcher
        try
            let self.matcher = tlib#Filter_{a:match_mode}#New()
            call self.matcher.Init(self)
        catch /^Vim\%((\a\+)\)\=:E117/
            throw 'tlib: Unknown mode for tlib#input#filter_mode: '. a:match_mode
        endtry
    endif
endf


" function! s:prototype.Match(text) dict "{{{3
"     return self.matcher.Match(self, text)
" endf


" :nodoc:
function! s:prototype.MatchBaseIdx(idx) dict "{{{3
    let text = self.GetBaseItem(a:idx)
    if !empty(self.filter_format)
        let text = self.FormatName(self.fmt_filter, self.filter_format, text)
    endif
    " TLogVAR text
    " return self.Match(text)
    return self.matcher.Match(self, text)
endf


" :nodoc:
function! s:prototype.BuildTableList() dict "{{{3
    " let time0 = str2float(reltimestr(reltime()))  " DBG
    " TLogVAR time0
    call self.SetFilter()
    " TLogVAR self.filter_neg, self.filter_pos
    let self.table = range(1, len(self.base))
    " TLogVAR self.filtered_items
    let copy_base = 1
    if !empty(self.filtered_items)
        let self.table = filter(self.table, 'index(self.filtered_items, v:val) != -1')
        let copy_base = 0
    endif
    if !empty(self.filter_pos) || !empty(self.filter_neg)
        let self.table = filter(self.table, 'self.MatchBaseIdx(v:val)')
        let copy_base = 0
    endif
    if copy_base
        let self.list = copy(self.base)
    else
        let self.list  = map(copy(self.table), 'self.GetBaseItem(v:val)')
    endif
endf


" :nodoc:
function! s:prototype.ReduceFilter() dict "{{{3
    " TLogVAR self.filter
    let reduced = 0
    while !reduced
        if self.filter[0] == [''] && len(self.filter) > 1
            call remove(self.filter, 0)
        elseif empty(self.filter[0][0]) && len(self.filter[0]) > 1
            call remove(self.filter[0], 0)
        else
            call self.matcher.ReduceFrontFilter(self)
        endif
        if self.IsValidFilter()
            let reduced = 1
        endif
    endwh
endf


" :nodoc:
" filter is either a string or a list of list of strings.
function! s:prototype.SetInitialFilter(filter) dict "{{{3
    " let self.initial_filter = [[''], [a:filter]]
    Tlibtrace 'tlib', a:filter
    if type(a:filter) == 3
        let self.initial_filter = deepcopy(a:filter)
    else
        let self.initial_filter = [[a:filter]]
    endif
endf


" :nodoc:
function! s:prototype.PopFilter() dict "{{{3
    " TLogVAR self.filter
    if len(self.filter[0]) > 1
        call remove(self.filter[0], 0)
    elseif len(self.filter) > 1
        call remove(self.filter, 0)
    else
        let self.filter[0] = ['']
    endif
endf


" :nodoc:
function! s:prototype.FilterIsEmpty() dict "{{{3
    " TLogVAR self.filter
    return self.filter == copy(self.initial_filter)
endf


" :nodoc:
function! s:prototype.DisplayFilter() dict "{{{3
    let filter1 = copy(self.filter)
    call filter(filter1, 'v:val != [""]')
    " TLogVAR self.matcher['_class']
    let rv = self.matcher.DisplayFilter(filter1)
    let rv = self.CleanFilter(rv)
    return rv
endf


" :nodoc:
function! s:prototype.SetFrontFilter(pattern) dict "{{{3
    call self.matcher.SetFrontFilter(self, a:pattern)
endf


" :nodoc:
function! s:prototype.PushFrontFilter(char) dict "{{{3
    call self.matcher.PushFrontFilter(self, a:char)
endf


" :nodoc:
function! s:prototype.CleanFilter(filter) dict "{{{3
    return self.matcher.CleanFilter(a:filter)
endf


" :nodoc:
function! s:prototype.UseScratch() dict "{{{3
    " if type(self.scratch) != 0 && get(self, 'buffer_local', 1)
    "     if self.scratch != fnamemodify(self.scratch, ':p')
    "         let self.scratch = tlib#file#Join([expand('%:p:h'), self.scratch])
    "         " TLogVAR self.scratch
    "     endif
    "     " let self.scratch_hidden = 'wipe'
    " endif
    keepjumps keepalt let rv = tlib#scratch#UseScratch(self)
    " if expand('%:t') == self.scratch
        let b:tlib_world = self
    " endif
    return rv
endf


" :nodoc:
function! s:prototype.CloseScratch(...) dict "{{{3
    TVarArg ['reset_scratch', 0]
    " TVarArg ['reset_scratch', 1]
    " TLogVAR reset_scratch
    if self.sticky
        return 0
    else
        let rv = tlib#scratch#CloseScratch(self, reset_scratch)
        " TLogVAR rv
        if rv
            call self.SwitchWindow('win')
        endif
        return rv
    endif
endf


" :nodoc:
function! s:prototype.Initialize() dict "{{{3
    let self.initialized = 1
    call self.SetOrigin(1)
    call self.Reset(1)
    if !empty(self.cache_var) && exists(self.cache_var)
        for prop in self.restore_from_cache
            exec 'let self[prop] = get('. self.cache_var .', prop, self[prop])'
        endfor
        exec 'unlet '. self.cache_var
    endif
endf


" :nodoc:
function! s:prototype.Leave() dict "{{{3
    if !empty(self.cache_var)
        exec 'let '. self.cache_var .' = self'
    endif
    for handler in self.on_leave
        call call(handler, [self])
    endfor
endf


" :nodoc:
function! s:prototype.UseInputListScratch() dict "{{{3
    let scratch = self.UseScratch()
    if !exists('b:tlib_list_init')
        call tlib#autocmdgroup#Init()
        autocmd TLib VimResized <buffer> call feedkeys("\<c-j>", 't')
        " autocmd TLib WinLeave <buffer> let b:tlib_world_event = 'WinLeave' | call feedkeys("\<c-j>", 't')
        let b:tlib_list_init = 1
    endif
    if !exists('w:tlib_list_init')
        " TLogVAR scratch
        if has_key(self, 'index_next_syntax')
            if type(self.index_next_syntax) == 1
                exec 'syntax match InputlListIndex /^\d\+:\s/ nextgroup='. self.index_next_syntax
            elseif type(self.index_next_syntax) == 4
                for [n, nsyn] in items(self.index_next_syntax)
                    let fn = printf('%0'. world.index_width .'d', n)
                    exec 'syntax match InputlListIndex /^'. fn .':\s/ nextgroup='. nsyn
                endfor
            endif
        else
            syntax match InputlListIndex /^\d\+:\s/
        endif
        call tlib#hook#Run('tlib_UseInputListScratch', self)
        syntax match InputlListCursor /^\d\+\* .*$/ contains=InputlListIndex
        syntax match InputlListSelected /^\d\+# .*$/ contains=InputlListIndex
        hi def link InputlListIndex Constant
        hi def link InputlListCursor Search
        hi def link InputlListSelected IncSearch
        setlocal nowrap
        " hi def link InputlListIndex Special
        " let b:tlibDisplayListMarks = {}
        let b:tlibDisplayListMarks = []
        let b:tlibDisplayListWorld = self
        let w:tlib_list_init = 1
    endif
    return scratch
endf


" s:prototype.Reset(?initial=0)
" :nodoc:
function! s:prototype.Reset(...) dict "{{{3
    TVarArg ['initial', 0]
    " TLogVAR initial
    Tlibtrace 'tlib', initial, self.initial_filter
    let self.state     = 'display'
    let self.offset    = 1
    let self.filter    = deepcopy(self.initial_filter)
    let self.idx       = ''
    let self.prefidx   = 0
    let self.initial_display = 1
    let self.fmt_display = {}
    let self.fmt_filter = {}
    call self.UseInputListScratch()
    call self.ResetSelected()
    call self.Retrieve(!initial)
    call self.FormatBaseFromData()
    return self
endf


" :nodoc:
function! s:prototype.ResetSelected() dict "{{{3
    let self.sel_idx   = []
endf


" :nodoc:
function! s:prototype.Retrieve(anyway) dict "{{{3
    " TLogVAR a:anyway, self.base
    " TLogDBG (a:anyway || empty(self.base))
    if (a:anyway || empty(self.base))
        let ra = self.retrieve_eval
        " TLogVAR ra
        if !empty(ra)
            let back  = self.SwitchWindow('win')
            let world = self
            let self.base = eval(ra)
            " TLogVAR self.base
            exec back
            return 1
        endif
    endif
    return 0
endf


function! s:FormatHelp(help) "{{{3
    " TLogVAR a:help
    let max = [0, 0]
    for item in a:help
        " TLogVAR item
        if type(item) == 3
            let itemlen = map(copy(item), 'strwidth(v:val)')
            " TLogVAR itemlen
            let max = map(range(2), 'max[v:val] >= itemlen[v:val] ? max[v:val] : itemlen[v:val]')
        endif
        unlet item
    endfor
    " TLogVAR max
    let cols = float2nr((winwidth(0) - &foldcolumn - 1) / (max[0] + max[1] + 2))
    if cols < 1
        let cols = 1
    endif
    let fmt = printf('%%%ds: %%-%ds', max[0], max[1])
    " TLogVAR cols, fmt
    let help = []
    let idx = -1
    let maxidx = len(a:help)
    while idx < maxidx
        let push_item = 0
        let accum = []
        for i in range(cols)
            let idx += 1
            if idx >= maxidx
                break
            endif
            let item = a:help[idx]
            if type(item) == 3
                call add(accum, item)
            else
                let push_item = 1
                break
            endif
            unlet item
        endfor
        if !empty(accum)
            call add(help, s:FormatHelpItem(accum, fmt))
        endif
        if push_item
            call add(help, a:help[idx])
        endif
    endwh
    " TLogVAR help
    return help
endf


function! s:FormatHelpItem(item, fmt) "{{{3
    let args = [join(repeat([a:fmt], len(a:item)), '  ')]
    for item in a:item
        " TLogVAR item
        let args += item
    endfor
    " TLogVAR args
    return call('printf', args)
endf


" :nodoc:
function! s:prototype.InitHelp() dict "{{{3
    return []
endf


" :nodoc:
function! s:prototype.PushHelp(...) dict "{{{3
    " TLogVAR a:000
    if a:0 == 1
        if type(a:1) == 3
            let self.temp_lines += a:1
        else
            call add(self.temp_lines, a:1)
        endif
    elseif a:0 == 2
        call add(self.temp_lines, a:000)
    else
        throw "TLIB: PushHelp: Wrong number of arguments: ". string(a:000)
    endif
    " TLogVAR helpstring
endf


" :nodoc:
function! s:prototype.DisplayHelp() dict "{{{3
    let self.temp_lines = self.InitHelp()
    call self.PushHelp('<Esc>', self.key_mode == 'default' ? 'Abort' : 'Reset keymap')
    call self.PushHelp('Enter, <cr>', 'Pick the current item')
    call self.PushHelp('Mouse', 'L: Pick item, R: Show menu')
    call self.PushHelp('<M-Number>',  'Select an item')
    call self.PushHelp('<BS>, <C-BS>', 'Reduce filter')
    call self.PushHelp('<Tab>', 'Complete word')
    call self.PushHelp('<S-Esc>, <F10>', 'Enter command')

    if self.key_mode == 'default'
        call self.PushHelp('<C|M-r>',      'Reset the display')
        call self.PushHelp('Up/Down',      'Next/previous item')
        call self.PushHelp('<C|M-q>',      'Edit top filter string')
        call self.PushHelp('Page Up/Down', 'Scroll')
        call self.PushHelp('<S-Space>',    'Enter * Wildcard')
        if self.allow_suspend
            call self.PushHelp('<C|M-z>', 'Suspend/Resume')
            call self.PushHelp('<C-o>', 'Switch to origin')
        endif
        if stridx(self.type, 'm') != -1
            call self.PushHelp('<S-Up/Down>', '(Un)Select items')
            call self.PushHelp('#', '(Un)Select the current item')
            call self.PushHelp('<C|M-a>', '(Un)Select all items')
            call self.PushHelp('<F9>', '(Un)Restrict view to selection')
            " \ '<c-\>        ... Show only selected',
        endif
    endif

    " TLogVAR len(self.temp_lines)
    call self.matcher.Help(self)

    " TLogVAR self.key_mode
    for handler in values(self.key_map[self.key_mode])
        " TLogVAR handler
        let key = get(handler, 'key_name', '')
        " TLogVAR key
        if !empty(key)
            let desc = get(handler, 'help', '')
            if empty(desc)
                let desc = get(handler, 'agent', '')
            endif
            call self.PushHelp(key, desc)
        endif
    endfor

    if !has_key(self.key_map[self.key_mode], 'unknown_key')
        call self.PushHelp('Letter', 'Filter the list')
    endif

    if self.key_mode == 'default' && !empty(self.help_extra)
        call self.PushHelp(self.help_extra)
    endif

    " TLogVAR len(self.temp_lines)
    call self.PushHelp([
                \ '',
                \ 'Matches at word boundaries are prioritized.',
                \ ])
    let self.temp_lines = s:FormatHelp(self.temp_lines)
    call self.PrintLines()
endf


function! s:prototype.PrintLines() dict "{{{3
    let self.temp_prompt = ['Press any key to continue.', 'Question']
    call tlib#buffer#DeleteRange('1', '$')
    call append(0, self.temp_lines)
    call tlib#buffer#DeleteRange('$', '$')
    1
    call self.Resize(len(self.temp_lines), 0)
    let self.temp_lines = []
endf


" :nodoc:
function! s:prototype.Resize(hsize, vsize) dict "{{{3
    " TLogVAR self.scratch_vertical, a:hsize, a:vsize
    let world_resize = ''
    let winpos = ''
    let scratch_split = get(self, 'scratch_split', 1)
    " TLogVAR scratch_split
    if scratch_split > 0
        if self.scratch_vertical
            if a:vsize
                let world_resize = 'vert resize '. a:vsize
                let winpos = tlib#fixes#Winpos()
                " let w:winresize = {'v': a:vsize}
                setlocal winfixwidth
            endif
        else
            if a:hsize
                let world_resize = 'resize '. a:hsize
                " let w:winresize = {'h': a:hsize}
                setlocal winfixheight
            endif
        endif
    endif
    if !empty(world_resize)
        " TLogVAR world_resize, winpos
        exec world_resize
        if !empty(winpos)
            exec winpos
        endif
        " redraw!
    endif
endf


" :nodoc:
function! s:prototype.GetResize(size) dict "{{{3
    let resize0 = get(self, 'resize', 0)
    let resize = empty(resize0) ? 0 : eval(resize0)
    " TLogVAR resize0, resize
    let resize = resize == 0 ? a:size : min([a:size, resize])
    " let min = self.scratch_vertical ? &cols : &lines
    let min1 = (self.scratch_vertical ? self.win_width : self.win_height) * g:tlib_inputlist_pct
    let min2 = (self.scratch_vertical ? &columns : &lines) * self.win_pct
    let min = max([min1, min2])
    let resize = min([resize, (min / 100)])
    " TLogVAR resize, a:size, min, min1, min2
    return resize
endf


" function! s:prototype.DisplayList(?query=self.Query(), ?list=[])
" :nodoc:
function! s:prototype.DisplayList(...) dict "{{{3
    " TLogVAR self.state
    let query = a:0 >= 1 ? a:1 : self.Query()
    let list = a:0 >= 2 ? a:2 : []
    " TLogVAR query, len(list)
    " TLogDBG 'len(list) = '. len(list)
    call self.UseScratch()
    " TLogVAR self.scratch
    " TAssert IsNotEmpty(self.scratch)
    if self.state == 'scroll'
        call self.ScrollToOffset()
    elseif self.state == 'help'
        call self.DisplayHelp()
        call self.SetStatusline(query)
    elseif self.state == 'printlines'
        call self.PrintLines()
        call self.SetStatusline(query)
    else
        " TLogVAR query
        " let ll = len(list)
        let ll = self.llen
        " let x  = len(ll) + 1
        let x  = self.index_width + 1
        " TLogVAR ll
        if self.state =~ '\<display\>'
            call self.Resize(self.GetResize(ll), eval(get(self, 'resize_vertical', 0)))
            call tlib#normal#WithRegister('gg"tdG', 't')
            let lines = copy(list)
            let lines = map(lines, 'substitute(v:val, ''[[:cntrl:][:space:]]'', " ", "g")')
            let w = winwidth(0) - &fdc
            " let w = winwidth(0) - &fdc - 1
            let lines = map(lines, 'printf("%-'. w .'.'. w .'s", v:val)')
            " TLogVAR lines
            call append(0, lines)
            call tlib#normal#WithRegister('G"tddgg', 't')
        endif
        " TLogVAR self.prefidx
        let base_pref = self.GetBaseIdx(self.prefidx)
        " TLogVAR base_pref
        if self.state =~ '\<redisplay\>'
            call filter(b:tlibDisplayListMarks, 'index(self.sel_idx, v:val) == -1 && v:val != base_pref')
            " TLogVAR b:tlibDisplayListMarks
            call map(b:tlibDisplayListMarks, 'self.DisplayListMark(x, v:val, ":")')
            " let b:tlibDisplayListMarks = map(copy(self.sel_idx), 'self.DisplayListMark(x, v:val, "#")')
            " call add(b:tlibDisplayListMarks, self.prefidx)
            " call self.DisplayListMark(x, self.GetBaseIdx(self.prefidx), '*')
        endif
        let b:tlibDisplayListMarks = map(copy(self.sel_idx), 'self.DisplayListMark(x, v:val, "#")')
        call add(b:tlibDisplayListMarks, base_pref)
        call self.DisplayListMark(x, base_pref, '*')
        call self.SetOffset()
        call self.SetStatusline(query)
        " TLogVAR self.offset
        call self.ScrollToOffset()
        let rx0 = self.GetRx0()
        " TLogVAR rx0
        if !empty(self.matcher.highlight)
            if empty(rx0)
                match none
            elseif self.IsValidFilter()
                if has_key(self, 'Highlighter')
                    call self.Highlighter(rx0)
                else
                    exec 'match '. self.matcher.highlight .' /\c'. escape(rx0, '/') .'/'
                endif
            endif
        endif
    endif
    redraw
endf


" :nodoc:
function! s:prototype.SetStatusline(query) dict "{{{3
    " TLogVAR a:query
    if !empty(self.temp_prompt)
        let echo = get(self.temp_prompt, 0, '')
        let hl = get(self.temp_prompt, 1, 'Normal')
        let self.temp_prompt = []
    else
        let hl = 'Normal'
        let query   = a:query
        let options = [self.matcher.name]
        if self.sticky
            call add(options, '#')
        endif
        if self.key_mode != 'default'
            call add(options, 'map:'. self.key_mode)
        endif
        if !empty(self.filtered_items)
            if g:tlib_inputlist_shortmessage
                call add(options, 'R')
            else
                call add(options, 'restricted')
            endif
        endif
        if !empty(options)
            let sopts = printf('[%s]', join(options, ', '))
            " let echo  = query . repeat(' ', &columns - len(sopts) - len(query) - 20) . sopts
            let echo  = query . '  ' . sopts
            " let query .= '%%='. sopts .' '
        endif
        " TLogVAR &l:statusline, query
        " let &l:statusline = query
    endif
    echo
    if hl != 'Normal'
        exec 'echohl' hl
        echo echo
        echohl None
    else
        echo echo
    endif
endf


" :nodoc:
function! s:prototype.Query() dict "{{{3
    let flt = self.DisplayFilter()
    if g:tlib_inputlist_shortmessage
        let query = 'Filter: '. flt
    else
        let query = self.query .' (filter: '. flt .'; press <F1> for help)'
    endif
    return query
endf


" :nodoc:
function! s:prototype.ScrollToOffset() dict "{{{3
    " TLogVAR self.scratch_vertical, self.llen, winheight(0)
    exec 'norm! '. self.offset .'zt'
endf


" :nodoc:
function! s:prototype.SetOffset() dict "{{{3
    " TLogVAR self.prefidx, self.offset
    " TLogDBG winheight(0)
    " TLogDBG self.prefidx > self.offset + winheight(0) - 1
    let listtop = len(self.list) - winheight(0) + 1
    if listtop < 1
        let listtop = 1
    endif
    if self.prefidx > listtop
        let self.offset = listtop
    elseif self.prefidx > self.offset + winheight(0) - 1
        let listoff = self.prefidx - winheight(0) + 1
        let self.offset = min([listtop, listoff])
    "     TLogVAR self.prefidx
    "     TLogDBG len(self.list)
    "     TLogDBG winheight(0)
    "     TLogVAR listtop, listoff, self.offset
    elseif self.prefidx < self.offset
        let self.offset = self.prefidx
    endif
    " TLogVAR self.offset
endf


" :nodoc:
function! s:prototype.ClearAllMarks() dict "{{{3
    let x = self.index_width + 1
    call map(range(1, line('$')), 'self.DisplayListMark(x, v:val, ":")')
endf


" :nodoc:
function! s:prototype.MarkCurrent(y) dict "{{{3
    let x = self.index_width + 1
    call self.DisplayListMark(x, a:y, '*')
endf


" :nodoc:
function! s:prototype.DisplayListMark(x, y, mark) dict "{{{3
    " TLogVAR a:y, a:mark
    if a:x > 0 && a:y >= 0
        " TLogDBG a:x .'x'. a:y .' '. a:mark
        let sy = self.GetListIdx(a:y) + 1
        " TLogVAR sy
        if sy >= 1
            call setpos('.', [0, sy, a:x, 0])
            exec 'norm! r'. a:mark
            " exec 'norm! '. a:y .'gg'. a:x .'|r'. a:mark
        endif
    endif
    return a:y
endf


" :nodoc:
function! s:prototype.SwitchWindow(where) dict "{{{3
    " TLogDBG string(tlib#win#List())
    if self.tabpagenr != tabpagenr()
        call tlib#tab#Set(self.tabpagenr)
    endif
    let wnr = get(self, a:where.'_wnr')
    " TLogVAR self, wnr
    return tlib#win#Set(wnr)
endf


" :nodoc:
function! s:prototype.FollowCursor() dict "{{{3
    if !empty(self.follow_cursor)
        let back = self.SwitchWindow('win')
        " TLogVAR back
        " TLogDBG winnr()
        try
            call call(self.follow_cursor, [self, [self.CurrentItem()]])
        finally
            exec back
        endtry
    endif
endf


" :nodoc:
function! s:prototype.SetOrigin(...) dict "{{{3
    TVarArg ['winview', 0]
    " TLogVAR self.win_wnr, self.bufnr
    " TLogDBG bufname('%')
    " TLogDBG winnr()
    " TLogDBG winnr('$')
    let self.win_wnr = winnr()
    let self.win_height = winheight(self.win_wnr)
    let self.win_width = winwidth(self.win_wnr)
    " TLogVAR self.win_wnr, self.win_height, self.win_width
    let self.bufnr   = bufnr('%')
    let self.tabpagenr = tabpagenr()
    let self.cursor  = getpos('.')
    if winview
        let self.winview = tlib#win#GetLayout()
    endif
    " TLogVAR self.win_wnr, self.bufnr, self.winview
    return self
endf


" :nodoc:
function! s:prototype.RestoreOrigin(...) dict "{{{3
    TVarArg ['winview', 0]
    if winview
        " TLogVAR winview
        call tlib#win#SetLayout(self.winview)
    endif
    " TLogVAR self.win_wnr, self.bufnr, self.cursor, &splitbelow
    " TLogDBG "RestoreOrigin0 ". string(tlib#win#List())
    " If &splitbelow or &splitright is false, we cannot rely on 
    " self.win_wnr to be our source buffer since, e.g, opening a buffer 
    " in a split window changes the whole layout.
    " Possible solutions:
    " - Restrict buffer switching to cases when the number of windows 
    "   hasn't changed.
    " - Guess the right window, which we try to do here.
    if &splitbelow == 0 || &splitright == 0
        let wn = bufwinnr(self.bufnr)
        " TLogVAR wn
        if wn == -1
            let wn = 1
        end
    else
        let wn = self.win_wnr
    endif
    if wn != winnr()
        exec wn .'wincmd w'
    endif
    exec 'buffer! '. self.bufnr
    call setpos('.', self.cursor)
    " TLogDBG "RestoreOrigin1 ". string(tlib#win#List())
endf


function! s:prototype.Suspend() dict "{{{3
    call tlib#agent#Suspend(self, self.rv)
endf

