Commits

ZyX_I committed 4b8fd98

Splitted ftplugin/aurum* into ftplugin/aurum* with maps and plugin/aurum/* with buffer setup, improved :read support (still untested)

Comments (0)

Files changed (8)

ftplugin/aurumannotate.vim

 endif
 setlocal noswapfile
 setlocal nomodeline
-if exists('s:_pluginloaded') && empty(getline('.'))
-    call s:F.setup()
-endif
 execute frawor#Setup('0.0', {'@aurum': '0.0',
             \            '@/mappings': '0.0',
-            \        '@/autocommands': '0.0',
-            \               '@/table': '0.0',
             \                  '@/os': '0.0',})
 let s:_messages={
             \'nofile': 'File was added in this revision',
     endif
     return self[a:hex]
 endfunction
-"▶1 setup
-function s:F.setup()
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
-    let ann=bvar.repo.functions.annotate(bvar.repo, bvar.rev, bvar.file)
-    let d={}
-    call map(ann, 'call(s:F.formatann, [bvar.repo, v:val, v:key], d)')
-    call setline('.', ann)
-    setlocal readonly nomodifiable
-    augroup AuAnnotateNoInsert
-        autocmd InsertEnter <buffer> :call feedkeys("\e", 'n')
-    augroup END
-    if has_key(bvar, 'annbuf') && bufwinnr(bvar.annbuf)!=-1
-        " TODO support for folds
-        execute bufwinnr(bvar.annbuf).'wincmd w'
-    endif
-endfunction
-let s:_augroups+=['AuAnnotateNoInsert']
-if has_key(s:_r.aurum.bufvars, bufnr('%'))
-    call s:F.setup()
-endif
 "▶1 getfile :: repo, cs → path
 function s:F.getfile(repo, cs)
     let file=0
                             \                              bvar.file))
             else
                 try
-                    execute 'edit '.fnameescape('aurum://file:'.epath.':'.
-                                \                               rev1.':'.
-                                \                               bvar.file)
+                    execute 'silent edit '.
+                                \fnameescape('aurum://file:'.epath.':'.
+                                \                            rev1.':'.
+                                \                            bvar.file)
                 catch /\V\^Frawor:\[^:]\+:nofile:/
                     call s:_f.warn('nofile')
                     return
                 let rev1=rev2
                 let rev2=''
             endif
-            execute 'edit '.fnameescape('aurum://diff:'.epath.':'.
-                        \               rev1.':'.rev2.':'.
-                        \               ((a:0&&a:1)?(''):(escape(bvar.file,
-                        \                                        '\;:'))))
+            execute 'silent edit '.
+                        \fnameescape('aurum://diff:'.epath.':'.
+                        \            rev1.':'.rev2.':'.
+                        \            ((a:0&&a:1)?(''):(escape(bvar.file,
+                        \                                     '\;:'))))
         endif
     elseif a:action is# 'open'
         if a:0 && a:1
             endif
         endif
         if hasannbuf
-            execute 'edit '.fnameescape('aurum://annotate:'.epath.':'.hex.
-                        \                                         ':'.file)
+            execute 'silent edit '.
+                        \fnameescape('aurum://annotate:'.epath.':'.hex.':'.file)
             setlocal scrollbind
             let newbvar=s:_r.aurum.bufvars[bufnr('%')]
             execute bufwinnr(bvar.annbuf).'wincmd w'
         endif
-        execute 'edit '.fnameescape('aurum://file:'.epath.':'.hex.':'.file)
+        execute 'silent edit '.
+                    \fnameescape('aurum://file:'.epath.':'.hex.':'.file)
         setlocal scrollbind
         if hasannbuf
             let newbvar.annbuf=bufnr('%')

ftplugin/aurumgraphlog.vim

 endif
 setlocal noswapfile
 setlocal nomodeline
-if exists('s:_pluginloaded') && empty(getline('.'))
-    call s:F.setup()
-endif
 execute frawor#Setup('0.0', {'@aurum': '0.0',
-            \            '@/mappings': '0.0',
-            \        '@/autocommands': '0.0',
-            \               '@/table': '0.0',
             \                  '@/os': '0.0',
-            \             '@/options': '0.0',})
-let s:F.glog={}
-let s:_options={
-            \'ignorefiles': {'default': [],
-            \                'checker': 'list in [patch renames diff]'},
-            \'closewindow': {'default': 1, 'filter': 'bool'},
-        \}
+            \            '@/mappings': '0.0',})
 "▶1 bisect :: [a], function + self → a
 function s:F.bisect(list, function)
     let llist=len(a:list)
     endwhile
     return a:list[lborder]
 endfunction
-"▶1 glog
-"▶2 glog.getstats  :: lines, regex → stats
-function s:F.glog.getstats(lines, diffre)
-    let i=0
-    let llines=len(a:lines)
-    let stats={'files': {}, 'insertions': 0, 'deletions': 0}
-    let file=0
-    while i<llines
-        let line=a:lines[i]
-        if line[:3] is# 'diff'
-            let file=get(matchlist(line, a:diffre, 4), 1, 0)
-            if file isnot 0
-                let stats.files[file]={'insertions': 0, 'deletions': 0,}
-                let i+=1
-                let oldi=i
-                let pmlines=2
-                while pmlines
-                    let lstart=a:lines[i][:2]
-                    if lstart is# '+++' || lstart is# '---'
-                        let pmlines-=1
-                    endif
-                    let i+=1
-                    if i-oldi>=4
-                        let i=oldi
-                        break
-                    endif
-                endwhile
-                continue
-            endif
-        elseif file is 0
-        elseif line[0] is# '+'
-            let stats.insertions+=1
-            let stats.files[file].insertions+=1
-        elseif line[0] is# '-'
-            let stats.deletions+=1
-            let stats.files[file].deletions+=1
-        endif
-        let i+=1
-    endwhile
-    return stats
-endfunction
-"▶2 glog.utfedges
-function s:F.glog.utfedges(seen, hex, parents)
-    let nodeidx=index(a:seen, a:hex)
-    if nodeidx==-1
-        let nodeidx=len(a:seen)
-        call add(a:seen, a:hex)
-    endif
-    let knownparents=[]
-    let newparents=[]
-    for parenthex in a:parents
-        call add(((index(a:seen, parenthex)==-1)?
-                    \   (newparents):
-                    \   (knownparents)),
-                    \parenthex)
-    endfor
-    let ncols=len(a:seen)
-    call remove(a:seen, nodeidx)
-    call extend(a:seen, newparents, nodeidx)
-    let edges=map(knownparents, '['.nodeidx.', index(a:seen, v:val)]')
-    if !empty(newparents)
-        call add(edges, [nodeidx, nodeidx])
-        if len(newparents)>1
-            call add(edges, [nodeidx, nodeidx+1])
-        endif
-    endif
-    let nmorecols=len(a:seen)-ncols
-    return [nodeidx, edges, ncols, nmorecols]
-endfunction
-"▶2 glog.fix_long_right_edges
-function s:F.glog.fix_long_right_edges(edges)
-    call map(a:edges, '((v:val[1]>v:val[0])?([v:val[0], v:val[1]+1]):(v:val))')
-endfunction
-"▶2 glog.get_nodeline_edges_tail
-function s:F.glog.get_nodeline_edges_tail(node_index, p_node_index, n_columns,
-            \                             n_columns_diff, p_diff, fix_tail)
-    if a:fix_tail && a:n_columns_diff==a:p_diff && a:n_columns_diff!=0
-        if a:n_columns_diff==-1
-            let start=max([a:node_index+1, a:p_node_index])
-            return repeat(['|', ' '], (start-a:node_index-1))+
-                        \repeat(['/', ' '], (a:n_columns-start))
-        else
-            return repeat(['\', ' '], (a:n_columns-a:node_index-1))
-        endif
-    else
-        return repeat(['|', ' '], (a:n_columns-a:node_index-1))
-    endif
-endfunction
-"▶2 glog.draw_edges
-function s:F.glog.draw_edges(edges, nodeline, interline)
-    for [start, end] in a:edges
-        if start==end+1
-            let a:interline[2*end   + 1]='/'
-        elseif start==end-1
-            let a:interline[2*start + 1]='\'
-        elseif start==end
-            let a:interline[2*start    ]='|'
-        else
-            let a:nodeline[2*end]='+'
-            if start>end
-                let [start, end]=[end, start]
-            endif
-            for i in range(2*start+1, 2*end-1)
-                if a:nodeline[i] isnot# '+'
-                    let a:nodeline[i]='-'
-                endif
-            endfor
-        endif
-    endfor
-endfunction
-"▶2 glog.get_padding_line
-function s:F.glog.get_padding_line(ni, n_columns, edges)
-    let c=' '
-    if index(a:edges, [a:ni, a:ni-1])!=-1 || index(a:edges, [a:ni, a:ni])!=-1
-        let c='|'
-    endif
-    return repeat(['|', ' '], a:ni)+
-                \[c, ' ']+
-                \repeat(['|', " "], (a:n_columns-a:ni-1))
-endfunction
-"▶2 glog.addlines
-function s:F.glog.addlines(text, lnum)
-    let mapexpr='[v:val[0]+'.a:lnum.']+v:val[1:]'
-    call map(a:text.special, 'v:key[-2:] is? "_r"?'.
-                \                  'map(v:val, '.string(mapexpr).'):'.
-                \                  mapexpr)
-    return a:text
-endfunction
-"▶2 glog.addcols
-function s:F.glog.addcols(text, cnum)
-    let mapexpr='[v:val[0], v:val[1]+'.a:cnum.']+v:val[2:]'
-    call map(a:text.special, 'v:key[-2:] is? "_r"?'.
-                \                  'map(v:val, '.string(mapexpr).'):'.
-                \                  mapexpr)
-    return a:text
-endfunction
-"▶2 glog.utf
-function s:F.glog.utf(state, type, char, text, coldata)
-    let [idx, edges, ncols, coldiff]=a:coldata
-    let add_padding_line=0
-    let lnum=len(a:text.text)
-    if coldiff==-1
-        call s:F.glog.fix_long_right_edges(edges)
-        if lnum>2
-            let add_padding_line=!empty(map(filter(copy(edges),
-                        \                          '(v:val[0]+1)<v:val[1]'),
-                        \                   'v:val[0]'))
-        endif
-    endif
-    let fix_nodeline_tail = (lnum<=2 && add_padding_line)
-    let shift_interline=repeat(['|', ' '], idx)
-    let nodeline=copy(shift_interline)+
-                \[a:char, ' ']+
-                \s:F.glog.get_nodeline_edges_tail(idx,     a:state[1], ncols,
-                \                                 coldiff, a:state[0],
-                \                                 fix_nodeline_tail)
-    if coldiff==-1
-        let n_spaces=1
-        let edge_ch='/'
-    elseif coldiff==0
-        let n_spaces=2
-        let edge_ch='|'
-    else
-        let n_spaces=3
-        let edge_ch='\'
-    endif
-    let shift_interline+=repeat([' '], n_spaces)+
-                \        repeat([edge_ch, ' '], (ncols-idx-1))
-    call s:F.glog.draw_edges(edges, nodeline, shift_interline)
-    let lines=[nodeline]
-    if add_padding_line
-        call add(lines, s:F.glog.get_padding_line(idx, ncols, edges))
-    endif
-    if a:text.min
-        let joined_sil=join(shift_interline, '')
-        let joined_nl=substitute(join(nodeline, ''), '\V'.a:char, '|', '')
-        let a:text.text=[]
-        if joined_nl!~#'\v^[o| ]+$'
-            let a:text.text+=[substitute(joined_nl,'\v\-@<=\||\|\-@=','+','')]
-        endif
-        if joined_sil!~#'\v^[| ]+$' && joined_sil isnot# joined_nl
-            let a:text.text+=[joined_sil]
-        endif
-        return a:text
-    else
-        call add(lines, shift_interline)
-    endif
-    let ltdiff=lnum-len(lines)
-    if ltdiff>0
-        let extra_interline=repeat(['|', ' '], ncols+coldiff)
-        call extend(lines, repeat([extra_interline], ltdiff))
-    else
-        call extend(a:text.text, repeat([''], -ltdiff))
-    endif
-    let indentation_level=2*max([ncols, ncols+coldiff])
-    let a:state[0]=coldiff
-    let a:state[1]=idx
-    call map(lines, 'printf("%-*s ", indentation_level, join(v:val, ""))')
-    let curspecial=a:text.special
-    let shiftlen=len(lines[0])
-    call s:F.glog.addcols(a:text, shiftlen)
-    let a:text.block_r=[[0, shiftlen],
-                \       [len(a:text.text)-1,
-                \        max(map(copy(lines), 'len(v:val)'))]]
-    let curspecial.bullet=[0, stridx(lines[0], a:char), a:char]
-    call map(a:text.text, 'lines[v:key].v:val')
-    return a:text
-endfunction
-"▶2 glog.summary
-let s:datechars='YmdHM'
-function s:F.glog.summary(cs, opts)
-    let special={}
-    let text=[]
-    let r={'text': text, 'special': special, 'min': 0}
-    "▶3 Add changeset (and, possibly, branch)
-    let text+=['Changeset '.a:cs.rev.':'.a:cs.hex]
-    let special.changeset_l=[0, 0]
-    if a:cs.branch isnot# 'default'
-        let changesetend=len(text[0])
-        let special.changeset_r=[[0, 0], [0, changesetend]]
-        let text[0].=' (branch '.a:cs.branch.')'
-        let special.branch_r=[[0, changesetend+1], [0, len(text[0])-1]]
-    endif
-    "▶3 Minimize changesets that do not match opts
-    if (has_key(a:opts, 'branch') && a:cs.branch isnot# a:opts.branch) ||
-                \(has_key(a:opts, 'merges') &&
-                \   ((a:opts.merges)?(len(a:cs.parents)<=1):
-                \                    (len(a:cs.parents)>1))) ||
-                \(has_key(a:opts, 'search') &&
-                \   a:cs.description!~#a:opts.search) ||
-                \(has_key(a:opts, 'user') && a:cs.user!~#a:opts.user) ||
-                \(has_key(a:opts, 'revision') &&
-                \   !has_key(a:opts.revisions, a:cs.hex))
-        let r.min=1
-        return r
-    endif
-    "▶4 Date selector
-    if has_key(a:opts, 'date')
-        let i=0
-        for num in split(a:opts.date, '\v[^0-9*.]+')
-            if num isnot# '*'
-                let spec='%'.s:datechars[i]
-                if num is# '.'
-                    let num=str2nr(strftime(spec))
-                else
-                    if i==0 && len(num)==2
-                        let y=str2nr(num)
-                        let cy=str2nr(strftime('%y'))
-                        let c=str2nr(strftime('%Y')[:-3])
-                        if y<=cy
-                            let num=((c*100)+y)
-                        else
-                            let num=(((c-1)*100)+y)
-                        endif
-                    else
-                        let num=str2nr(num)
-                    endif
-                endif
-                if str2nr(strftime(spec, a:cs.time))!=num
-                    let r.min=1
-                    return r
-                endif
-            endif
-            let i+=1
-        endfor
-    endif
-    "▶4 Files selector
-    if has_key(a:opts, 'files')
-        " XXX files list will be used later in section “Add files”
-        let files=a:opts.csfiles[a:cs.hex]
-        if empty(files)
-            let r.min=1
-            return r
-        endif
-    endif
-    "▶3 Add date and user
-    let date=strftime("%d %b %Y %H:%M", a:cs.time)
-    let text+=['Commited '.date.' by '.a:cs.user]
-    "▶4 Specials: commit_l, date_r, user_r
-    let special.commit_l=[1, 0]
-    let commitedlen=stridx(text[-1], ' ')+1
-    let special.date_r=[[1, commitedlen], [1, commitedlen+len(date)]]
-    let special.user_r=[[1, len(text[1])-len(a:cs.user)], [1, len(text[1])-1]]
-    "▶3 Add bookmarks and tags
-    for key in filter(['tags', 'bookmarks'], 'has_key(a:cs, v:val) && '.
-                \                            '!empty(a:cs[v:val])')
-        let upkey=toupper(key[0]).key[1:-2]
-        let newtext=[upkey]
-        if len(a:cs[key])>1
-            let newtext[0].='s: '.join(a:cs[key], ', ')
-        else
-            let newtext[0].=': '.a:cs[key][0]
-        endif
-        let special[key.'_R']=[[len(text),                 0],
-                    \           [len(text)+len(newtext)-1, 0]]
-        let text+=newtext
-    endfor
-    "▶3 Add files
-    if get(a:opts, 'showfiles', 0) &&
-                \!empty(a:opts.repo.functions.getcsprop(a:opts.repo, a:cs,
-                \                                       'files'))
-        let curline=len(text)
-        let ftext=['Files: ']
-        let ww=winwidth(0)-10
-        let lw=s:_r.strdisplaywidth(ftext[-1])
-        let curfilenum=0
-        let fi=0
-        for file in exists('files') ? files : a:cs.files
-            let fw=s:_r.strdisplaywidth(file, lw)
-            if curfilenum && fw+lw>ww
-                let ftext+=['Files: '.file.', ']
-                let lw=s:_r.strdisplaywidth(ftext[-1])
-                let curfilenum=0
-            else
-                let ftext[-1].=file.', '
-                let lw+=fw
-            endif
-            let cl=curline+len(ftext)-1
-            let fl=len(file)
-            let ll=len(ftext[-1])
-            let special['file'.fi.'_r']=[[cl, ll-fl-2], [cl, ll-2]]
-            if exists('files')
-                let fi=index(a:cs.files, file)
-            else
-                let fi+=1
-            endif
-            let curfilenum+=1
-        endfor
-        call map(ftext, 'v:val[:-3]')
-        let special.files_R=[[curline, 0], [curline+len(ftext)-1, 0]]
-        let text+=ftext
-    endif
-    "▶3 Add renames
-    if get(a:opts, 'showrenames', 0)
-        for [cur, old] in items(a:opts.repo.functions.getcsprop(a:opts.repo,
-                    \                                           a:cs,
-                    \                                           'renames'))
-            if type(old)!=type('') ||
-                        \(exists('files') &&
-                        \ !has_key(a:opts.ignorefiles, 'renames') &&
-                        \ index(files, cur)==-1)
-                continue
-            endif
-            let cl=len(text)
-            let fl=len(cur)
-            let ol=len(old)
-            let text+=['Renamed '.old.' to '.cur]
-            let ll=len(text[-1])
-            let fi=index(a:cs.files, cur)
-            let special['file'.fi.'_r']=[[cl, ll-fl], [cl, ll-1]]
-            let special['oldname'.fi.'_r']=[[cl, ll-fl-4-ol], [cl, ll-fl-4]]
-            let special['rename'.fi.'_l']=[cl, 0]
-        endfor
-    endif
-    "▶3 Add description
-    let description=map(split(a:cs.description, "\n", 1), '"  ".v:val')
-    let special.description_R=[[len(text),                    0],
-                \              [len(text)+len(description)-1, 0]]
-    let text+=description
-    "▶3 Add patch and stat
-    let dopatch = get(a:opts, 'patch', 0)
-    let dostat  = get(a:opts, 'stat',  0)
-    if (dopatch || dostat) && !empty(a:cs.parents)
-        let patchfiles=((exists('files') &&
-                    \    !has_key(a:opts.ignorefiles, 'patch'))?
-                    \       (files):
-                    \       ([]))
-        let diff=a:opts.repo.functions.diff(a:opts.repo, a:cs.hex,
-                    \                       a:cs.parents[0], patchfiles, a:opts)
-        if dostat
-            " TODO Put this regular expressions somewhere else
-            let git=get(a:opts, 'git', 0)
-            let stats=s:F.glog.getstats(diff, git ? '\V --git a/\(\.\{-}\) b/'
-                        \                         : '\v\ .*\-r\ \w+\s(.*)$')
-            " TODO Add per-file stat
-            let statsum=printf('#%u files changed, %u insertions, %u deletions',
-                        \      len(keys(stats.files)),
-                        \      stats.insertions, stats.deletions)
-            let special.stat_l=[len(text), 0]
-            let text+=[statsum]
-        endif
-        if dopatch
-            call map(diff, '":".v:val')
-            let special.diff_R=[[len(text), 0], [len(text)+len(diff)-1, 0]]
-            let text+=diff
-        endif
-    endif
-    "▲3
-    let text+=['']
-    return r
-endfunction
-"▶2 glog.generate
-function s:F.glog.generate(css, showparents, Dumper, opts)
-    let seen=[]
-    let state=[0, 0]
-    let r=      {'text': [],
-                \'specials': {},
-                \'rectangles': [],
-                \'csstarts': {},}
-    for cs in a:css
-        let char=((index(a:showparents, cs.hex)==-1)?('o'):('@'))
-        let text=call(a:Dumper, [cs, a:opts], {})
-        call s:F.glog.utf(state, 'C', char, text,
-                    \     s:F.glog.utfedges(seen, cs.hex, cs.parents))
-        if empty(text.text)
-            continue
-        endif
-        if !text.min
-            let text.block_r[0][0]+=len(r.text)
-            let text.block_r[1][0]+=len(r.text)
-            let r.specials[cs.hex]=text.special
-            let r.rectangles+=[text.block_r+[cs.hex]]
-            let r.csstarts[cs.hex]=text.block_r[0][0]
-        endif
-        let r.text+=text.text
-    endfor
-    return r
-endfunction
-"▶2 glog.graphlog
-function s:F.glog.graphlog(repo, opts)
-    let css=reverse(a:repo.cslist[a:opts.revs[0]:a:opts.revs[1]])
-    let a:opts.repo=a:repo
-    return s:F.glog.generate(css, [a:repo.work_hex], s:F.glog.summary, a:opts)
-endfunction
-"▶1 setup
-"▶2 trackfile :: repo, cs, file, csfiles → + csfiles
-function s:F.trackfile(repo, cs, file, csfiles)
-    let tocheck=[[a:file, a:cs]]
-    while !empty(tocheck)
-        let [file, cs]=remove(tocheck, 0)
-        if !has_key(a:csfiles, cs.hex)
-            " TODO Check whether it causes a problem with more then one parent
-            return
-        endif
-        let rename=get(cs.renames, file, 0)
-        if type(rename)!=type('')
-            let rename=file
-        endif
-        if index(a:csfiles[cs.hex], file)==-1
-            let a:csfiles[cs.hex]+=[file]
-        endif
-        let tocheck+=map(copy(cs.parents), '[rename, a:repo.changesets[v:val]]')
-    endwhile
-endfunction
-"▲2
-function s:F.setup()
-    let buf=bufnr('%')
-    let bvar=s:_r.aurum.bufvars[buf]
-    if has_key(bvar, 'createdlog')
-        return
-    endif
-    let bvar.createdlog=1
-    call bvar.repo.functions.getchangesets(bvar.repo)
-    "▶2 Add `ignorefiles'
-    let ignorefiles=(has_key(bvar.opts, 'ignfiles')?
-                \               (bvar.opts.ignfiles):
-                \               (s:_f.getoption('ignorefiles')))
-    let bvar.opts.ignorefiles={}
-    call map(copy(ignorefiles), 'extend(bvar.opts.ignorefiles, {v:val : 1})')
-    unlet ignorefiles
-    "▶2 Get revision range
-    if has_key(bvar.opts, 'revrange')
-        let bvar.opts.revs=map(copy(bvar.opts.revrange),
-                    \'bvar.repo.changesets['.
-                    \   'bvar.repo.functions.getrevhex(bvar.repo, v:val)].rev')
-    elseif get(bvar.opts, 'limit', 0)>0
-        let bvar.opts.revs=[bvar.repo.csnum-bvar.opts.limit-1,
-                    \       bvar.repo.csnum-2]
-    else
-        let bvar.opts.revs=[0, bvar.repo.csnum-2]
-    endif
-    "▶2 Process `revision' option
-    if has_key(bvar.opts, 'revision')
-        let hex=bvar.repo.functions.getrevhex(bvar.repo, bvar.opts.revision)
-        let cs=bvar.repo.changesets[hex]
-        if cs.rev<bvar.opts.revs[1]
-            let bvar.opts.revs[1]=cs.rev
-        endif
-        let bvar.opts.revisions={}
-        let addrevs=[cs]
-        while !empty(addrevs)
-            let cs=remove(addrevs, 0)
-            if has_key(bvar.opts.revisions, cs.hex)
-                continue
-            endif
-            let bvar.opts.revisions[cs.hex]=1
-            let addrevs+=map(copy(cs.parents), 'bvar.repo.changesets[v:val]')
-        endwhile
-    endif
-    "▲2
-    let cslist=bvar.repo.cslist[bvar.opts.revs[0]:bvar.opts.revs[1]]
-    "▶2 Generate cs.renames for `showrenames' option
-    if get(bvar.opts, 'showrenames', 0)
-        for cs in cslist
-            call bvar.repo.functions.getcsprop(bvar.repo, cs, 'renames')
-        endfor
-    endif
-    "▶2 Generate cs.files for several options
-    if has_key(bvar.opts, 'files') || get(bvar.opts, 'showrenames', 0) ||
-                \                     get(bvar.opts, 'showfiles',   0)
-        for cs in cslist
-            call bvar.repo.functions.getcsprop(bvar.repo, cs, 'files')
-        endfor
-    endif
-    "▶2 Generate file lists for `files' option
-    if has_key(bvar.opts, 'files')
-        let bvar.opts.filepats=map(copy(bvar.opts.files),
-                    \              's:_r.aurum.globtopattern(v:val)')
-        let bvar.opts.csfiles={}
-        for cs in cslist
-            let rns     = bvar.repo.functions.getcsprop(bvar.repo,cs, 'renames')
-            let changes = bvar.repo.functions.getcsprop(bvar.repo,cs, 'changes')
-            let changes = copy(changes)
-            let csfiles = []
-            let bvar.opts.csfiles[cs.hex]=csfiles
-            for pattern in bvar.opts.filepats
-                let newfiles=filter(copy(changes), 'v:val=~#pattern')
-                call filter(changes, 'index(newfiles, v:val)==-1')
-                let csfiles+=newfiles
-                if empty(changes)
-                    break
-                endif
-            endfor
-            call map(copy(csfiles), 's:F.trackfile(bvar.repo, cs, v:val, '.
-                        \                         'bvar.opts.csfiles)')
-        endfor
-        let bvar.opts.totrack={}
-    endif
-    "▲2
-    let text=s:F.glog.graphlog(bvar.repo, bvar.opts)
-    let bvar.specials=text.specials
-    let bvar.rectangles=text.rectangles
-    let bvar.csstarts=text.csstarts
-    setlocal noreadonly modifiable
-    call setline(1, text.text)
-    setlocal readonly nomodifiable buftype=nofile
-    augroup AuGlogNoInsert
-        autocmd InsertEnter <buffer> :call feedkeys("\e", "n")
-    augroup END
-endfunction
-let s:_augroups+=['AuGlogNoInsert']
-if has_key(s:_r.aurum.bufvars, bufnr('%'))
-    call s:F.setup()
-endif
 "▶1 checkinblock :: block → -1|0|1
 function s:F.checkinblock(block)
     let curline=line('.')-1
     endfor
     return 0
 endfunction
-"▶1 cwin
-function s:F.cwin()
-    return s:_f.getoption('closewindow')?
-                \":wincmd c\n":
-                \":new\n"
-endfunction
 "▶1 cr
 function s:F.cr(...)
     "▶2 Get changeset, current special, encode options
         endif
     endif
     "▲3
-    return s:F.cwin().":".cmd."\n"
+    return bvar.cw.":silent ".cmd."\n"
 endfunction
 "▶1 gethexfile
 function s:F.gethexfile()
     endif
     let bvar=s:_r.aurum.bufvars[bufnr('%')]
     let epath=escape(bvar.repo.path, ':\')
-    return s:F.cwin().":edit ".
+    return bvar.cw.":silent edit ".
                 \fnameescape('aurum://file:'.epath.':'.hex.':'.file)."\n"
 endfunction
 "▶1 annotate
     let epath=escape(bvar.repo.path, ':\')
     let efile=escape(file, '\;:')
     if a:0 && a:1
-        return s:F.cwin().":edit ".
+        return bvar.cw.":silent edit ".
                     \fnameescape('aurum://diff:'.epath.':'.hex.'::'.efile)."\n"
     else
-        return s:F.cwin().":edit ".
+        return bvar.cw.":silent edit ".
                     \fnameescape('aurum://diff:'.epath.'::'.hex.':'.efile)."\n"
     endif
 endfunction
     let cs=bvar.repo.changesets[hex]
     let epath=escape(bvar.repo.path, ':\')
     if a:0 && a:1
-        return s:F.cwin().":edit ".
+        return bvar.cw.":silent edit ".
                     \fnameescape(s:_r.os.path.join(bvar.repo.path, file))."\n".
-                    \':diffsplit '.fnameescape('aurum://file:'.epath.':'.
-                    \                          hex.':'.file)."\n"
+                    \':silent diffsplit '.
+                    \fnameescape('aurum://file:'.epath.':'.hex.':'.file)."\n"
     elseif !empty(cs.parents)
-        return s:F.cwin().":edit ".
+        return bvar.cw.":silent edit ".
                     \fnameescape('aurum://file:'.epath.':'.hex.':'.file)."\n".
-                    \':diffsplit '.fnameescape('aurum://file:'.epath.':'.
-                    \                          cs.parents[0].':'.file)."\n"
+                    \':silent diffsplit '.
+                    \fnameescape('aurum://file:'.epath.':'.
+                    \                            cs.parents[0].':'.file)."\n"
     endif
     return ''
 endfunction
     let bvar=s:_r.aurum.bufvars[bufnr('%')]
     let opts=s:_r.aurum.encodeopts(bvar.opts)
     let epath=escape(bvar.repo.path, ':\')
-    return ':edit '.fnameescape('aurum://glog:'.epath.':'.opts.
-                \               'files:'.escape(file, ';,:\*?[').','.
-                \               'crrestrict:files')."\n"
+    return ':silent edit '.
+                \fnameescape('aurum://glog:'.epath.':'.opts.
+                \            'files:'.escape(file, ';,:\*?[').','.
+                \            'crrestrict:files')."\n"
 endfunction
 "▶1 AuGlog mapping group
 " TODO U (update) mapping

ftplugin/aurumstatus.vim

 setlocal nolist nowrap
 setlocal noswapfile
 setlocal nomodeline
-if exists('s:_pluginloaded') && empty(getline('.'))
-    call s:F.setup()
-endif
 execute frawor#Setup('0.0', {'@aurum': '0.0',
             \            '@/mappings': '0.0',
-            \        '@/autocommands': '0.0',
             \                  '@/os': '0.0',})
-"▶1 setup
-let s:statchars={
-            \ 'deleted': '!',
-            \ 'unknown': '?',
-        \}
-let s:defshow=['modified', 'added', 'removed', 'deleted', 'unknown']
-let s:allshow=s:defshow+['ignored', 'clean']
-let s:showchars={}
-call map(copy(s:statchars), 'extend(s:showchars, {v:val            : v:key})')
-call map(copy(s:allshow),   'extend(s:showchars, {toupper(v:val[0]): v:val})')
-function s:F.setup()
-    let bvar=s:_r.aurum.bufvars[bufnr('%')]
-    let status=bvar.repo.functions.status(bvar.repo, get(bvar.opts, 'rev1', 0),
-                \                                    get(bvar.opts, 'rev2', 0))
-    let bvar.types=[]
-    let bvar.chars=[]
-    let bvar.files=[]
-    if has_key(bvar.opts, 'show')
-        if index(bvar.opts.show, 'all')==-1
-            let show=[]
-            for type in bvar.opts.show
-                if type[0]=~#'^\l'
-                    let show+=[type]
-                else
-                    let show+=map(split(type, '\v.@='), 's:showchars[v:val]')
-                endif
-            endfor
-        else
-            let show=s:allshow
-        endif
-    else
-        let show=s:defshow
-    endif
-    if has_key(bvar.opts, 'files')
-        let bvar.opts.filepats=map(copy(bvar.opts.files),
-                    \              's:_r.aurum.globtopattern(v:val)')
-    endif
-    let isrecord=get(bvar.opts, 'record', 0)
-    for [type, files] in filter(items(status), 'index(show, v:val[0])!=-1')
-        let char=has_key(s:statchars, type)? s:statchars[type]: toupper(type[0])
-        for file in files
-            let ignore=0
-            if has_key(bvar.opts, 'files')
-                let ignore=1
-                for pattern in bvar.opts.filepats
-                    if file=~#pattern
-                        let ignore=0
-                        break
-                    endif
-                endfor
-            endif
-            if ignore
-                continue
-            endif
-            call append('$', ((isrecord)?('-'):('')).char.' '.file)
-            let bvar.types+=[type]
-            let bvar.chars+=[char]
-            let bvar.files+=[file]
-        endfor
-    endfor
-    if empty(bvar.types)
-        call append('$', 'No changes found')
-    endif
-    silent 1 delete _
-    setlocal readonly nomodifiable
-    augroup AuStatusNoInsert
-        autocmd InsertEnter <buffer> :call feedkeys("\e", 'n')
-    augroup END
-endfunction
-let s:_augroups+=['AuStatusNoInsert']
-if has_key(s:_r.aurum.bufvars, bufnr('%'))
-    call s:F.setup()
-endif
 "▶1 runmap
 let s:noacttypes={
             \    'open': ['deleted'],
                 \            '@/autocommands': '0.0',
                 \               '@/resources': '0.0',
                 \                     '@/fwc': '0.3',
-                \                 '@/options': '0.0',}, 0)
+                \                 '@/options': '0.0',
+                \           '@aurum/annotate': '0.0',
+                \             '@aurum/status': '0.0',
+                \           '@aurum/graphlog': '0.0',}, 0)
     call map(['hg', 'comm', 'rec', 'com'], 'extend(s:F, {v:val : {}})')
     lockvar 1 s:F
     "▶2 Команды
     " TODO improve files completion
     " TODO U (update), K and J mappings for aurum://file and aurum://diff
     " TODO :Au[Read?] (read revision to current buffer) (?: cmd %r)
-    " TODO :Au[Grep?], :AuMove, :Au[Remove?], :AuBrowse
+    " TODO :AuSearch, :AuMove, :Au[Delete?], :AuBrowse
     " TODO {count}K, {count}J
     " TODO Global mappings for most commands
     " TODO aurum#changeset() and aurum#repository()
     if height<=0
         let height=winheight(0)/5
     endif
-    execute 'botright '.height.' split '.
+    execute 'silent botright '.height.' split '.
                 \fnameescape('aurum://status:'.epath.':'.opts)
     let w:aurecid='AuRecordStatus'
     setlocal nomodifiable
 endfunction
 "▶3 comm.getrrf :: opts, failmsg + buf -> (hasbuf, repo, rev, file)
 let s:rrffailresult=[0, 0, 0, 0]
-function s:F.comm.getrrf(opts, failmsg)
+function s:F.comm.getrrf(opts, failmsg, ann)
     let hasbuf=0
     let file=0
     "▶4 a:opts.file file -> (repo?)
                 let rev=repo.functions.getrevhex(repo, rev)
                 let annrev=repo.functions.getrevhex(repo, bvar.rev)
                 if rev is# annrev
-                    if has_key(bvar, 'annbuf') && bufwinnr(bvar.annbuf)!=-1
+                    if a:ann!=1
+                        " Don't do the following if we are not annotating
+                    elseif has_key(bvar, 'annbuf') && bufwinnr(bvar.annbuf)!=-1
                         execute bufwinnr(bvar.annbuf).'wincmd w'
                     else
                         let epath=escape(repo.path, ':\')
                         setlocal scrollbind
-                        execute 'rightbelow vsplit'
+                        execute 'silent rightbelow vsplit'
                                     \fnameescape('aurum://file:'.epath.':'.
                                     \                            rev.  ':'.
                                     \                            file)
             call insert(closedfolds, line)
         endif
     endfor
-    execute 'diffsplit' fnameescape(a:difftarget)
+    execute 'silent diffsplit' fnameescape(a:difftarget)
     let bvar.diffbuf=bufnr('%')
     augroup AurumDiff
         execute 'autocmd BufWipeOut <buffer> '.
         let [repo, opts]=s:F.comm.repotupleoptssplit(tail, 0)
         if has_key(opts, 'files')
             let opts.files=split(opts.files, '\v%(\\@<!\\%(\\\\)*)@<!;')
+            let opts.filepats=map(copy(opts.files),
+                        \         's:F.comm.globtopattern(v:val)')
         endif
         if has_key(opts, 'revrange')
             let opts.revrange=split(opts.revrange, ';')
                     \     'has_key(opts, v:val)')
             let opts[key]=+opts[key]
         endfor
-        let s:bufvars[buf]={'repo': repo, 'opts': opts, 'read': a:0}
+        let bvar={'repo': repo, 'opts': opts, 'read': a:0}
+        call s:_r.glog.setup(bvar)
         if !a:0
+            let s:bufvars[buf]=bvar
             setlocal filetype=aurumgraphlog bufhidden=wipe
+            runtime ftplugin/aurumgraphlog.vim
         endif
-        runtime ftplugin/aurumgraphlog.vim
     "▶3 file command (repo:rev:file)
     elseif command is# 'file'
         let [repo, rev, file]=s:F.comm.repotuplesplit(tail, 2)
     "▶3 annotate command (repo:rev:file)
     elseif command is# 'annotate'
         let [repo, rev, file]=s:F.comm.repotuplesplit(tail, 2)
-        let s:bufvars[buf]={'repo': repo, 'rev': rev, 'file': file, 'read': a:0}
+        let bvar={'repo': repo, 'rev': rev, 'file': file, 'read': a:0}
+        call s:_r.annotate.setup(bvar)
         if !a:0
+            let s:bufvars[buf]=bvar
             setlocal filetype=aurumannotate bufhidden=wipe
+            runtime ftplugin/aurumannotate.vim
         endif
-        runtime ftplugin/aurumannotate.vim
     "▶3 diff command (repo:rev1:rev2:files:opts)
     elseif command is# 'diff'
         let [repo, rev1, rev2, files, opts]=s:F.comm.repotupleoptssplit(tail, 3)
         let [repo, opts]=s:F.comm.repotupleoptssplit(tail, 0)
         if has_key(opts, 'files')
             let opts.files=split(opts.files, '\v%(\\@<!\\%(\\\\)*)@<!;')
+            let opts.filepats=map(copy(opts.files),
+                        \         's:F.comm.globtopattern(v:val)')
         endif
         if has_key(opts, 'show')
             let opts.show=split(opts.show, ';')
         for key in filter(['rev1', 'rev2'], 'has_key(opts, v:val)')
             let opts[key]=repo.functions.getrevhex(repo, opts[key])
         endfor
-        let s:bufvars[buf]={'repo': repo, 'opts': opts, 'read': a:0}
+        let bvar={'repo': repo, 'opts': opts, 'read': a:0}
+        call s:_r.status.setup(bvar)
         if !a:0
+            let s:bufvars[buf]=bvar
             setlocal filetype=aurumstatus bufhidden=wipe
+            runtime ftplugin/aurumstatus.vim
         endif
-        runtime ftplugin/aurumstatus.vim
     "▶3 commit command  (repo:user:date:files)
     elseif command is# 'commit'
         if a:0
     endif
     "▲3
     let s:bufvars[buf].command=command
+    file
 endfunction
 let s:_augroups+=['AuCommitMessage']
 "▶2 glogfunc
             \'\Vcmd\s\+type ""',    'cmd first (in cmds, idof command)', ''))
 "▶2 annfunc
 function s:annfunc.function(opts)
-    let [hasannbuf, repo, rev, file]=s:F.comm.getrrf(a:opts, 'noafile')
+    let [hasannbuf, repo, rev, file]=s:F.comm.getrrf(a:opts, 'noafile', 1)
     if repo is 0
         return
     endif
         setlocal scrollbind
     else
         " TODO Check for errors
-        execute 'edit' fnameescape('aurum://file:'.epath.':'.rev.':'.file)
+        execute 'silent edit '.
+                    \fnameescape('aurum://file:'.epath.':'.rev.':'.file)
         let annbuf=bufnr('%')
         setlocal scrollbind
     endif
-    execute 'leftabove 42vsplit '.
+    execute 'silent leftabove 42vsplit '.
                 \fnameescape('aurum://annotate:'.epath.':'.rev.':'.file)
     setlocal scrollbind
     let s:bufvars[bufnr('%')].annbuf=annbuf
             \'\Vcmd\s\+(type "")',  'cmd first (in cmds, idof command)', ''))
 "▶2 vimdfunc
 function s:vimdfunc.function(opts, ...)
-    let [hasbuf, repo, rev, file]=s:F.comm.getrrf(a:opts, 'nodfile')
+    let [hasbuf, repo, rev, file]=s:F.comm.getrrf(a:opts, 'nodfile', 0)
     if repo is 0
         return
     endif
             let opts.file=a:file
         endif
     endif
-    let [hasbuf, repo, rev, file]=s:F.comm.getrrf(opts, 'noffile')
+    let [hasbuf, repo, rev, file]=s:F.comm.getrrf(opts, 'noffile', 0)
     if repo is 0
         return
     endif

plugin/aurum/annotate.vim

+"▶1 
+scriptencoding utf-8
+if !exists('s:_pluginloaded')
+    execute frawor#Setup('0.0', {'@/table': '0.0',
+                \            '@/resources': '0.0',}, 0)
+    finish
+elseif s:_pluginloaded
+    finish
+endif
+"▶1 formatann :: repo, hex, lnum → String
+function s:F.formatann(repo, hex, lnum)
+    if !has_key(self, a:hex)
+        let cs=a:repo.functions.getcs(a:repo, a:hex)
+        let numlen=len(len(a:repo.cslist))
+        let description=matchstr(cs.description, '\v[^\r\n]+')
+        while s:_r.strdisplaywidth(description, numlen+1)>30
+            let description=substitute(description, '.$', '', '')
+        endwhile
+        if len(description)<len(cs.description)
+            let description.='…'
+        endif
+        let descwidth=s:_r.strdisplaywidth(description, numlen+1)
+        if descwidth<31
+            let description.=repeat(' ', 31-descwidth)
+        endif
+        let user=substitute(cs.user, '\m\s*<[^>]\+>$', '', '')
+        let self[a:hex]=printf('%*u %s / %s', numlen, cs.rev, description, user)
+    endif
+    return self[a:hex]
+endfunction
+"▶1 setup
+function s:F.setup(bvar)
+    let bvar=a:bvar
+    let ann=bvar.repo.functions.annotate(bvar.repo, bvar.rev, bvar.file)
+    let d={}
+    call map(ann, 'call(s:F.formatann, [bvar.repo, v:val, v:key], d)')
+    if bvar.read
+        call append('.', ann)
+    else
+        call setline('.', ann)
+        setlocal readonly nomodifiable
+        augroup AuAnnotateNoInsert
+            autocmd InsertEnter <buffer> :call feedkeys("\e", 'n')
+        augroup END
+        if has_key(bvar, 'annbuf') && bufwinnr(bvar.annbuf)!=-1
+            " TODO support for folds
+            execute bufwinnr(bvar.annbuf).'wincmd w'
+        endif
+    endif
+endfunction
+let s:_augroups+=['AuAnnotateNoInsert']
+call s:_f.postresource('annotate', {'setup': s:F.setup})
+"▶1
+call frawor#Lockvar(s:, '_r,_pluginloaded')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

plugin/aurum/graphlog.vim

+"▶1
+scriptencoding utf-8
+if !exists('s:_pluginloaded')
+    execute frawor#Setup('0.0', {'@/table': '0.0',
+                \            '@/resources': '0.0',
+                \              '@/options': '0.0',}, 0)
+    finish
+elseif s:_pluginloaded
+    finish
+endif
+let s:F.glog={}
+let s:_options={
+            \'ignorefiles': {'default': [],
+            \                'checker': 'list in [patch renames diff]'},
+            \'closewindow': {'default': 1, 'filter': 'bool'},
+        \}
+"▶1 glog
+"▶2 glog.getstats  :: lines, regex → stats
+function s:F.glog.getstats(lines, diffre)
+    let i=0
+    let llines=len(a:lines)
+    let stats={'files': {}, 'insertions': 0, 'deletions': 0}
+    let file=0
+    while i<llines
+        let line=a:lines[i]
+        if line[:3] is# 'diff'
+            let file=get(matchlist(line, a:diffre, 4), 1, 0)
+            if file isnot 0
+                let stats.files[file]={'insertions': 0, 'deletions': 0,}
+                let i+=1
+                let oldi=i
+                let pmlines=2
+                while pmlines
+                    let lstart=a:lines[i][:2]
+                    if lstart is# '+++' || lstart is# '---'
+                        let pmlines-=1
+                    endif
+                    let i+=1
+                    if i-oldi>=4
+                        let i=oldi
+                        break
+                    endif
+                endwhile
+                continue
+            endif
+        elseif file is 0
+        elseif line[0] is# '+'
+            let stats.insertions+=1
+            let stats.files[file].insertions+=1
+        elseif line[0] is# '-'
+            let stats.deletions+=1
+            let stats.files[file].deletions+=1
+        endif
+        let i+=1
+    endwhile
+    return stats
+endfunction
+"▶2 glog.utfedges
+function s:F.glog.utfedges(seen, hex, parents)
+    let nodeidx=index(a:seen, a:hex)
+    if nodeidx==-1
+        let nodeidx=len(a:seen)
+        call add(a:seen, a:hex)
+    endif
+    let knownparents=[]
+    let newparents=[]
+    for parenthex in a:parents
+        call add(((index(a:seen, parenthex)==-1)?
+                    \   (newparents):
+                    \   (knownparents)),
+                    \parenthex)
+    endfor
+    let ncols=len(a:seen)
+    call remove(a:seen, nodeidx)
+    call extend(a:seen, newparents, nodeidx)
+    let edges=map(knownparents, '['.nodeidx.', index(a:seen, v:val)]')
+    if !empty(newparents)
+        call add(edges, [nodeidx, nodeidx])
+        if len(newparents)>1
+            call add(edges, [nodeidx, nodeidx+1])
+        endif
+    endif
+    let nmorecols=len(a:seen)-ncols
+    return [nodeidx, edges, ncols, nmorecols]
+endfunction
+"▶2 glog.fix_long_right_edges
+function s:F.glog.fix_long_right_edges(edges)
+    call map(a:edges, '((v:val[1]>v:val[0])?([v:val[0], v:val[1]+1]):(v:val))')
+endfunction
+"▶2 glog.get_nodeline_edges_tail
+function s:F.glog.get_nodeline_edges_tail(node_index, p_node_index, n_columns,
+            \                             n_columns_diff, p_diff, fix_tail)
+    if a:fix_tail && a:n_columns_diff==a:p_diff && a:n_columns_diff!=0
+        if a:n_columns_diff==-1
+            let start=max([a:node_index+1, a:p_node_index])
+            return repeat(['|', ' '], (start-a:node_index-1))+
+                        \repeat(['/', ' '], (a:n_columns-start))
+        else
+            return repeat(['\', ' '], (a:n_columns-a:node_index-1))
+        endif
+    else
+        return repeat(['|', ' '], (a:n_columns-a:node_index-1))
+    endif
+endfunction
+"▶2 glog.draw_edges
+function s:F.glog.draw_edges(edges, nodeline, interline)
+    for [start, end] in a:edges
+        if start==end+1
+            let a:interline[2*end   + 1]='/'
+        elseif start==end-1
+            let a:interline[2*start + 1]='\'
+        elseif start==end
+            let a:interline[2*start    ]='|'
+        else
+            let a:nodeline[2*end]='+'
+            if start>end
+                let [start, end]=[end, start]
+            endif
+            for i in range(2*start+1, 2*end-1)
+                if a:nodeline[i] isnot# '+'
+                    let a:nodeline[i]='-'
+                endif
+            endfor
+        endif
+    endfor
+endfunction
+"▶2 glog.get_padding_line
+function s:F.glog.get_padding_line(ni, n_columns, edges)
+    let c=' '
+    if index(a:edges, [a:ni, a:ni-1])!=-1 || index(a:edges, [a:ni, a:ni])!=-1
+        let c='|'
+    endif
+    return repeat(['|', ' '], a:ni)+
+                \[c, ' ']+
+                \repeat(['|', " "], (a:n_columns-a:ni-1))
+endfunction
+"▶2 glog.addlines
+function s:F.glog.addlines(text, lnum)
+    let mapexpr='[v:val[0]+'.a:lnum.']+v:val[1:]'
+    call map(a:text.special, 'v:key[-2:] is? "_r"?'.
+                \                  'map(v:val, '.string(mapexpr).'):'.
+                \                  mapexpr)
+    return a:text
+endfunction
+"▶2 glog.addcols
+function s:F.glog.addcols(text, cnum)
+    let mapexpr='[v:val[0], v:val[1]+'.a:cnum.']+v:val[2:]'
+    call map(a:text.special, 'v:key[-2:] is? "_r"?'.
+                \                  'map(v:val, '.string(mapexpr).'):'.
+                \                  mapexpr)
+    return a:text
+endfunction
+"▶2 glog.utf
+function s:F.glog.utf(state, type, char, text, coldata)
+    let [idx, edges, ncols, coldiff]=a:coldata
+    let add_padding_line=0
+    let lnum=len(a:text.text)
+    if coldiff==-1
+        call s:F.glog.fix_long_right_edges(edges)
+        if lnum>2
+            let add_padding_line=!empty(map(filter(copy(edges),
+                        \                          '(v:val[0]+1)<v:val[1]'),
+                        \                   'v:val[0]'))
+        endif
+    endif
+    let fix_nodeline_tail = (lnum<=2 && add_padding_line)
+    let shift_interline=repeat(['|', ' '], idx)
+    let nodeline=copy(shift_interline)+
+                \[a:char, ' ']+
+                \s:F.glog.get_nodeline_edges_tail(idx,     a:state[1], ncols,
+                \                                 coldiff, a:state[0],
+                \                                 fix_nodeline_tail)
+    if coldiff==-1
+        let n_spaces=1
+        let edge_ch='/'
+    elseif coldiff==0
+        let n_spaces=2
+        let edge_ch='|'
+    else
+        let n_spaces=3
+        let edge_ch='\'
+    endif
+    let shift_interline+=repeat([' '], n_spaces)+
+                \        repeat([edge_ch, ' '], (ncols-idx-1))
+    call s:F.glog.draw_edges(edges, nodeline, shift_interline)
+    let lines=[nodeline]
+    if add_padding_line
+        call add(lines, s:F.glog.get_padding_line(idx, ncols, edges))
+    endif
+    if a:text.min
+        let joined_sil=join(shift_interline, '')
+        let joined_nl=substitute(join(nodeline, ''), '\V'.a:char, '|', '')
+        let a:text.text=[]
+        if joined_nl!~#'\v^[o| ]+$'
+            let a:text.text+=[substitute(joined_nl,'\v\-@<=\||\|\-@=','+','')]
+        endif
+        if joined_sil!~#'\v^[| ]+$' && joined_sil isnot# joined_nl
+            let a:text.text+=[joined_sil]
+        endif
+        return a:text
+    else
+        call add(lines, shift_interline)
+    endif
+    let ltdiff=lnum-len(lines)
+    if ltdiff>0
+        let extra_interline=repeat(['|', ' '], ncols+coldiff)
+        call extend(lines, repeat([extra_interline], ltdiff))
+    else
+        call extend(a:text.text, repeat([''], -ltdiff))
+    endif
+    let indentation_level=2*max([ncols, ncols+coldiff])
+    let a:state[0]=coldiff
+    let a:state[1]=idx
+    call map(lines, 'printf("%-*s ", indentation_level, join(v:val, ""))')
+    let curspecial=a:text.special
+    let shiftlen=len(lines[0])
+    call s:F.glog.addcols(a:text, shiftlen)
+    let a:text.block_r=[[0, shiftlen],
+                \       [len(a:text.text)-1,
+                \        max(map(copy(lines), 'len(v:val)'))]]
+    let curspecial.bullet=[0, stridx(lines[0], a:char), a:char]
+    call map(a:text.text, 'lines[v:key].v:val')
+    return a:text
+endfunction
+"▶2 glog.summary
+let s:datechars='YmdHM'
+function s:F.glog.summary(cs, opts)
+    let special={}
+    let text=[]
+    let r={'text': text, 'special': special, 'min': 0}
+    "▶3 Add changeset (and, possibly, branch)
+    let text+=['Changeset '.a:cs.rev.':'.a:cs.hex]
+    let special.changeset_l=[0, 0]
+    if a:cs.branch isnot# 'default'
+        let changesetend=len(text[0])
+        let special.changeset_r=[[0, 0], [0, changesetend]]
+        let text[0].=' (branch '.a:cs.branch.')'
+        let special.branch_r=[[0, changesetend+1], [0, len(text[0])-1]]
+    endif
+    "▶3 Minimize changesets that do not match opts
+    if (has_key(a:opts, 'branch') && a:cs.branch isnot# a:opts.branch) ||
+                \(has_key(a:opts, 'merges') &&
+                \   ((a:opts.merges)?(len(a:cs.parents)<=1):
+                \                    (len(a:cs.parents)>1))) ||
+                \(has_key(a:opts, 'search') &&
+                \   a:cs.description!~#a:opts.search) ||
+                \(has_key(a:opts, 'user') && a:cs.user!~#a:opts.user) ||
+                \(has_key(a:opts, 'revision') &&
+                \   !has_key(a:opts.revisions, a:cs.hex))
+        let r.min=1
+        return r
+    endif
+    "▶4 Date selector
+    if has_key(a:opts, 'date')
+        let i=0
+        for num in split(a:opts.date, '\v[^0-9*.]+')
+            if num isnot# '*'
+                let spec='%'.s:datechars[i]
+                if num is# '.'
+                    let num=str2nr(strftime(spec))
+                else
+                    if i==0 && len(num)==2
+                        let y=str2nr(num)
+                        let cy=str2nr(strftime('%y'))
+                        let c=str2nr(strftime('%Y')[:-3])
+                        if y<=cy
+                            let num=((c*100)+y)
+                        else
+                            let num=(((c-1)*100)+y)
+                        endif
+                    else
+                        let num=str2nr(num)
+                    endif
+                endif
+                if str2nr(strftime(spec, a:cs.time))!=num
+                    let r.min=1
+                    return r
+                endif
+            endif
+            let i+=1
+        endfor
+    endif
+    "▶4 Files selector
+    if has_key(a:opts, 'files')
+        " XXX files list will be used later in section “Add files”
+        let files=a:opts.csfiles[a:cs.hex]
+        if empty(files)
+            let r.min=1
+            return r
+        endif
+    endif
+    "▶3 Add date and user
+    let date=strftime("%d %b %Y %H:%M", a:cs.time)
+    let text+=['Commited '.date.' by '.a:cs.user]
+    "▶4 Specials: commit_l, date_r, user_r
+    let special.commit_l=[1, 0]
+    let commitedlen=stridx(text[-1], ' ')+1
+    let special.date_r=[[1, commitedlen], [1, commitedlen+len(date)]]
+    let special.user_r=[[1, len(text[1])-len(a:cs.user)], [1, len(text[1])-1]]
+    "▶3 Add bookmarks and tags
+    for key in filter(['tags', 'bookmarks'], 'has_key(a:cs, v:val) && '.
+                \                            '!empty(a:cs[v:val])')
+        let upkey=toupper(key[0]).key[1:-2]
+        let newtext=[upkey]
+        if len(a:cs[key])>1
+            let newtext[0].='s: '.join(a:cs[key], ', ')
+        else
+            let newtext[0].=': '.a:cs[key][0]
+        endif
+        let special[key.'_R']=[[len(text),                 0],
+                    \           [len(text)+len(newtext)-1, 0]]
+        let text+=newtext
+    endfor
+    "▶3 Add files
+    if get(a:opts, 'showfiles', 0) &&
+                \!empty(a:opts.repo.functions.getcsprop(a:opts.repo, a:cs,
+                \                                       'files'))
+        let curline=len(text)
+        let ftext=['Files: ']
+        let ww=winwidth(0)-10
+        let lw=s:_r.strdisplaywidth(ftext[-1])
+        let curfilenum=0
+        let fi=0
+        for file in exists('files') ? files : a:cs.files
+            let fw=s:_r.strdisplaywidth(file, lw)
+            if curfilenum && fw+lw>ww
+                let ftext+=['Files: '.file.', ']
+                let lw=s:_r.strdisplaywidth(ftext[-1])
+                let curfilenum=0
+            else
+                let ftext[-1].=file.', '
+                let lw+=fw
+            endif
+            let cl=curline+len(ftext)-1
+            let fl=len(file)
+            let ll=len(ftext[-1])
+            let special['file'.fi.'_r']=[[cl, ll-fl-2], [cl, ll-2]]
+            if exists('files')
+                let fi=index(a:cs.files, file)
+            else
+                let fi+=1
+            endif
+            let curfilenum+=1
+        endfor
+        call map(ftext, 'v:val[:-3]')
+        let special.files_R=[[curline, 0], [curline+len(ftext)-1, 0]]
+        let text+=ftext
+    endif
+    "▶3 Add renames
+    if get(a:opts, 'showrenames', 0)
+        for [cur, old] in items(a:opts.repo.functions.getcsprop(a:opts.repo,
+                    \                                           a:cs,
+                    \                                           'renames'))
+            if type(old)!=type('') ||
+                        \(exists('files') &&
+                        \ !has_key(a:opts.ignorefiles, 'renames') &&
+                        \ index(files, cur)==-1)
+                continue
+            endif
+            let cl=len(text)
+            let fl=len(cur)
+            let ol=len(old)
+            let text+=['Renamed '.old.' to '.cur]
+            let ll=len(text[-1])
+            let fi=index(a:cs.files, cur)
+            let special['file'.fi.'_r']=[[cl, ll-fl], [cl, ll-1]]
+            let special['oldname'.fi.'_r']=[[cl, ll-fl-4-ol], [cl, ll-fl-4]]
+            let special['rename'.fi.'_l']=[cl, 0]
+        endfor
+    endif
+    "▶3 Add description
+    let description=map(split(a:cs.description, "\n", 1), '"  ".v:val')
+    let special.description_R=[[len(text),                    0],
+                \              [len(text)+len(description)-1, 0]]
+    let text+=description
+    "▶3 Add patch and stat
+    let dopatch = get(a:opts, 'patch', 0)
+    let dostat  = get(a:opts, 'stat',  0)
+    if (dopatch || dostat) && !empty(a:cs.parents)
+        let patchfiles=((exists('files') &&
+                    \    !has_key(a:opts.ignorefiles, 'patch'))?
+                    \       (files):
+                    \       ([]))
+        let diff=a:opts.repo.functions.diff(a:opts.repo, a:cs.hex,
+                    \                       a:cs.parents[0], patchfiles, a:opts)
+        if dostat
+            " TODO Put this regular expressions somewhere else
+            let git=get(a:opts, 'git', 0)
+            let stats=s:F.glog.getstats(diff, git ? '\V --git a/\(\.\{-}\) b/'
+                        \                         : '\v\ .*\-r\ \w+\s(.*)$')
+            " TODO Add per-file stat
+            let statsum=printf('#%u files changed, %u insertions, %u deletions',
+                        \      len(keys(stats.files)),
+                        \      stats.insertions, stats.deletions)
+            let special.stat_l=[len(text), 0]
+            let text+=[statsum]
+        endif
+        if dopatch
+            call map(diff, '":".v:val')
+            let special.diff_R=[[len(text), 0], [len(text)+len(diff)-1, 0]]
+            let text+=diff
+        endif
+    endif
+    "▲3
+    let text+=['']
+    return r
+endfunction
+"▶2 glog.generate
+function s:F.glog.generate(css, showparents, Dumper, opts)
+    let seen=[]
+    let state=[0, 0]
+    let r=      {'text': [],
+                \'specials': {},
+                \'rectangles': [],
+                \'csstarts': {},}
+    for cs in a:css
+        let char=((index(a:showparents, cs.hex)==-1)?('o'):('@'))
+        let text=call(a:Dumper, [cs, a:opts], {})
+        call s:F.glog.utf(state, 'C', char, text,
+                    \     s:F.glog.utfedges(seen, cs.hex, cs.parents))
+        if empty(text.text)
+            continue
+        endif
+        if !text.min
+            let text.block_r[0][0]+=len(r.text)
+            let text.block_r[1][0]+=len(r.text)
+            let r.specials[cs.hex]=text.special
+            let r.rectangles+=[text.block_r+[cs.hex]]
+            let r.csstarts[cs.hex]=text.block_r[0][0]
+        endif
+        let r.text+=text.text
+    endfor
+    return r
+endfunction
+"▶2 glog.graphlog
+function s:F.glog.graphlog(repo, opts)
+    let css=reverse(a:repo.cslist[a:opts.revs[0]:a:opts.revs[1]])
+    let a:opts.repo=a:repo
+    return s:F.glog.generate(css, [a:repo.work_hex], s:F.glog.summary, a:opts)
+endfunction
+"▶1 setup
+"▶2 trackfile :: repo, cs, file, csfiles → + csfiles
+function s:F.trackfile(repo, cs, file, csfiles)
+    let tocheck=[[a:file, a:cs]]
+    while !empty(tocheck)
+        let [file, cs]=remove(tocheck, 0)
+        if !has_key(a:csfiles, cs.hex)
+            " TODO Check whether it causes a problem with more then one parent
+            return
+        endif
+        let rename=get(cs.renames, file, 0)
+        if type(rename)!=type('')
+            let rename=file
+        endif
+        if index(a:csfiles[cs.hex], file)==-1
+            let a:csfiles[cs.hex]+=[file]
+        endif
+        let tocheck+=map(copy(cs.parents), '[rename, a:repo.changesets[v:val]]')
+    endwhile
+endfunction
+"▲2
+function s:F.setup(bvar)
+    let bvar=a:bvar
+    if has_key(bvar, 'createdlog')
+        return
+    endif
+    let bvar.createdlog=1
+    call bvar.repo.functions.getchangesets(bvar.repo)
+    "▶2 Add `ignorefiles'
+    let ignorefiles=(has_key(bvar.opts, 'ignfiles')?
+                \               (bvar.opts.ignfiles):
+                \               (s:_f.getoption('ignorefiles')))
+    let bvar.opts.ignorefiles={}
+    call map(copy(ignorefiles), 'extend(bvar.opts.ignorefiles, {v:val : 1})')
+    unlet ignorefiles
+    "▶2 Get revision range
+    if has_key(bvar.opts, 'revrange')
+        let bvar.opts.revs=map(copy(bvar.opts.revrange),
+                    \'bvar.repo.changesets['.
+                    \   'bvar.repo.functions.getrevhex(bvar.repo, v:val)].rev')
+    elseif get(bvar.opts, 'limit', 0)>0
+        let bvar.opts.revs=[bvar.repo.csnum-bvar.opts.limit-1,
+                    \       bvar.repo.csnum-2]
+    else
+        let bvar.opts.revs=[0, bvar.repo.csnum-2]
+    endif
+    "▶2 Process `revision' option
+    if has_key(bvar.opts, 'revision')
+        let hex=bvar.repo.functions.getrevhex(bvar.repo, bvar.opts.revision)
+        let cs=bvar.repo.changesets[hex]
+        if cs.rev<bvar.opts.revs[1]
+            let bvar.opts.revs[1]=cs.rev
+        endif
+        let bvar.opts.revisions={}
+        let addrevs=[cs]
+        while !empty(addrevs)
+            let cs=remove(addrevs, 0)
+            if has_key(bvar.opts.revisions, cs.hex)
+                continue
+            endif
+            let bvar.opts.revisions[cs.hex]=1
+            let addrevs+=map(copy(cs.parents), 'bvar.repo.changesets[v:val]')
+        endwhile
+    endif
+    "▲2
+    let cslist=bvar.repo.cslist[bvar.opts.revs[0]:bvar.opts.revs[1]]
+    "▶2 Generate cs.renames for `showrenames' option
+    if get(bvar.opts, 'showrenames', 0)
+        for cs in cslist
+            call bvar.repo.functions.getcsprop(bvar.repo, cs, 'renames')
+        endfor
+    endif
+    "▶2 Generate cs.files for several options
+    if has_key(bvar.opts, 'files') || get(bvar.opts, 'showrenames', 0) ||
+                \                     get(bvar.opts, 'showfiles',   0)
+        for cs in cslist
+            call bvar.repo.functions.getcsprop(bvar.repo, cs, 'files')
+        endfor
+    endif
+    "▶2 Generate file lists for `files' option
+    if has_key(bvar.opts, 'files')
+        let bvar.opts.csfiles={}
+        for cs in cslist
+            let rns     = bvar.repo.functions.getcsprop(bvar.repo,cs, 'renames')
+            let changes = bvar.repo.functions.getcsprop(bvar.repo,cs, 'changes')
+            let changes = copy(changes)
+            let csfiles = []
+            let bvar.opts.csfiles[cs.hex]=csfiles
+            for pattern in bvar.opts.filepats
+                let newfiles=filter(copy(changes), 'v:val=~#pattern')
+                call filter(changes, 'index(newfiles, v:val)==-1')
+                let csfiles+=newfiles
+                if empty(changes)
+                    break
+                endif
+            endfor
+            call map(copy(csfiles), 's:F.trackfile(bvar.repo, cs, v:val, '.
+                        \                         'bvar.opts.csfiles)')
+        endfor
+        let bvar.opts.totrack={}
+    endif
+    "▲2
+    let text=s:F.glog.graphlog(bvar.repo, bvar.opts)
+    let bvar.specials=text.specials
+    let bvar.rectangles=text.rectangles
+    let bvar.csstarts=text.csstarts
+    let bvar.cw=s:_f.getoption('closewindow')?
+                \       ":wincmd c\n":
+                \       ":new\n"
+    if !bvar.read
+        setlocal noreadonly modifiable
+    endif
+    call setline(1, text.text)
+    if !bvar.read
+        setlocal readonly nomodifiable buftype=nofile
+        augroup AuGlogNoInsert
+            autocmd InsertEnter <buffer> :call feedkeys("\e", "n")
+        augroup END
+    endif
+endfunction
+let s:_augroups+=['AuGlogNoInsert']
+call s:_f.postresource('glog', {'setup': s:F.setup})
+"▶1
+execute frawor#Lockvar(s:, '_r,_pluginloaded')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

plugin/aurum/status.vim

+"▶1 
+scriptencoding utf-8
+if !exists('s:_pluginloaded')
+    execute frawor#Setup('0.0', {'@/resources': '0.0',}, 0)
+    finish
+elseif s:_pluginloaded
+    finish
+endif
+"▶1 setup
+let s:statchars={
+            \ 'deleted': '!',
+            \ 'unknown': '?',
+        \}
+let s:defshow=['modified', 'added', 'removed', 'deleted', 'unknown']
+let s:allshow=s:defshow+['ignored', 'clean']
+let s:showchars={}
+call map(copy(s:statchars), 'extend(s:showchars, {v:val            : v:key})')
+call map(copy(s:allshow),   'extend(s:showchars, {toupper(v:val[0]): v:val})')
+function s:F.setup(bvar)
+    let bvar=a:bvar
+    let status=bvar.repo.functions.status(bvar.repo, get(bvar.opts, 'rev1', 0),
+                \                                    get(bvar.opts, 'rev2', 0))
+    let bvar.types=[]
+    let bvar.chars=[]
+    let bvar.files=[]
+    if has_key(bvar.opts, 'show')
+        if index(bvar.opts.show, 'all')==-1
+            let show=[]
+            for type in bvar.opts.show
+                if type[0]=~#'^\l'
+                    let show+=[type]
+                else
+                    let show+=map(split(type, '\v.@='), 's:showchars[v:val]')
+                endif
+            endfor
+        else
+            let show=s:allshow
+        endif
+    else
+        let show=s:defshow
+    endif
+    let isrecord=get(bvar.opts, 'record', 0)
+    let statlines=[]
+    for [type, files] in filter(items(status), 'index(show, v:val[0])!=-1')
+        let char=has_key(s:statchars, type)? s:statchars[type]: toupper(type[0])
+        for file in files
+            let ignore=0
+            if has_key(bvar.opts, 'files')
+                let ignore=1
+                for pattern in bvar.opts.filepats
+                    if file=~#pattern
+                        let ignore=0
+                        break
+                    endif
+                endfor
+            endif
+            if ignore
+                continue
+            endif
+            let statlines+=[((isrecord)?('-'):('')).char.' '.file]
+            let bvar.types+=[type]
+            let bvar.chars+=[char]
+            let bvar.files+=[file]
+        endfor
+    endfor
+    if empty(statlines)
+        let statlines=['No changes found']
+    endif
+    if bvar.read
+        call append('.', statlines)
+    else
+        call setline('.', statlines)
+        setlocal readonly nomodifiable
+        augroup AuStatusNoInsert
+            autocmd InsertEnter <buffer> :call feedkeys("\e", 'n')
+        augroup END
+    endif
+endfunction
+let s:_augroups+=['AuStatusNoInsert']
+call s:_f.postresource('status', {'setup': s:F.setup})
+"▶1
+call frawor#Lockvar(s:, '_r,_pluginloaded')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

test/annotate-buffers.in

 :W{{{1 file
-:silent edit `=g:curtest.'repo/chgrepo.zsh'`
+:R silent edit chgrepo.zsh
 :AuAnnotate
 :call WriteFile(bufname('%'), bufname(winbufnr(winnr('#'))), bufname(winbufnr(winnr('$'))), 'w$: '.winnr('$').', w0: '.winnr().', w#: '.winnr('#'))
 :bwipeout!