Commits

ZyX_I  committed 76fb28e

Moved everything from plugin/ to autoload/

  • Participants
  • Parent commits b99a842

Comments (0)

Files changed (5)

File autoload/gtregen.vim

+"▶1
+scriptencoding utf-8
+execute frawor#Setup('0.0', {'@/fwc': '0.0',
+            \          '@/functions': '0.0',
+            \           '@/mappings': '0.0',
+            \              '@/table': '0.0',
+            \    '@%gtregen/bufvars': '0.0',})
+let s:_messages={
+            \'treeex': 'Tree %s already exsists',
+            \'invrun': 'Failed to run mapping',
+        \}
+let s:tree={}
+let s:trees={}
+"▶1 Default mappings
+let s:defmappings={}
+"▶2 (un)selection
+let s:defmappings._select={'lhs': 's', 'selection': 'ignore', 'passnodes': 0}
+function s:defmappings._select.function(bvar, lnr)
+    let synname='TreeSelected_'.a:lnr
+    let a:bvar.selected[a:lnr]=synname
+    let blnr=a:lnr+a:bvar.cstart
+    execute 'syntax region '.synname.' start=/\v^%'.blnr.'l/'.
+                \                    ' end=/\v%'.blnr.'l$/'.
+                \                    ' contains=TreeSelected'.
+                \                    ' contained containedin=TreeData'
+    return ''
+endfunction
+let s:defmappings._unselect={'lhs': 'S', 'selection': 'ignore', 'passnodes': 0}
+function s:F.unselect(bvar, lnr)
+    if has_key(a:bvar.selected, a:lnr)
+        let synname=remove(a:bvar.selected, a:lnr)
+        execute 'syntax clear '.synname
+    endif
+    return ''
+endfunction
+let s:defmappings._unselect.function=s:F.unselect
+"▶1 runmap :: tree, mdescr
+function s:F.runmap(tree, mdescr, mode)
+    let bvar=s:_r.bufvars[bufnr('%')]
+    let postcmd=''
+    if a:mode is# 'x'
+        let lstart=line("'<")
+        let lend=line("'>")
+        if lstart>lend
+            let [lstart, lend]=[lend, lstart]
+        endif
+        if lstart<bvar.cstart
+            let lstart=bvar.cstart
+        elseif lstart>bvar.cend
+            " TODO Footer action
+            return ''
+        endif
+        if lend<bvar.cstart
+            " TODO Header action
+            return ''
+        elseif lend>bvar.cend
+            let lend=bvar.cend
+        endif
+        let selection=range(lstart-bvar.cstart, lend-bvar.cstart)
+        " TODO Use vselection here?
+        if a:mdescr.selection isnot# 'save'
+            let postcmd="\<C-\>\<C-n>"
+        endif
+    elseif a:mode is# 'n'
+        if a:mdescr.selection isnot# 'ignore' && !empty(bvar.selected)
+            let selection=map(keys(bvar.selected), '+v:val')
+            if a:mdescr.selection isnot# 'save'
+                call map(copy(selection), 's:F.unselect(bvar, v:val)')
+            endif
+        else
+            let line=line('.')
+            if line<bvar.cstart
+                " TODO Header action
+                return ''
+            elseif line>bvar.cend
+                " TODO Footer action
+                return ''
+            endif
+            let selection=[line-bvar.cstart]
+        endif
+    endif
+    if a:mdescr.treerun isnot# 'all'
+        " TODO leaf/tree selecting
+    endif
+    if a:mdescr.passnodes
+        call map(selection, 'bvar.nodes[v:val]')
+    endif
+    if a:mdescr.multrun is# 'map' || (a:mdescr.multrun is# 'no' &&
+                \                     len(selection)==1)
+        return join(map(selection, 'a:mdescr.function(bvar,v:val)'), '').postcmd
+    elseif a:mdescr.multrun is# 'list'
+        return a:mdescr.function(bvar, selection).postcmd
+    else
+        call s:_f.throw('invrun')
+    endif
+    return postcmd
+endfunction
+"▶1 tree.add :: {f}, tid, Func, options → + s:trees, :au
+let s:tree.cons={'add': {}, 'del': {}}
+function s:tree.cons.add.function(plugdict, fdict, tid, Func, options)
+    let tree={'id': a:tid, 'plid': a:plugdict.id,
+                \'get': a:Func,
+                \'columns': copy(get(a:options, 'columns', {}))}
+    let patstart=a:tid.'tree://'
+    let tree.startlen=len(patstart)
+    let tree.pattern=patstart.escape(get(a:options, 'pattern', '*'), ' ,')
+    if has_key(a:options, 'coldefault')
+        let tree.coldefault=a:options.coldefault
+    else
+        let tree.coldefault=['name']+sort(filter(keys(tree.columns),
+                    \                            'v:val isnot# "name"'))
+    endif
+    let tree.defsortby=get(a:options, 'defsortby', 'name')
+    let tree.options=get(a:options, 'options', 0)
+    "▶2 Generate mappings
+    let tree.mgname='GTG'.a:tid
+    let tree.mappings={}
+    let mgdescr={'func': s:F.runmap, 'mode': 'nx', 'buffer': 1, 'dontmap': 1}
+    let mappings={}
+    for [mapname, mdescr] in items(extend(copy(s:defmappings),
+                \                         get(a:options, 'mappings', {})))
+        let tree.mappings[mapname]={'function': mdescr.function,
+                    \                'multrun': get(mdescr,'multrun',  'map'  ),
+                    \                'treerun': get(mdescr,'treerun',  'no'   ),
+                    \              'passnodes': get(mdescr,'passnodes',1      ),
+                    \              'selection': get(mdescr,'selection','clear'),
+                    \}
+        " TODO operator mappings
+        " TODO non-expr mappings
+        let mappings[mapname]={'lhs': get(mdescr, 'lhs', 0),
+                    \          'rhs': [tree, tree.mappings[mapname], '%mode'],}
+        if get(mdescr, 'multrun', 0) is# 'no'
+            let mappings[mapname].mode='n'
+        endif
+    endfor
+    call s:_f.mapgroup.add(tree.mgname, mappings, mgdescr)
+    "▲2
+    augroup Gtregen
+        execute 'autocmd BufReadCmd '.tree.pattern.' '.
+                    \'call s:F.setup('.string(a:tid).')'
+    augroup END
+    let tree.buffers=[]
+    lockvar! tree
+    unlockvar tree.buffers
+    let a:fdict[a:tid]=tree
+    let s:trees[a:tid]=tree
+endfunction
+let s:aligns=['left', 'right', 'center']
+let s:optcheck='dict {columns   list type ""'.
+            \       ' colopts   dict {- dict {align  (in aligns)'.
+            \                               ' long   (in [truncate wrap '.
+            \                                            'ignore])'.
+            \                               ' minw   range 0 inf'.
+            \                               ' title  type ""'.
+            \                               ' talign (in aligns)'.
+            \                               ' cmp    (|isfunc)'.
+            \                               '}}'.
+            \       ' treechars (type ""  match /\v^%(%(\t)@!\p){16}$/ '.
+            \                   '?=(@%@.p._r.strdisplaywidth(@.@)==16))'.
+            \       ' header    type ""'.
+            \       ' footer    type ""'.
+            \       ' sortby    either is =(0), type ""'.
+            \       '}'
+let s:tree.cons.add['@FWC']=
+            \['_ _ (match @\v^[a-zA-Z0-9]+$@ #treeex(.) not key trees) '.
+            \     'isfunc '.
+            \     'dict {columns '.
+            \               '(dict {?match /\v^\w+$/  (|isfunc)}) '.
+            \           'coldefault  (value @^ haskey columns '.
+            \                        'list either is "name", key @^^.columns)'.
+            \           'defsortby   (value @^ haskey columns '.
+            \                        'either is "name", key @^.columns)'.
+            \           'write       (|isfunc) '.
+            \           'pattern     type "" '.
+            \           'options     either ((|isfunc), '.
+            \                                s:optcheck.') '.
+            \           'mappings    dict {'.
+            \                         '?match /\v^\a\w*$/ '.
+            \                               '(haskey function '.
+            \                                'dict {'.
+            \                                  ' function  (|isfunc)'.
+            \                                  ' multrun   in [map list no]'.
+            \                                  ' treerun   in [leafs trees all'.
+            \                                                ' no]'.
+            \                                  ' lhs       type ""'.
+            \                                  ' selection in [ignore clear'.
+            \                                                ' save]'.
+            \                                '})'.
+            \                       '} '.
+            \          '}',
+            \ 'filter']
+let s:tree.cons.add=s:_f.wrapfunc(s:tree.cons.add)
+let s:_augroups+=['Gtregen']
+"▶1 tree.del :: {f}, tid → + s:trees, :au
+function s:tree.cons.del.function(plugdict, fdict, tid)
+    let tree=a:fdict[a:tid]
+    augroup Gtregen
+        execute 'autocmd! BufReadCmd' tree.pattern
+    augroup END
+    for buf in tree.buffers
+        " TODO
+    endfor
+    unlet a:fdict[a:tid]
+    unlet s:trees[a:tid]
+endfunction
+let s:tree.cons.del['@FWC']=['_ _ key @<', 'check']
+let s:tree.cons.del=s:_f.wrapfunc(s:tree.cons.del)
+"▶1 Register feature
+call s:_f.newfeature('tree', s:tree)
+"▶1 defcmp :: str, str → -1|0|1
+function s:F.defcmp(a, b)
+    return ((a:a is# a:b)?(0):((a:a>#a:b)?(1):(-1)))
+endfunction
+"▶1 sorter :: node, node → -1|0|1
+function s:F.sorter(...)
+    let sb=self.sortby
+    let tree=self.tree
+    return call(self.cmp, map(copy(a:000),
+                \             '((has_key(v:val.columns, '''.sb.'''))?'.
+                \                   '(v:val.columns.'.sb.'):'.
+                \                   '(call(tree.columns.'.sb.', [v:val], {})'.
+                \                   '))'), self)
+endfunction
+"▶1 sortcols :: tree, [node], options → [node] + [node]
+function s:F.sortcols(tree, nodes, options)
+    if empty(a:options.sortby)
+        return a:nodes
+    endif
+    let d  =  {'sortby': a:options.sortby,
+                \ 'cmp': get(a:options.colopts[a:options.sortby], 'cmp',
+                \           s:F.defcmp),
+                \'tree': a:tree}
+    " TODO workaround for older vims not supporting sort(..., dict)
+    return sort(a:nodes, s:F.sorter, d)
+endfunction
+"▶1 gencolumns :: tree, tdata, options, level → [node:Number:[String]]
+function s:F.gencolumns(tree, tdata, options, level)
+    let columns=copy(a:options.columns)
+    let r=[[a:tdata, a:level]+
+                \map(copy(columns),
+                \    '((has_key(a:tdata.columns, v:val))?'.
+                \           '(a:tdata.columns[v:val]):'.
+                \           '(call(a:tree.columns[v:val], [a:tdata],{})))')]
+    if has_key(a:tdata, 'nodes')
+        if type(a:tdata.nodes)==2
+            let a:tdata.nodes_generator=a:tdata.nodes
+            let a:tdata.nodes=a:tdata.nodes(a:tdata)
+        elseif a:tdata.nodes is# 'get'
+            let a:tdata.nodes_generator=a:tdata.nodes
+            call extend(a:tdata, a:tree.get(join(a:tdata.path, '/')))
+        endif
+        call map(a:tdata.nodes, 's:F.proctdata(a:tree, v:val)')
+        call s:F.sortcols(a:tree, a:tdata.nodes, a:options)
+        for node in a:tdata.nodes
+            let r+=s:F.gencolumns(a:tree, node, a:options, a:level+1)
+        endfor
+    endif
+    return r
+endfunction
+"▶1 proctdata :: tdata → tdata
+function s:F.proctdata(tree, tdata)
+    if has_key(a:tdata, 'processed')
+        return a:tdata
+    endif
+    if !has_key(a:tdata, 'columns')
+        let a:tdata.columns={}
+    endif
+    if !has_key(a:tdata.columns, 'name') && !has_key(a:tree.columns, 'name')
+        let a:tdata.columns.name=a:tdata.path[-1]
+    endif
+    let a:tdata.processed=1
+    return a:tdata
+endfunction
+"▶1 align :: String, alignment, width → String
+function s:F.align(text, alignment, textwidth, width)
+    let add=a:width-a:textwidth
+    let r=a:text
+    if add
+        if a:alignment is# 'left'
+            let r.=repeat(' ', add)
+        elseif a:alignment is# 'right'
+            let r=repeat(' ', add).r
+        elseif a:alignment is# 'center'
+            let r=repeat(' ', add/2).r.repeat(' ', add/2+add%2)
+        endif
+    endif
+    return r
+endfunction
+"▶1 hasbrother :: collevels, i → Bool
+function s:F.hasbrother(cls, i)
+    let cl=a:cls[a:i]
+    for l in a:cls[(a:i+1):]
+        if l<cl
+            return 0
+        elseif l==cl
+            return 1
+        endif
+    endfor
+    return 0
+endfunction
+"▶1 firstchars :: columns → [String]
+" {down}|({up}<<1)|({right}<<2)|({left}<<3)
+function s:F.firstchars(columns, chars)
+    " TODO add additional character before every node
+    let collevels=map(copy(a:columns), 'v:val[0]')
+    let r=[]
+    let i=0
+    let hasbrothers=[0, 0]
+    let prevlevel=0
+    for level in collevels
+        let s=''
+        if level==0
+        elseif level==1
+            let s=a:chars[(len(collevels)>1)+4]
+        else
+            let nextlevel=get(collevels, i+1, 0)
+            let hasbrother=s:F.hasbrother(collevels, i)
+            let hasobrother=get(hasbrothers, level, 0)
+            if len(hasbrothers)-1>=level
+                call remove(hasbrothers, level, -1)
+            endif
+            call add(hasbrothers, hasbrother)
+            let up=(level<prevlevel)
+            let haschild=(level<nextlevel)
+            let numbars=(up?(level):(prevlevel))-1
+            let s.=join(map(range(1, numbars),
+                        \   'a:chars[3*hasbrothers[v:val]]'), '')
+            if up
+                let s.=a:chars[(hasbrother||haschild)+2*hasobrother+4]
+            elseif level>prevlevel
+                let s.=a:chars[hasbrothers[level-1]+2+4]
+                let s.=a:chars[(hasbrother||haschild)+4+8]
+            else
+                let s.=a:chars[(hasbrother||haschild)+2+4]
+            endif
+        endif
+        let r+=[s]
+        let i+=1
+        let prevlevel=level
+    endfor
+    return r
+endfunction
+"▶1 addsyncol :: clustername, colname, colstart, colwidth, islast → + :syn
+function s:F.addsyncol(clustername, colname, colstart, colwidth, islast)
+    let synname=a:clustername.'_'.a:colname
+    if a:islast
+        let endreg='$'
+    else
+        let endreg='%'.(a:colstart+1+a:colwidth).'v'
+    endif
+    execute 'syntax region '.synname.' '.
+                \         'start=/\v%'.(a:colstart+1).'v/ '.
+                \         'end=/\v'.endreg.'/ '.
+                \         'contained'
+    execute 'syntax cluster '.a:clustername.' add='.synname
+endfunction
+"▶1 setbuf :: bvar → + bvar, buffer
+function s:F.setbuf(bvar)
+    let options=a:bvar.options
+    let tree=a:bvar.tree
+    let tdata=a:bvar.tdata
+    let columns=s:F.gencolumns(tree, tdata, options, 1)
+    let a:bvar.nodes=map(copy(columns), 'remove(v:val, 0)')
+    "▶2 Get column titles
+    let hastitles=0
+    let titles=map(copy(options.columns),
+                \  'get(options.colopts[v:val], "title", "")')
+    if !empty(filter(copy(titles), '!empty(v:val)'))
+        call insert(columns, [0]+titles)
+        let hastitles=1
+    endif
+    "▶2 Get column widths
+    let widths=[]
+    let colnum=len(columns[0])
+    let i=1
+    let colwidths=[]
+    let totalwidth=0
+    let colstarts=[]
+    while i<colnum
+        let expr='s:_r.strdisplaywidth(v:val['.i.'], '.totalwidth
+        if i==1
+            let expr.='+v:val[0]'
+        endif
+        let expr.=')'
+        if i==1
+            let expr.='+v:val[0]'
+        endif
+        let widths+=[map(copy(columns), expr)]
+        let colwidths+=[max(widths[-1])]
+        let colstarts+=[totalwidth]
+        let totalwidth+=colwidths[-1]+1
+        let i+=1
+    endwhile
+    let totalwidth-=1
+    "▶2 TODO Wrap/truncate
+    if totalwidth>winwidth(0)
+    endif
+    "▶2 Get titles line
+    if hastitles
+        let hwidths=map(copy(widths), 'remove(v:val, 0)')
+        call remove(columns, 0)
+        let hline=join(map(copy(titles),
+                    \      's:F.align(v:val, get(options.colopts['.
+                    \                               'options.columns[v:key]], '.
+                    \                           '"talign", "left"), '.
+                    \                'hwidths[v:key], colwidths[v:key])'))
+    endif
+    "▶2 Get other lines
+    " TODO Non-tree style
+    let firstchars=s:F.firstchars(columns, options.treechars)
+    let lines=map(copy(columns),
+                \ 'firstchars[v:key].'.
+                \ 's:F.align(v:val[1], get(options.colopts['.
+                \                           'options.columns[0]], '.
+                \                         '"align", "left"), '.
+                \                         'widths[0][v:key], '.
+                \                         'colwidths[0]).'.
+                \ 'join(map(v:val[2:], '.
+                \          '"\" \".s:F.align(v:val, '.
+                \                           'get(options.colopts['.
+                \                                 'options.columns[v:key+1]], '.
+                \                               '\"align\", \"left\"), '.
+                \                               'widths[v:key+1][".v:key."], '.
+                \                               'colwidths[v:key+1])"), "")')
+    "▶2 Populate buffer variable with regions borders
+    let a:bvar.hastitles=hastitles
+    let a:bvar.cstart=1
+    let a:bvar.cend=len(lines)
+    let a:bvar.colwidths=colwidths
+    let a:bvar.colstarts=colstarts
+    "▶2 Header, footer and titles
+    if hastitles
+        call insert(lines, hline)
+        let a:bvar.cstart+=1
+        let a:bvar.cend+=1
+    endif
+    if has_key(options, 'header')
+        let header=split(options.header, "\n", 1)
+        call extend(lines, header, 0)
+        let a:bvar.hlen=len(header)
+        let a:bvar.cstart+=a:bvar.hlen
+        let a:bvar.cend+=a:bvar.hlen
+    endif
+    if has_key(options, 'footer')
+        let footer=split(options.footer, "\n", 1)
+        call extend(lines, footer)
+        let a:bvar.flen=len(footer)
+    endif
+    "▶2 Create syntax
+    if has_key(a:bvar, 'hlen')
+        execute 'syntax region TreeHeader start=/\v%1l^/ '.
+                    \                    'end=/\v%'.a:bvar.hlen.'l$/'
+    endif
+    if has_key(a:bvar, 'flen')
+        let fend=(a:bvar.cend+a:bvar.flen)
+        execute 'syntax region TreeFooter start=/\v%'.(a:bvar.cend+1).'l^/ '.
+                    \                    'end=/\v%'.fend.'l$/'
+    endif
+    if a:bvar.hastitles
+        execute 'syntax match TreeTitles /\v%'.(a:bvar.cstart-1).'l^.*/ '.
+                    \                   'contains=@TreeTitle'
+    endif
+    execute 'syntax match TreeData /\v%>'.(a:bvar.cstart-1).'l'.
+                \                    '%<'.(a:bvar.cend+1).'l^.*/ '.
+                \                 'contains=@TreeColumn'
+    let i=0
+    for column in a:bvar.options.columns
+        let islast=(i==len(a:bvar.options.columns)-1)
+        if a:bvar.hastitles
+            call s:F.addsyncol('TreeTitle', column, a:bvar.colstarts[i],
+                        \      a:bvar.colwidths[i], islast)
+        endif
+        call s:F.addsyncol('TreeColumn', column, a:bvar.colstarts[i],
+                    \      a:bvar.colwidths[i], islast)
+        let i+=1
+    endfor
+    " TODO Make this syntax highlighting merge with existing
+    syntax match TreeSelected /.*/ contained
+    "▲2
+    setlocal modifiable noreadonly
+    call setline('.', lines)
+    setlocal nomodified nomodifiable readonly
+    let a:bvar.selected={}
+    return a:bvar
+endfunction
+"▶1 setup :: tid → + buffer
+let s:uchars  = split(' ╷╵│╶┌└├╴┐┘┤─┬┴┼', '\v.@=')
+let s:nuchars = split(' |||-+`+-+++-+++', '\v.@=')
+function s:F.setup(tid)
+    let tree=s:trees[a:tid]
+    let path=expand('<amatch>')[(tree.startlen):]
+    let buf=+expand('<abuf>')
+    " tdata :: {path : [String],
+    "          nodes : {name : tdata},
+    "        columns : {colname : col}
+    "          [nodes_sorted : Either [tdata] Fref,]
+    "          [nodes_generator : Fref,]}
+    let tdata=tree.get(path)
+    if !has_key(tdata, 'path')
+        let tdata.path=[path]
+    endif
+    call s:F.proctdata(tree, tdata)
+    "▶2 Get options
+    if type(tree.options)==2
+        let options=deepcopy(tree.options(path))
+    elseif type(tree.options)==type({})
+        let options=deepcopy(tree.options)
+    else
+        let options={}
+    endif
+    if has_key(options, 'treechars')
+        let options.treechars=split(options.treechars, '\v.@=')
+    else
+        let options.treechars=((&encoding is# 'utf-8')?(s:uchars):
+                    \                                  (s:nuchars))
+    endif
+    if !has_key(options, 'sortby')
+        let options.sortby=tree.defsortby
+    endif
+    if !has_key(options, 'columns')
+        let options.columns=tree.coldefault
+    endif
+    if !has_key(options, 'colopts')
+        let options.colopts={}
+    endif
+    for col in filter(copy(options.columns), '!has_key(options.colopts, v:val)')
+        let options.colopts[col]={}
+    endfor
+    "▲2
+    let bvar=s:F.setbuf({'tree': tree, 'tdata': tdata, 'options': options})
+    let s:_r.bufvars[buf]=bvar
+    let tree.buffers+=[buf]
+    if has_key(tree, 'write')
+        augroup Gtregen
+            " FIXME Run this commmand only for {tid}:// targets
+            execute 'autocmd! BufWriteCmd <buffer> '.
+                        \':call s:trees['.string(tree.id).'].write()'
+        augroup END
+        setlocal buftype=acwrite
+    else
+        setlocal buftype=nofile
+    endif
+    augroup Gtregen
+        autocmd! InsertEnter <buffer> :call feedkeys("\<C-\>\<C-n>")
+    augroup END
+    call s:_f.mapgroup.map(tree.mgname, buf)
+endfunction
+"▶1
+call frawor#Lockvar(s:, '_r,trees')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

File autoload/gtregen/bufvars.vim

+"▶1 
+scriptencoding utf-8
+execute frawor#Setup('0.0', {'@/resources': '0.0',}, 0)
+let s:bufvars={}
+"▶1 bufwipeout
+function s:F.bufwipeout()
+    let buf=+expand('<abuf>')
+    if has_key(s:bufvars, buf)
+        call filter(s:bufvars[buf].tree.buffers, 'v:val=='.buf)
+        unlet s:bufvars[buf]
+    endif
+endfunction
+augroup GtregenBufVars
+    autocmd BufWipeOut * :call s:F.bufwipeout()
+augroup END
+let s:_augroups+=['GtregenBufVars']
+"▶1
+call s:_f.postresource('bufvars', s:bufvars, 1)
+"▶1
+call frawor#Lockvar(s:, 'bufvars')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

File gtregen-addon-info.txt

         }
     },
     "files": [
-        "macros/fstree.vim",
-        "plugin/gtregen.vim",
-        "plugin/gtregen/bufvars.vim"
+        "autoload/gtregen.vim",
+        "autoload/gtregen/bufvars.vim",
+        "macros/fstree.vim"
     ],
 }

File plugin/gtregen.vim

-"▶1
-scriptencoding utf-8
-if !exists('s:_pluginloaded')
-    execute frawor#Setup('0.0', {'@/fwc': '0.0',
-                \          '@/functions': '0.0',
-                \           '@/mappings': '0.0',
-                \              '@/table': '0.0',
-                \     '@gtregen/bufvars': '0.0',}, 0)
-    finish
-elseif s:_pluginloaded
-    finish
-endif
-let s:_messages={
-            \'treeex': 'Tree %s already exsists',
-            \'invrun': 'Failed to run mapping',
-        \}
-let s:tree={}
-let s:trees={}
-"▶1 Default mappings
-let s:defmappings={}
-"▶2 (un)selection
-let s:defmappings._select={'lhs': 's', 'selection': 'ignore', 'passnodes': 0}
-function s:defmappings._select.function(bvar, lnr)
-    let synname='TreeSelected_'.a:lnr
-    let a:bvar.selected[a:lnr]=synname
-    let blnr=a:lnr+a:bvar.cstart
-    execute 'syntax region '.synname.' start=/\v^%'.blnr.'l/'.
-                \                    ' end=/\v%'.blnr.'l$/'.
-                \                    ' contains=TreeSelected'.
-                \                    ' contained containedin=TreeData'
-    return ''
-endfunction
-let s:defmappings._unselect={'lhs': 'S', 'selection': 'ignore', 'passnodes': 0}
-function s:F.unselect(bvar, lnr)
-    if has_key(a:bvar.selected, a:lnr)
-        let synname=remove(a:bvar.selected, a:lnr)
-        execute 'syntax clear '.synname
-    endif
-    return ''
-endfunction
-let s:defmappings._unselect.function=s:F.unselect
-"▶1 runmap :: tree, mdescr
-function s:F.runmap(tree, mdescr, mode)
-    let bvar=s:_r.bufvars[bufnr('%')]
-    let postcmd=''
-    if a:mode is# 'x'
-        let lstart=line("'<")
-        let lend=line("'>")
-        if lstart>lend
-            let [lstart, lend]=[lend, lstart]
-        endif
-        if lstart<bvar.cstart
-            let lstart=bvar.cstart
-        elseif lstart>bvar.cend
-            " TODO Footer action
-            return ''
-        endif
-        if lend<bvar.cstart
-            " TODO Header action
-            return ''
-        elseif lend>bvar.cend
-            let lend=bvar.cend
-        endif
-        let selection=range(lstart-bvar.cstart, lend-bvar.cstart)
-        " TODO Use vselection here?
-        if a:mdescr.selection isnot# 'save'
-            let postcmd="\<C-\>\<C-n>"
-        endif
-    elseif a:mode is# 'n'
-        if a:mdescr.selection isnot# 'ignore' && !empty(bvar.selected)
-            let selection=map(keys(bvar.selected), '+v:val')
-            if a:mdescr.selection isnot# 'save'
-                call map(copy(selection), 's:F.unselect(bvar, v:val)')
-            endif
-        else
-            let line=line('.')
-            if line<bvar.cstart
-                " TODO Header action
-                return ''
-            elseif line>bvar.cend
-                " TODO Footer action
-                return ''
-            endif
-            let selection=[line-bvar.cstart]
-        endif
-    endif
-    if a:mdescr.treerun isnot# 'all'
-        " TODO leaf/tree selecting
-    endif
-    if a:mdescr.passnodes
-        call map(selection, 'bvar.nodes[v:val]')
-    endif
-    if a:mdescr.multrun is# 'map' || (a:mdescr.multrun is# 'no' &&
-                \                     len(selection)==1)
-        return join(map(selection, 'a:mdescr.function(bvar,v:val)'), '').postcmd
-    elseif a:mdescr.multrun is# 'list'
-        return a:mdescr.function(bvar, selection).postcmd
-    else
-        call s:_f.throw('invrun')
-    endif
-    return postcmd
-endfunction
-"▶1 tree.add :: {f}, tid, Func, options → + s:trees, :au
-let s:tree.cons={'add': {}, 'del': {}}
-function s:tree.cons.add.function(plugdict, fdict, tid, Func, options)
-    let tree={'id': a:tid, 'plid': a:plugdict.id,
-                \'get': a:Func,
-                \'columns': copy(get(a:options, 'columns', {}))}
-    let patstart=a:tid.'tree://'
-    let tree.startlen=len(patstart)
-    let tree.pattern=patstart.escape(get(a:options, 'pattern', '*'), ' ,')
-    if has_key(a:options, 'coldefault')
-        let tree.coldefault=a:options.coldefault
-    else
-        let tree.coldefault=['name']+sort(filter(keys(tree.columns),
-                    \                            'v:val isnot# "name"'))
-    endif
-    let tree.defsortby=get(a:options, 'defsortby', 'name')
-    let tree.options=get(a:options, 'options', 0)
-    "▶2 Generate mappings
-    let tree.mgname='GTG'.a:tid
-    let tree.mappings={}
-    let mgdescr={'func': s:F.runmap, 'mode': 'nx', 'buffer': 1, 'dontmap': 1}
-    let mappings={}
-    for [mapname, mdescr] in items(extend(copy(s:defmappings),
-                \                         get(a:options, 'mappings', {})))
-        let tree.mappings[mapname]={'function': mdescr.function,
-                    \                'multrun': get(mdescr,'multrun',  'map'  ),
-                    \                'treerun': get(mdescr,'treerun',  'no'   ),
-                    \              'passnodes': get(mdescr,'passnodes',1      ),
-                    \              'selection': get(mdescr,'selection','clear'),
-                    \}
-        " TODO operator mappings
-        " TODO non-expr mappings
-        let mappings[mapname]={'lhs': get(mdescr, 'lhs', 0),
-                    \          'rhs': [tree, tree.mappings[mapname], '%mode'],}
-        if get(mdescr, 'multrun', 0) is# 'no'
-            let mappings[mapname].mode='n'
-        endif
-    endfor
-    call s:_f.mapgroup.add(tree.mgname, mappings, mgdescr)
-    "▲2
-    augroup Gtregen
-        execute 'autocmd BufReadCmd '.tree.pattern.' '.
-                    \'call s:F.setup('.string(a:tid).')'
-    augroup END
-    let tree.buffers=[]
-    lockvar! tree
-    unlockvar tree.buffers
-    let a:fdict[a:tid]=tree
-    let s:trees[a:tid]=tree
-endfunction
-let s:aligns=['left', 'right', 'center']
-let s:optcheck='dict {columns   list type ""'.
-            \       ' colopts   dict {- dict {align  (in aligns)'.
-            \                               ' long   (in [truncate wrap '.
-            \                                            'ignore])'.
-            \                               ' minw   range 0 inf'.
-            \                               ' title  type ""'.
-            \                               ' talign (in aligns)'.
-            \                               ' cmp    (|isfunc)'.
-            \                               '}}'.
-            \       ' treechars (type ""  match /\v^%(%(\t)@!\p){16}$/ '.
-            \                   '?=(@%@.p._r.strdisplaywidth(@.@)==16))'.
-            \       ' header    type ""'.
-            \       ' footer    type ""'.
-            \       ' sortby    either is =(0), type ""'.
-            \       '}'
-let s:tree.cons.add['@FWC']=
-            \['_ _ (match @\v^[a-zA-Z0-9]+$@ #treeex(.) not key trees) '.
-            \     'isfunc '.
-            \     'dict {columns '.
-            \               '(dict {?match /\v^\w+$/  (|isfunc)}) '.
-            \           'coldefault  (value @^ haskey columns '.
-            \                        'list either is "name", key @^^.columns)'.
-            \           'defsortby   (value @^ haskey columns '.
-            \                        'either is "name", key @^.columns)'.
-            \           'write       (|isfunc) '.
-            \           'pattern     type "" '.
-            \           'options     either ((|isfunc), '.
-            \                                s:optcheck.') '.
-            \           'mappings    dict {'.
-            \                         '?match /\v^\a\w*$/ '.
-            \                               '(haskey function '.
-            \                                'dict {'.
-            \                                  ' function  (|isfunc)'.
-            \                                  ' multrun   in [map list no]'.
-            \                                  ' treerun   in [leafs trees all'.
-            \                                                ' no]'.
-            \                                  ' lhs       type ""'.
-            \                                  ' selection in [ignore clear'.
-            \                                                ' save]'.
-            \                                '})'.
-            \                       '} '.
-            \          '}',
-            \ 'filter']
-let s:tree.cons.add=s:_f.wrapfunc(s:tree.cons.add)
-let s:_augroups+=['Gtregen']
-"▶1 tree.del :: {f}, tid → + s:trees, :au
-function s:tree.cons.del.function(plugdict, fdict, tid)
-    let tree=a:fdict[a:tid]
-    augroup Gtregen
-        execute 'autocmd! BufReadCmd' tree.pattern
-    augroup END
-    for buf in tree.buffers
-        " TODO
-    endfor
-    unlet a:fdict[a:tid]
-    unlet s:trees[a:tid]
-endfunction
-let s:tree.cons.del['@FWC']=['_ _ key @<', 'check']
-let s:tree.cons.del=s:_f.wrapfunc(s:tree.cons.del)
-"▶1 Register feature
-call s:_f.newfeature('tree', s:tree)
-"▶1 defcmp :: str, str → -1|0|1
-function s:F.defcmp(a, b)
-    return ((a:a is# a:b)?(0):((a:a>#a:b)?(1):(-1)))
-endfunction
-"▶1 sorter :: node, node → -1|0|1
-function s:F.sorter(...)
-    let sb=self.sortby
-    let tree=self.tree
-    return call(self.cmp, map(copy(a:000),
-                \             '((has_key(v:val.columns, '''.sb.'''))?'.
-                \                   '(v:val.columns.'.sb.'):'.
-                \                   '(call(tree.columns.'.sb.', [v:val], {})'.
-                \                   '))'), self)
-endfunction
-"▶1 sortcols :: tree, [node], options → [node] + [node]
-function s:F.sortcols(tree, nodes, options)
-    if empty(a:options.sortby)
-        return a:nodes
-    endif
-    let d  =  {'sortby': a:options.sortby,
-                \ 'cmp': get(a:options.colopts[a:options.sortby], 'cmp',
-                \           s:F.defcmp),
-                \'tree': a:tree}
-    " TODO workaround for older vims not supporting sort(..., dict)
-    return sort(a:nodes, s:F.sorter, d)
-endfunction
-"▶1 gencolumns :: tree, tdata, options, level → [node:Number:[String]]
-function s:F.gencolumns(tree, tdata, options, level)
-    let columns=copy(a:options.columns)
-    let r=[[a:tdata, a:level]+
-                \map(copy(columns),
-                \    '((has_key(a:tdata.columns, v:val))?'.
-                \           '(a:tdata.columns[v:val]):'.
-                \           '(call(a:tree.columns[v:val], [a:tdata],{})))')]
-    if has_key(a:tdata, 'nodes')
-        if type(a:tdata.nodes)==2
-            let a:tdata.nodes_generator=a:tdata.nodes
-            let a:tdata.nodes=a:tdata.nodes(a:tdata)
-        elseif a:tdata.nodes is# 'get'
-            let a:tdata.nodes_generator=a:tdata.nodes
-            call extend(a:tdata, a:tree.get(join(a:tdata.path, '/')))
-        endif
-        call map(a:tdata.nodes, 's:F.proctdata(a:tree, v:val)')
-        call s:F.sortcols(a:tree, a:tdata.nodes, a:options)
-        for node in a:tdata.nodes
-            let r+=s:F.gencolumns(a:tree, node, a:options, a:level+1)
-        endfor
-    endif
-    return r
-endfunction
-"▶1 proctdata :: tdata → tdata
-function s:F.proctdata(tree, tdata)
-    if has_key(a:tdata, 'processed')
-        return a:tdata
-    endif
-    if !has_key(a:tdata, 'columns')
-        let a:tdata.columns={}
-    endif
-    if !has_key(a:tdata.columns, 'name') && !has_key(a:tree.columns, 'name')
-        let a:tdata.columns.name=a:tdata.path[-1]
-    endif
-    let a:tdata.processed=1
-    return a:tdata
-endfunction
-"▶1 align :: String, alignment, width → String
-function s:F.align(text, alignment, textwidth, width)
-    let add=a:width-a:textwidth
-    let r=a:text
-    if add
-        if a:alignment is# 'left'
-            let r.=repeat(' ', add)
-        elseif a:alignment is# 'right'
-            let r=repeat(' ', add).r
-        elseif a:alignment is# 'center'
-            let r=repeat(' ', add/2).r.repeat(' ', add/2+add%2)
-        endif
-    endif
-    return r
-endfunction
-"▶1 hasbrother :: collevels, i → Bool
-function s:F.hasbrother(cls, i)
-    let cl=a:cls[a:i]
-    for l in a:cls[(a:i+1):]
-        if l<cl
-            return 0
-        elseif l==cl
-            return 1
-        endif
-    endfor
-    return 0
-endfunction
-"▶1 firstchars :: columns → [String]
-" {down}|({up}<<1)|({right}<<2)|({left}<<3)
-function s:F.firstchars(columns, chars)
-    " TODO add additional character before every node
-    let collevels=map(copy(a:columns), 'v:val[0]')
-    let r=[]
-    let i=0
-    let hasbrothers=[0, 0]
-    let prevlevel=0
-    for level in collevels
-        let s=''
-        if level==0
-        elseif level==1
-            let s=a:chars[(len(collevels)>1)+4]
-        else
-            let nextlevel=get(collevels, i+1, 0)
-            let hasbrother=s:F.hasbrother(collevels, i)
-            let hasobrother=get(hasbrothers, level, 0)
-            if len(hasbrothers)-1>=level
-                call remove(hasbrothers, level, -1)
-            endif
-            call add(hasbrothers, hasbrother)
-            let up=(level<prevlevel)
-            let haschild=(level<nextlevel)
-            let numbars=(up?(level):(prevlevel))-1
-            let s.=join(map(range(1, numbars),
-                        \   'a:chars[3*hasbrothers[v:val]]'), '')
-            if up
-                let s.=a:chars[(hasbrother||haschild)+2*hasobrother+4]
-            elseif level>prevlevel
-                let s.=a:chars[hasbrothers[level-1]+2+4]
-                let s.=a:chars[(hasbrother||haschild)+4+8]
-            else
-                let s.=a:chars[(hasbrother||haschild)+2+4]
-            endif
-        endif
-        let r+=[s]
-        let i+=1
-        let prevlevel=level
-    endfor
-    return r
-endfunction
-"▶1 addsyncol :: clustername, colname, colstart, colwidth, islast → + :syn
-function s:F.addsyncol(clustername, colname, colstart, colwidth, islast)
-    let synname=a:clustername.'_'.a:colname
-    if a:islast
-        let endreg='$'
-    else
-        let endreg='%'.(a:colstart+1+a:colwidth).'v'
-    endif
-    execute 'syntax region '.synname.' '.
-                \         'start=/\v%'.(a:colstart+1).'v/ '.
-                \         'end=/\v'.endreg.'/ '.
-                \         'contained'
-    execute 'syntax cluster '.a:clustername.' add='.synname
-endfunction
-"▶1 setbuf :: bvar → + bvar, buffer
-function s:F.setbuf(bvar)
-    let options=a:bvar.options
-    let tree=a:bvar.tree
-    let tdata=a:bvar.tdata
-    let columns=s:F.gencolumns(tree, tdata, options, 1)
-    let a:bvar.nodes=map(copy(columns), 'remove(v:val, 0)')
-    "▶2 Get column titles
-    let hastitles=0
-    let titles=map(copy(options.columns),
-                \  'get(options.colopts[v:val], "title", "")')
-    if !empty(filter(copy(titles), '!empty(v:val)'))
-        call insert(columns, [0]+titles)
-        let hastitles=1
-    endif
-    "▶2 Get column widths
-    let widths=[]
-    let colnum=len(columns[0])
-    let i=1
-    let colwidths=[]
-    let totalwidth=0
-    let colstarts=[]
-    while i<colnum
-        let expr='s:_r.strdisplaywidth(v:val['.i.'], '.totalwidth
-        if i==1
-            let expr.='+v:val[0]'
-        endif
-        let expr.=')'
-        if i==1
-            let expr.='+v:val[0]'
-        endif
-        let widths+=[map(copy(columns), expr)]
-        let colwidths+=[max(widths[-1])]
-        let colstarts+=[totalwidth]
-        let totalwidth+=colwidths[-1]+1
-        let i+=1
-    endwhile
-    let totalwidth-=1
-    "▶2 TODO Wrap/truncate
-    if totalwidth>winwidth(0)
-    endif
-    "▶2 Get titles line
-    if hastitles
-        let hwidths=map(copy(widths), 'remove(v:val, 0)')
-        call remove(columns, 0)
-        let hline=join(map(copy(titles),
-                    \      's:F.align(v:val, get(options.colopts['.
-                    \                               'options.columns[v:key]], '.
-                    \                           '"talign", "left"), '.
-                    \                'hwidths[v:key], colwidths[v:key])'))
-    endif
-    "▶2 Get other lines
-    " TODO Non-tree style
-    let firstchars=s:F.firstchars(columns, options.treechars)
-    let lines=map(copy(columns),
-                \ 'firstchars[v:key].'.
-                \ 's:F.align(v:val[1], get(options.colopts['.
-                \                           'options.columns[0]], '.
-                \                         '"align", "left"), '.
-                \                         'widths[0][v:key], '.
-                \                         'colwidths[0]).'.
-                \ 'join(map(v:val[2:], '.
-                \          '"\" \".s:F.align(v:val, '.
-                \                           'get(options.colopts['.
-                \                                 'options.columns[v:key+1]], '.
-                \                               '\"align\", \"left\"), '.
-                \                               'widths[v:key+1][".v:key."], '.
-                \                               'colwidths[v:key+1])"), "")')
-    "▶2 Populate buffer variable with regions borders
-    let a:bvar.hastitles=hastitles
-    let a:bvar.cstart=1
-    let a:bvar.cend=len(lines)
-    let a:bvar.colwidths=colwidths
-    let a:bvar.colstarts=colstarts
-    "▶2 Header, footer and titles
-    if hastitles
-        call insert(lines, hline)
-        let a:bvar.cstart+=1
-        let a:bvar.cend+=1
-    endif
-    if has_key(options, 'header')
-        let header=split(options.header, "\n", 1)
-        call extend(lines, header, 0)
-        let a:bvar.hlen=len(header)
-        let a:bvar.cstart+=a:bvar.hlen
-        let a:bvar.cend+=a:bvar.hlen
-    endif
-    if has_key(options, 'footer')
-        let footer=split(options.footer, "\n", 1)
-        call extend(lines, footer)
-        let a:bvar.flen=len(footer)
-    endif
-    "▶2 Create syntax
-    if has_key(a:bvar, 'hlen')
-        execute 'syntax region TreeHeader start=/\v%1l^/ '.
-                    \                    'end=/\v%'.a:bvar.hlen.'l$/'
-    endif
-    if has_key(a:bvar, 'flen')
-        let fend=(a:bvar.cend+a:bvar.flen)
-        execute 'syntax region TreeFooter start=/\v%'.(a:bvar.cend+1).'l^/ '.
-                    \                    'end=/\v%'.fend.'l$/'
-    endif
-    if a:bvar.hastitles
-        execute 'syntax match TreeTitles /\v%'.(a:bvar.cstart-1).'l^.*/ '.
-                    \                   'contains=@TreeTitle'
-    endif
-    execute 'syntax match TreeData /\v%>'.(a:bvar.cstart-1).'l'.
-                \                    '%<'.(a:bvar.cend+1).'l^.*/ '.
-                \                 'contains=@TreeColumn'
-    let i=0
-    for column in a:bvar.options.columns
-        let islast=(i==len(a:bvar.options.columns)-1)
-        if a:bvar.hastitles
-            call s:F.addsyncol('TreeTitle', column, a:bvar.colstarts[i],
-                        \      a:bvar.colwidths[i], islast)
-        endif
-        call s:F.addsyncol('TreeColumn', column, a:bvar.colstarts[i],
-                    \      a:bvar.colwidths[i], islast)
-        let i+=1
-    endfor
-    " TODO Make this syntax highlighting merge with existing
-    syntax match TreeSelected /.*/ contained
-    "▲2
-    setlocal modifiable noreadonly
-    call setline('.', lines)
-    setlocal nomodified nomodifiable readonly
-    let a:bvar.selected={}
-    return a:bvar
-endfunction
-"▶1 setup :: tid → + buffer
-let s:uchars  = split(' ╷╵│╶┌└├╴┐┘┤─┬┴┼', '\v.@=')
-let s:nuchars = split(' |||-+`+-+++-+++', '\v.@=')
-function s:F.setup(tid)
-    let tree=s:trees[a:tid]
-    let path=expand('<amatch>')[(tree.startlen):]
-    let buf=+expand('<abuf>')
-    " tdata :: {path : [String],
-    "          nodes : {name : tdata},
-    "        columns : {colname : col}
-    "          [nodes_sorted : Either [tdata] Fref,]
-    "          [nodes_generator : Fref,]}
-    let tdata=tree.get(path)
-    if !has_key(tdata, 'path')
-        let tdata.path=[path]
-    endif
-    call s:F.proctdata(tree, tdata)
-    "▶2 Get options
-    if type(tree.options)==2
-        let options=deepcopy(tree.options(path))
-    elseif type(tree.options)==type({})
-        let options=deepcopy(tree.options)
-    else
-        let options={}
-    endif
-    if has_key(options, 'treechars')
-        let options.treechars=split(options.treechars, '\v.@=')
-    else
-        let options.treechars=((&encoding is# 'utf-8')?(s:uchars):
-                    \                                  (s:nuchars))
-    endif
-    if !has_key(options, 'sortby')
-        let options.sortby=tree.defsortby
-    endif
-    if !has_key(options, 'columns')
-        let options.columns=tree.coldefault
-    endif
-    if !has_key(options, 'colopts')
-        let options.colopts={}
-    endif
-    for col in filter(copy(options.columns), '!has_key(options.colopts, v:val)')
-        let options.colopts[col]={}
-    endfor
-    "▲2
-    let bvar=s:F.setbuf({'tree': tree, 'tdata': tdata, 'options': options})
-    let s:_r.bufvars[buf]=bvar
-    let tree.buffers+=[buf]
-    if has_key(tree, 'write')
-        augroup Gtregen
-            " FIXME Run this commmand only for {tid}:// targets
-            execute 'autocmd! BufWriteCmd <buffer> '.
-                        \':call s:trees['.string(tree.id).'].write()'
-        augroup END
-        setlocal buftype=acwrite
-    else
-        setlocal buftype=nofile
-    endif
-    augroup Gtregen
-        autocmd! InsertEnter <buffer> :call feedkeys("\<C-\>\<C-n>")
-    augroup END
-    call s:_f.mapgroup.map(tree.mgname, buf)
-endfunction
-"▶1
-call frawor#Lockvar(s:, '_pluginloaded,_r,trees')
-" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

File plugin/gtregen/bufvars.vim

-"▶1 
-scriptencoding utf-8
-if !exists('s:_pluginloaded')
-    execute frawor#Setup('0.0', {'@/resources': '0.0',}, 0)
-    finish
-elseif s:_pluginloaded
-    finish
-endif
-let s:bufvars={}
-"▶1 bufwipeout
-function s:F.bufwipeout()
-    let buf=+expand('<abuf>')
-    if has_key(s:bufvars, buf)
-        call filter(s:bufvars[buf].tree.buffers, 'v:val=='.buf)
-        unlet s:bufvars[buf]
-    endif
-endfunction
-augroup GtregenBufVars
-    autocmd BufWipeOut * :call s:F.bufwipeout()
-augroup END
-let s:_augroups+=['GtregenBufVars']
-"▶1
-call s:_f.postresource('bufvars', s:bufvars, 1)
-"▶1
-call frawor#Lockvar(s:, '_pluginloaded,bufvars')
-" vim: ft=vim ts=4 sts=4 et fmr=▶,▲