diff --git a/.vim/.netrwhist b/.vim/.netrwhist new file mode 100644 index 0000000..1804996 --- /dev/null +++ b/.vim/.netrwhist @@ -0,0 +1,12 @@ +let g:netrw_dirhistmax =10 +let g:netrw_dirhistcnt =3 +let g:netrw_dirhist_3='/home/sdk' +let g:netrw_dirhist_2='/home/sdk/.vim' +let g:netrw_dirhist_1='sftp://sdk@gopher.codevoid.de/../www/htdocs/gopher/' +let g:netrw_dirhist_0='/home/sdk/code/bcbackup' +let g:netrw_dirhist_9='/home/sdk/code' +let g:netrw_dirhist_8='/home/sdk/code/bar' +let g:netrw_dirhist_7='/home/sdk/code/drist' +let g:netrw_dirhist_6='/home/sdk/code' +let g:netrw_dirhist_5='/home/sdk/.FontForge' +let g:netrw_dirhist_4='/home/sdk/.audacity-data' diff --git a/.vim/autoload/pathogen.vim b/.vim/autoload/pathogen.vim new file mode 100644 index 0000000..3582fbf --- /dev/null +++ b/.vim/autoload/pathogen.vim @@ -0,0 +1,264 @@ +" pathogen.vim - path option manipulation +" Maintainer: Tim Pope +" Version: 2.4 + +" Install in ~/.vim/autoload (or ~\vimfiles\autoload). +" +" For management of individually installed plugins in ~/.vim/bundle (or +" ~\vimfiles\bundle), adding `execute pathogen#infect()` to the top of your +" .vimrc is the only other setup necessary. +" +" The API is documented inline below. + +if exists("g:loaded_pathogen") || &cp + finish +endif +let g:loaded_pathogen = 1 + +" Point of entry for basic default usage. Give a relative path to invoke +" pathogen#interpose() or an absolute path to invoke pathogen#surround(). +" Curly braces are expanded with pathogen#expand(): "bundle/{}" finds all +" subdirectories inside "bundle" inside all directories in the runtime path. +" If no arguments are given, defaults "bundle/{}", and also "pack/{}/start/{}" +" on versions of Vim without native package support. +function! pathogen#infect(...) abort + if a:0 + let paths = filter(reverse(copy(a:000)), 'type(v:val) == type("")') + else + let paths = ['bundle/{}', 'pack/{}/start/{}'] + endif + if has('packages') + call filter(paths, 'v:val !~# "^pack/[^/]*/start/[^/]*$"') + endif + let static = '^\%([$~\\/]\|\w:[\\/]\)[^{}*]*$' + for path in filter(copy(paths), 'v:val =~# static') + call pathogen#surround(path) + endfor + for path in filter(copy(paths), 'v:val !~# static') + if path =~# '^\%([$~\\/]\|\w:[\\/]\)' + call pathogen#surround(path) + else + call pathogen#interpose(path) + endif + endfor + call pathogen#cycle_filetype() + if pathogen#is_disabled($MYVIMRC) + return 'finish' + endif + return '' +endfunction + +" Split a path into a list. +function! pathogen#split(path) abort + if type(a:path) == type([]) | return a:path | endif + if empty(a:path) | return [] | endif + let split = split(a:path,'\\\@]','\\&','') + endif +endfunction + +" Like findfile(), but hardcoded to use the runtimepath. +function! pathogen#runtime_findfile(file,count) abort + let rtp = pathogen#join(1,pathogen#split(&rtp)) + let file = findfile(a:file,rtp,a:count) + if file ==# '' + return '' + else + return fnamemodify(file,':p') + endif +endfunction + +" vim:set et sw=2 foldmethod=expr foldexpr=getline(v\:lnum)=~'^\"\ Section\:'?'>1'\:getline(v\:lnum)=~#'^fu'?'a1'\:getline(v\:lnum)=~#'^endf'?'s1'\:'=': diff --git a/.vim/bundle/vim-diffchar/README.md b/.vim/bundle/vim-diffchar/README.md new file mode 100644 index 0000000..4f0f2a0 --- /dev/null +++ b/.vim/bundle/vim-diffchar/README.md @@ -0,0 +1,120 @@ +# diffchar.vim +*Highlight the exact differences, based on characters and words* +``` + ____ _ ____ ____ _____ _ _ _____ ____ +| | | || || || || | | || _ || _ | +| _ || || __|| __|| || | | || | | || | || +| | | || || |__ | |__ | __|| |_| || |_| || |_||_ +| |_| || || __|| __|| | | || || __ | +| || || | | | | |__ | _ || _ || | | | +|____| |_||_| |_| |_____||_| |_||_| |_||_| |_| +``` + +#### Introduction + +This plugin has been developed in order to make diff mode more useful. Vim +highlights all the text in between the first and last different characters on +a changed line. But this plugin will find the exact differences between them, +character by character - so called *DiffChar*. + +For example, in diff mode: +![example1](example1.png) + +This plugin will exactly show the changed and added units: +![example2](example2.png) + +This plugin will synchronously show/reset the highlights of the exact +differences as soon as the diff mode begins/ends. And the exact differences +will be kept updated while editing. + +This plugin shows the differences based on a `g:DiffUnit`. Its default is +'Word1' and it handles a `\w\+` word and a `\W` character as a difference unit. +There are other types of word provided and you can also set 'Char' to compare +character by character. + +In diff mode, the corresponding `hl-DiffChange` lines are compared between two +windows. You can set a number of matching colors to a `g:DiffColors` to make +it easy to find the corresponding units between two windows. As a default, all +the changed units are highlighted with `hl-DiffText`. In addition, +`hl-DiffAdd` is always used for the added units and both the previous and next +character of the deleted units are shown in bold/underline. + +While showing the exact differences, when the cursor is moved on a difference +unit, you can see its corresponding unit highlighted with `hl-Cursor`, +`hl-TermCursor`, or similar one in another window, based on a +`g:DiffPairVisible`. If you change its default, the corresponding unit is +echoed in the command line or displayed in a popup/floating window just below +the cursor position or at the mouse position. + +You can use `]b` or `]e` to jump cursor to start or end position of the next +difference unit, and `[b` or `[e` to the start or end position of the previous +unit. Those keymaps are configurable in your vimrc and so on. + +Like line-based `:diffget`/`:diffput` and `do`/`dp` vim commands, you can use +`g` and `p` commands in normal mode to get and put each +difference unit, where the cursor is on, between 2 buffers and undo its +difference. + +When the diff mode begins, this plugin locally checks the `hl-DiffChange` +lines in the limited range of the current visible and its upper/lower lines of +a window. And each time a cursor is moved on to a different range upon +scrolling or searching, the new `hl-DiffChange` lines will be incrementally +checked in that range. Which means, independently of the file size, the number +of lines to be checked and then the time consumed are always constant. + +This plugin works on each tab page individually. You can use a tab page +variable (t:), instead of a global one (g:), to specify different options on +each tab page. Note that this plugin can not handle more than two diff mode +windows in a tab page. If it would happen, to prevent any trouble, all the +highlighted units are to be reset in the tab page. + +To find the exact differences, this plugin uses "An O(NP) Sequence Comparison +Algorithm" developed by S.Wu, et al., which always finds an optimum sequence. +But it takes time to check a long and dissimilar line. To improve the +performance, if there are so many diff units included in a line or it has +taken much time in a diff session, this plugin tries to use the external diff +command together if available. + +#### Options + +* `g:DiffUnit`, `t:DiffUnit`: + A type of difference unit + * 'Char' : any single character + * 'Word1' : `\w\+` word and any `\W` single character (default) + * 'Word2' : non-space and space words + * 'Word3' : `\<` or `\>` character class boundaries + * 'CSV(,)' : separated by characters such as ',', ';', and '\t' + +* `g:DiffColors`, `t:DiffColors`: + Matching colors for changed units + * 0 : `hl-DiffText` (default) + * 1 : `hl-DiffText` + up to 3 other highlights + * 2 : `hl-DiffText` + up to 7 other highlights + * 3 : `hl-DiffText` + up to 15 other highlights + +* `g:DiffPairVisible`, `t:DiffPairVisible`: + Visibility of corresponding diff units + * 0 : disable + * 1 : highlight with `hl-Cursor` (default) + * 2 : highlight with `hl-Cursor` + echo in the command line + * 3 : highlight with `hl-Cursor` + popup/floating window at cursor position + * 4 : highlight with `hl-Cursor` + popup/floating window at mouse position + +#### Keymaps + +* `JumpDiffCharPrevStart` (default: `[b`) + * Jump cursor to the start position of the previous difference unit +* `JumpDiffCharNextStart` (default: `]b`) + * Jump cursor to the start position of the next difference unit +* `JumpDiffCharPrevEnd` (default: `[e`) + * Jump cursor to the end position of the previous difference unit +* `JumpDiffCharNextEnd` (default: `]e`) + * Jump cursor to the end position of the next difference unit +* `GetDiffCharPair` (default: `g`) + * Get a corresponding difference unit from another buffer to undo difference +* `PutDiffCharPair` (default: `p`) + * Put a corresponding difference unit to another buffer to undo difference + +#### Demo + +![demo](demo.gif) diff --git a/.vim/bundle/vim-diffchar/autoload/diffchar.vim b/.vim/bundle/vim-diffchar/autoload/diffchar.vim new file mode 100644 index 0000000..ce1e411 --- /dev/null +++ b/.vim/bundle/vim-diffchar/autoload/diffchar.vim @@ -0,0 +1,1658 @@ +" diffchar.vim: Highlight the exact differences, based on characters and words +" +" ____ _ ____ ____ _____ _ _ _____ ____ +" | | | || || || || | | || _ || _ | +" | _ || || __|| __|| || | | || | | || | || +" | | | || || |__ | |__ | __|| |_| || |_| || |_||_ +" | |_| || || __|| __|| | | || || __ | +" | || || | | | | |__ | _ || _ || | | | +" |____| |_||_| |_| |_____||_| |_||_| |_||_| |_| +" +" Last Change: 2021/12/07 +" Version: 8.91 +" Author: Rick Howe (Takumi Ohtani) +" Copyright: (c) 2014-2021 by Rick Howe + +let s:save_cpo = &cpoptions +set cpo&vim + +" Vim feature, function, event and patch number which this plugin depends on +" patch-8.0.736: OptionSet event triggered with diff option +" patch-8.0.794: count() fixed to accept a string +" patch-8.0.914: nocombine attribute available +" patch-8.0.1038: strikethrough attribute available +" patch-8.0.1160: gettabvar() fixed not to return empty +" patch-8.0.1290: changenr() fixed to return correct value +" patch-8.1.414: v:option fixed in OptionSet diff autocmd +" patch-8.1.1084: window ID argument available in all match functions +" patch-8.1.1832: win_execute() fixed to work in other tabpage +let s:VF = { + \'DiffUpdated': exists('##DiffUpdated'), + \'WinScrolled': exists('##WinScrolled'), + \'WinClosed': exists('##WinClosed'), + \'GUIColors': has('gui_running') || + \has('termguicolors') && &termguicolors, + \'DiffExecutable': executable('diff'), + \'PopupWindow': has('popupwin'), + \'FloatingWindow': exists('*nvim_create_buf'), + \'GetMousePos': exists('*getmousepos'), + \'WinExecute': exists('*win_execute') && + \(has('patch-8.1.1832') || has('nvim-0.5.0')), + \'DiffOptionSet': has('patch-8.0.736'), + \'CountString': has('patch-8.0.794'), + \'StrikeAttr': has('patch-8.0.1038') && + \(has('gui_running') || !empty(&t_Ts) && !empty(&t_Te)), + \'GettabvarFixed': has('patch-8.0.1160'), + \'ChangenrFixed': has('patch-8.0.1290'), + \'VOptionFixed': has('patch-8.1.414') || has('nvim-0.3.2'), + \'WinIDinMatch': has('patch-8.1.1084') || has('nvim-0.5.0'), + \'NvimDiffHLID': has('nvim') && !has('nvim-0.4.0')} + +function! s:SetDiffCharHL() abort + " check vim original Diff highlights and set attributes for changes + let s:DiffHL = {} + for [hs, hl] in [['A', 'DiffAdd'], ['C', 'DiffChange'], + \['D', 'DiffDelete'], ['T', 'DiffText']] + let dh = {} + let hn = has('nvim') ? '' : + \matchstr(split(&highlight, ','), '^' . hs . '\C:') + let dh.id = hlID(empty(hn) ? hl : hn[2:]) + let dh.it = synIDtrans(dh.id) " in case of linked + let dh.nm = synIDattr(dh.it, 'name') + " dh: 0 = original, 1 = for single color, 2 = for multi color + let dh.0 = {} + for hm in ['term', 'cterm', 'gui'] + for hc in ['fg', 'bg', 'sp'] + let dh.0[hm . hc] = synIDattr(dh.it, hc, hm) + endfor + let dh.0[hm] = join(filter(['bold', 'underline', 'undercurl', + \'strikethrough', 'reverse', 'inverse', 'italic', 'standout'], + \'!empty(synIDattr(dh.it, v:val, hm))'), ',') + endfor + call filter(dh.0, '!empty(v:val)') + let dh.1 = (hs == 'C' || hs == 'T') ? + \filter(copy(dh.0), 'v:key =~ "bg$"') : dh.0 + let dh.2 = (hs == 'T') ? {} : dh.1 + " diff_hlID() incorrectly returns (hlID() - 1) until nvim 0.4.0 + if s:VF.NvimDiffHLID | let dh.id -= 1 | endif + let s:DiffHL[hs] = dh + endfor + " set DiffChar specific highlights + let s:DCharHL = {'A': 'DiffAdd', 'D': 'DiffDelete', 'n': 'LineNr'} + if has('nvim') + let s:DCharHL.c = 'TermCursor' + else + let s:DCharHL.c = 'Cursor' + if !s:VF.GUIColors + let id = 1 + while 1 + let nm = synIDattr(id, 'name') + if empty(nm) | break | endif + if id == synIDtrans(id) && !empty(synIDattr(id, 'reverse')) && + \empty(filter(['fg', 'bg', 'sp', 'bold', 'underline', + \'undercurl', 'strikethrough', 'italic', 'standout'], + \'!empty(synIDattr(id, v:val))')) + let s:DCharHL.c = nm + break + endif + let id += 1 + endwhile + endif + endif + for [fs, ts, th, ta] in [['C', 'C', 'dcDiffChange', ''], + \['T', 'T', 'dcDiffText', ''], + \['C', 'E', 'dcDiffErase', 'bold,underline']] + + \(s:VF.StrikeAttr ? [['D', 'D', 'dcDiffDelete', 'strikethrough']] : []) + let fa = copy(s:DiffHL[fs].0) + if !empty(ta) + for hm in ['term', 'cterm', 'gui'] + let fa[hm] = has_key(fa, hm) ? fa[hm] . ',' . ta : ta + endfor + endif + call execute(['highlight clear ' . th, + \'highlight ' . th . ' ' . + \join(map(items(fa), 'join(v:val, "=")'))]) + let s:DCharHL[ts] = th + endfor + " change diff highlights according to current DChar + call s:ToggleDiffHL(exists('t:DChar')) +endfunction + +function! s:InitializeDiffChar() abort + " select current and next diff mode windows whose buffer is different + " do no initiate if more than 2 diff mode windows exist in a tab page and + " if a selected buffer already DChar highlighted in other tab pages + let cw = win_getid() + let cb = winbufnr(cw) + let nw = filter(map(range(winnr() + 1, winnr('$')) + + \range(1, winnr() - 1), 'win_getid(v:val)'), + \'getwinvar(v:val, "&diff") && winbufnr(v:val) != cb') + let nb = map(copy(nw), 'winbufnr(v:val)') + if !getwinvar(cw, '&diff') || empty(nw) || min(nb) != max(nb) + return -1 + endif + for tn in filter(range(1, tabpagenr('$')), 'v:val != tabpagenr()') + let dc = s:Gettabvar(tn, 'DChar') + if !empty(dc) + for bn in values(dc.bnr) + if index([cb, nb[0]], bn) != -1 + call s:EchoWarning('Both or either selected buffer already + \ highlighted in tab page ' . tn . '!') + return -1 + endif + endfor + endif + endfor + " set diffchar highlights + call s:SetDiffCharHL() + " define a DiffChar dictionary on this tab page + let t:DChar = {} + " windowID and bufnr + let t:DChar.wid = {'1': cw, '2': nw[0]} + let t:DChar.bnr = {'1': cb, '2': nb[0]} + " diff mode synchronization flag + let t:DChar.dsy = get(g:, 'DiffModeSync', 1) + " a multiple of the current visible page to locally detect diff lines + let t:DChar.dfp = get(g:, 'DiffFocalPages', 3) + " top/bottom/last lines, cursor line/column, changenr on each window + let t:DChar.lcc = s:GetLineColCnr(1) + " a list of diff focus lines + let t:DChar.dfl = s:FocusDiffLines(1, 1) + " a type of diff pair visible + let pv = get(t:, 'DiffPairVisible', g:DiffPairVisible) + if (pv == 3 || pv == 4) && !(s:VF.PopupWindow || s:VF.FloatingWindow) || + \pv == 4 && !s:VF.GetMousePos + let pv = 1 + endif + let t:DChar.dpv = {'pv': pv} + if 0 < pv + let t:DChar.dpv.ch = {} + if pv == 3 || pv == 4 + let t:DChar.dpv.pw = s:VF.PopupWindow ? 0 : + \s:VF.FloatingWindow ? {'fb': -1, 'fw': -1} : -1 + endif + endif + " a list of highlight IDs per line + let t:DChar.mid = {'1': {}, '2': {}} + " a list of added/deleted/changed columns per line + let t:DChar.hlc = {'1': {}, '2': {}} + " checksum per line + let t:DChar.cks = {'1': {}, '2': {}} + " ignorecase and ignorespace flags + let do = split(&diffopt, ',') + let t:DChar.igc = (index(do, 'icase') != -1) + let t:DChar.igs = (index(do, 'iwhiteall') != -1) ? 1 : + \(index(do, 'iwhite') != -1) ? 2 : + \(index(do, 'iwhiteeol') != -1) ? 3 : 0 + " a pattern to split difference units + let du = get(t:, 'DiffUnit', g:DiffUnit) + if du == 'Char' " any single character + let t:DChar.upa = '\zs' + elseif du == 'Word2' " non-space and space words + let t:DChar.upa = '\%(\s\+\|\S\+\)\zs' + elseif du == 'Word3' " \< or \> boundaries + let t:DChar.upa = '\<\|\>' + elseif du =~ '^CSV(.\+)$' " split characters + let s = escape(du[4 : -2], '^-]') + let t:DChar.upa = '\%([^'. s . ']\+\|[' . s . ']\)\zs' + elseif du =~ '^SRE(.\+)$' " split regular expression + let t:DChar.upa = du[4 : -2] + else + " \w\+ word and any \W character + let t:DChar.upa = '\%(\w\+\|\W\)\zs' + if du != 'Word1' + call s:EchoWarning('Not a valid difference unit type. + \ Use "Word1" instead.') + endif + endif + " a list of difference matching colors + let t:DChar.hgp = [s:DCharHL.T] + let dc = get(t:, 'DiffColors', g:DiffColors) + if 1 <= dc && dc <= 4 + " select all available hl which has bg and has not attribute + let [fd, bd] = map(['fg#', 'bg#'], 'synIDattr(hlID("Normal"), v:val)') + let id = 1 + while empty(fd) || empty(bd) + let nm = synIDattr(id, 'name') + if empty(nm) | break | endif + if id == synIDtrans(id) + if empty(fd) && synIDattr(id, 'bg') == 'fg' + let fd = synIDattr(id, 'bg#') + endif + if empty(bd) && synIDattr(id, 'fg') == 'bg' + let bd = synIDattr(id, 'fg#') + endif + endif + let id += 1 + endwhile + let xb = map(values(s:DCharHL), 'synIDattr(hlID(v:val), "bg#")') + let hl = {} + let id = 1 + while 1 + let nm = synIDattr(id, 'name') + if empty(nm) | break | endif + if id == synIDtrans(id) + let [fg, bg, rv] = map(['fg#', 'bg#', 'reverse'], + \'synIDattr(id, v:val)') + if empty(fg) | let fg = fd | endif + if !empty(rv) | let bg = !empty(fg) ? fg : fd | endif + if !empty(bg) && bg != fg && bg != bd && + \index(xb, bg) == -1 && empty(filter(map(['bold', + \'underline', 'undercurl', 'strikethrough', 'italic', + \'standout'], + \'synIDattr(id, v:val)'), '!empty(v:val)')) + let hl[bg] = nm + endif + endif + let id += 1 + endwhile + let t:DChar.hgp += values(hl)[: ((dc == 1) ? 2 : (dc == 2) ? 6 : + \(dc == 3) ? 14 : -1)] + elseif dc == 100 + let hl = {} + let id = 1 + while 1 + let nm = synIDattr(id, 'name') + if empty(nm) | break | endif + if index(values(s:DCharHL), nm) == -1 && id == synIDtrans(id) && + \!empty(filter(['fg', 'bg', 'sp', 'bold', 'underline', + \'undercurl', 'strikethrough', 'reverse', 'inverse', + \'italic', 'standout'], + '!empty(synIDattr(id, v:val))')) + let hl[reltimestr(reltime())[-2 :] . id] = nm + endif + let id += 1 + endwhile + let t:DChar.hgp += values(hl) + elseif -3 <= dc && dc <= -1 + let t:DChar.hgp += ['SpecialKey', 'Search', 'CursorLineNr', + \'Visual', 'WarningMsg', 'StatusLineNC', 'MoreMsg', + \'ErrorMsg', 'LineNr', 'Conceal', 'NonText', + \'ColorColumn', 'ModeMsg', 'PmenuSel', 'Title'] + \[: ((dc == -1) ? 2 : (dc == -2) ? 6 : -1)] + endif +endfunction + +function! diffchar#ToggleDiffChar(lines) abort + if exists('t:DChar') + for k in [1, 2, 0] + if k == 0 | return | endif + if t:DChar.wid[k] == win_getid() | break | endif + endfor + for hl in keys(t:DChar.hlc[k]) + if index(a:lines, eval(hl)) != -1 + call diffchar#ResetDiffChar(a:lines) + return + endif + endfor + endif + call diffchar#ShowDiffChar(a:lines) +endfunction + +function! diffchar#ShowDiffChar(...) abort + let init = !exists('t:DChar') + if init && s:InitializeDiffChar() == -1 | return | endif + for ak in [1, 2, 0] + if ak == 0 | return | endif + if t:DChar.wid[ak] == win_getid() | break | endif + endfor + let dl = [] + for n in filter(map(copy(t:DChar.dfl[ak]), + \'(a:0 && index(a:1, v:val) == -1) ? -1 : v:key'), 'v:val != -1') + let [l1, l2] = [t:DChar.dfl[1][n], t:DChar.dfl[2][n]] + if !has_key(t:DChar.hlc[1], l1) && !has_key(t:DChar.hlc[2], l2) + let dl += [[{l1: getbufline(t:DChar.bnr[1], l1)[0]}, + \{l2: getbufline(t:DChar.bnr[2], l2)[0]}]] + endif + endfor + if !init && empty(dl) | return | endif + let save_igc = &ignorecase | let &ignorecase = t:DChar.igc + let uu = [] + for n in range(len(dl)) + let [d1, d2] = dl[n] + for k in [1, 2] + let t = values(d{k})[0] + if t:DChar.igs + let u{k} = split(substitute(t, '\s\+$', '', ''), t:DChar.upa) + if t:DChar.igs == 1 " iwhiteall + call filter(u{k}, 'v:val !~ "^\\s\\+$"') + elseif t:DChar.igs == 2 " iwhite + let s = len(u{k}) - 1 + while 0 < s + if u{k}[s - 1] . u{k}[s] =~ '^\s\+$' + let u{k}[s - 1] .= u{k}[s] + unlet u{k}[s] + endif + let s -= 1 + endwhile + endif + else + let u{k} = split(t, t:DChar.upa) + endif + endfor + if u1 == u2 | let dl[n] = [] + else | let uu += [[u1, u2]] + endif + endfor + call filter(dl, '!empty(v:val)') + let es = [] + if s:VF.DiffExecutable + let [mu, mt, st] = [get(g:, 'DiffIntMaxUnits', 2000), + \get(g:, 'DiffIntMaxTime', 1.0), reltime()] + endif + for n in range(len(uu)) + let [u1, u2] = uu[n] + if s:VF.DiffExecutable && + \(mu < len(u1) + len(u2) || mt < reltimefloat(reltime(st))) + " the next line includes 2000+ units OR already spent 1.0+s + let es += s:ExecDiffCommand(uu[n :]) + break + endif + if t:DChar.igs == 2 " iwhite + for k in [1, 2] + let u{k} = map(copy(u{k}), + \'(v:val =~ "^\\s\\+$") ? " " : v:val') + endfor + endif + let es += [s:TraceDiffChar(u1, u2)] + endfor + let lc = {'1': {}, '2': {}} + for n in range(len(dl)) + let [d1, d2] = dl[n] + let [c1, c2] = s:GetDiffUnitPos(es[n], uu[n]) + for k in [1, 2] + let [l, t] = items(d{k})[0] + if t:DChar.igs == 1 " iwhiteall + if t =~ '\s\+' + let ap = filter(range(1, len(t)), 't[v:val - 1] !~ "\\s"') + call map(c{k}, '[v:val[0], + \[ap[v:val[1][0] - 1], ap[v:val[1][1] - 1]]]') + endif + endif + let lc[k][l] = c{k} + let t:DChar.cks[k][l] = s:ChecksumStr(t) + endfor + endfor + let &ignorecase = save_igc + call s:HighlightDiffChar(ak, lc) + if !t:DChar.dsy && index(values(t:DChar.hlc), {}) != -1 + unlet t:DChar + else + if init " set event when DChar HL is newly defined + call s:ToggleDiffCharEvent(1) + call s:ToggleDiffHL(1) + call s:ToggleDiffCharPair(1) + endif + if 0 < t:DChar.dpv.pv | call s:ShowDiffCharPair(ak) | endif + endif +endfunction + +function! s:ExecDiffCommand(uu) abort + " prepare 2 input files for diff + for [k, u] in [[1, 0], [2, 1]] + " insert '|:' before each line and + " add '=:' at the beginning of each unit + let u{k} = [''] " a dummy to avoid 1st null unit error + for n in range(len(a:uu)) + let u{k} += ['|' . n . ':'] + + \map(copy(a:uu[n][u]), '"=" . n . ":" . v:val') + endfor + let f{k} = tempname() + call writefile(u{k}, f{k}) + endfor + " call diff in unified format and assign edit symbols [=+-] to each unit + let dc = ['diff', '-a', '--binary', ((t:DChar.igc == 1) ? '-i' : ''), + \((t:DChar.igs == 1) ? '-w' : (t:DChar.igs == 2) ? '-b' : + \(t:DChar.igs == 3) ? '-Z' : ''), + \'-d', '-U', len(u1) + len(u2), f1, f2] + let save_stmp = &shelltemp + let &shelltemp = 0 + let dt = systemlist(join(dc)) + let &shelltemp = save_stmp + for k in [1, 2] | call delete(f{k}) | endfor + return split(join(map(filter(dt, 'v:val =~ "^[ +-][|=]"'), + \'(v:val[0] != " ") ? v:val[0] : v:val[1]'), ''), '|') +endfunction + +function! s:GetDiffUnitPos(es, uu) abort + let [u1, u2] = a:uu + if empty(u1) + return [[['d', [0, 0]]], [['a', [1, len(join(u2, ''))]]]] + elseif empty(u2) + return [[['a', [1, len(join(u1, ''))]]], [['d', [0, 0]]]] + endif + let [c1, c2] = [[], []] + let [l1, l2, p1, p2] = [1, 1, 0, 0] + for ed in split(a:es, '[+-]\+\zs', 1)[: -2] + let [qe, q1, q2] = [s:CountChar(ed, '='), s:CountChar(ed, '-'), + \s:CountChar(ed, '+')] + for k in [1, 2] + if 0 < qe + let [l{k}, p{k}] += + \[len(join(u{k}[p{k} : p{k} + qe - 1], '')), qe] + endif + if 0 < q{k} + let l = l{k} + let [l{k}, p{k}] += + \[len(join(u{k}[p{k} : p{k} + q{k} - 1], '')), q{k}] + let h{k} = [l, l{k} - 1] + else + let h{k} = [ + \l{k} - ((0 < p{k}) ? len(strcharpart(u{k}[p{k} - 1], + \strchars(u{k}[p{k} - 1]) - 1, 1)) : 0), + \l{k} + ((p{k} < len(u{k})) ? + \len(strcharpart(u{k}[p{k}], 0, 1)) - 1 : -1)] + endif + endfor + let [r1, r2] = (q1 == 0) ? ['d', 'a'] : + \(q2 == 0) ? ['a', 'd'] : ['c', 'c'] + let [c1, c2] += [[[r1, h1]], [[r2, h2]]] + endfor + return [c1, c2] +endfunction + +function! s:TraceDiffChar(u1, u2) abort + " An O(NP) Sequence Comparison Algorithm + let [n1, n2] = [len(a:u1), len(a:u2)] + if a:u1 == a:u2 | return repeat('=', n1) + elseif n1 == 0 | return repeat('+', n2) + elseif n2 == 0 | return repeat('-', n1) + endif + " reverse to be N >= M + let [N, M, u1, u2, e1, e2] = (n1 >= n2) ? + \[n1, n2, a:u1, a:u2, '+', '-'] : [n2, n1, a:u2, a:u1, '-', '+'] + let D = N - M + let fp = repeat([-1], M + N + 1) + let etree = [] " [next edit, previous p, previous k] + let p = -1 + while fp[D] != N + let p += 1 + let epk = repeat([[]], p * 2 + D + 1) + for k in range(-p, D - 1, 1) + range(D + p, D, -1) + let [y, epk[k]] = (fp[k - 1] < fp[k + 1]) ? + \[fp[k + 1], [e1, (k < D) ? p - 1 : p, k + 1]] : + \[fp[k - 1] + 1, [e2, (k > D) ? p - 1 : p, k - 1]] + let x = y - k + while x < M && y < N && u2[x] == u1[y] + let epk[k][0] .= '=' + let [x, y] += [1, 1] + endwhile + let fp[k] = y + endfor + let etree += [epk] + endwhile + " create a shortest edit script (SES) from last p and k + let ses = '' + while 1 + let ses = etree[p][k][0] . ses + if p == 0 && k == 0 | return ses[1 :] | endif + let [p, k] = etree[p][k][1 : 2] + endwhile +endfunction + +function! diffchar#ResetDiffChar(...) abort + if !exists('t:DChar') | return | endif + let last = (a:0 && type(a:1) == type(0)) ? a:1 : 0 + for k in [1, 2, 0] + if k == 0 | return | endif + if t:DChar.wid[k] == win_getid() | break | endif + endfor + let dl = {'1': [], '2': []} + for n in filter(map(copy(t:DChar.dfl[k]), + \'(a:0 && type(a:1) == type([]) && + \index(a:1, v:val) == -1) ? -1 : v:key'), 'v:val != -1') + let [l1, l2] = [t:DChar.dfl[1][n], t:DChar.dfl[2][n]] + if has_key(t:DChar.hlc[1], l1) || has_key(t:DChar.hlc[2], l2) + let [dl[1], dl[2]] += [[l1], [l2]] + unlet t:DChar.cks[1][l1] | unlet t:DChar.cks[2][l2] + endif + endfor + if !last && empty(dl[k])| return | endif + call s:ClearDiffChar(k, dl) + if 0 < t:DChar.dpv.pv | call s:ClearDiffCharPair(k) | endif + if last || !t:DChar.dsy && index(values(t:DChar.hlc), {}) != -1 + call s:ToggleDiffHL(0) + call s:ToggleDiffCharPair(0) + unlet t:DChar + call s:ToggleDiffCharEvent(0) + endif +endfunction + +function! s:ToggleDiffCharEvent(on) abort + call execute(g:DiffCharInitEvent) + let tv = filter(map(range(1, tabpagenr('$')), + \'s:Gettabvar(v:val, "DChar")'), '!empty(v:val)') + if empty(tv) | return | endif + let ac = [] + for td in tv + for k in [1, 2] + let bl = '' + if td.dsy + let ac += [['TextChanged,InsertLeave', bl, + \'s:UpdateDiffChar(' . k . ', 0)']] + if s:VF.DiffUpdated + let ac += [['DiffUpdated', bl, + \'s:UpdateDiffChar(' . k . ', 1)']] + endif + let ac += [[s:VF.WinClosed ? 'WinClosed' : 'BufWinLeave', bl, + \'s:WinClosedDiffChar()']] + if td.dfp != 0 + let ac += [[ + \s:VF.WinScrolled ? 'WinScrolled' : 'CursorMoved', bl, + \'s:ScrollDiffChar(' . k . ')']] + endif + endif + if 0 < td.dpv.pv + let ac += [['CursorMoved', bl, + \'s:ShowDiffCharPair(' . k . ')']] + endif + endfor + endfor + let ac += [['TabEnter', '*', 's:AdjustGlobalOption()']] + let ac += [['ColorScheme', '*', 's:SetDiffCharHL()']] + let ac += [[s:VF.WinClosed ? 'BufWinEnter' : 'BufWinEnter,WinEnter', '*', + \'s:RepairDiffChar()']] + if !s:VF.DiffUpdated + if s:VF.DiffOptionSet + let ac += [['OptionSet', 'diffopt', 's:FollowDiffOption()']] + elseif !empty(filter(tv, 'v:val.dsy')) + let s:save_ch = 's:ResetDiffModeSync()' + let ac += [['CursorHold', '*', s:save_ch]] + if !a:on | unlet s:save_ch | endif + call s:ChangeUTOpt(a:on) + endif + endif + call execute(map(ac, '"autocmd diffchar " . v:val[0] . " " . v:val[1] . + \" call " . v:val[2]')) +endfunction + +function! s:FocusDiffLines(key, init) abort + " a:init : initialize dfl (do not use previous dfl) + let dfl = {} + let ks = (a:key == 1) ? [2, 1] : [1, 2] + " check all diff lines + if t:DChar.dfp == 0 + if a:init + for k in ks + call s:WinGotoID(t:DChar.wid[k]) + call s:WinExecute('let dfl[k] = + \s:GetDiffLines(1, t:DChar.lcc[k].ll)') + endfor + return dfl + else + return t:DChar.dfl + endif + endif + " check visible diff lines and merge with previous dfl + " 1. get dfl in current visible lines and return if no new in both wins + for k in ks + call s:WinGotoID(t:DChar.wid[k]) + call s:WinExecute('let dfl[k] = + \s:GetDiffLines(t:DChar.lcc[k].tl, t:DChar.lcc[k].bl)') + endfor + if !a:init + let nd = 0 + for k in ks + if !empty(dfl[k]) + let [ti, bi] = [index(t:DChar.dfl[k], dfl[k][0]), + \index(t:DChar.dfl[k], dfl[k][-1])] + let nd += (ti == -1) || (bi == -1) || + \(t:DChar.dfl[k][ti : bi] != dfl[k]) + endif + endfor + if nd == 0 | return t:DChar.dfl | endif + let lh = {} + for k in ks + let lh[k] = empty(dfl[k]) ? {'l': 0, 'h': 0} : + \{'l': len(filter(copy(t:DChar.dfl[k]), 'v:val < dfl[k][0]')), + \'h': len(filter(copy(t:DChar.dfl[k]), 'dfl[k][-1] < v:val'))} + endfor + endif + " 2. get dfl in upper/lower lines and return if not found in both wins + for k in ks + let [fl, tl, bl, ll] = + \[1, t:DChar.lcc[k].tl, t:DChar.lcc[k].bl, t:DChar.lcc[k].ll] + if !a:init && 0 < t:DChar.dfp + if 0 < lh[k].l | let fl = t:DChar.dfl[k][lh[k].l - 1] + 1 | endif + if 0 < lh[k].h | let ll = t:DChar.dfl[k][-lh[k].h] - 1 | endif + endif + let [tz, bz] = [tl, bl] + let rc = min([(tl - fl) + (ll - bl), + \(abs(t:DChar.dfp) - 1) * (bl - tl + 1)]) + if 0 < rc + let hc = (rc + 1) / 2 + let [tr, br] = [tl - fl, ll - bl] + let tb = [hc <= tr, rc - hc <= br] + let [tc, bc] = (tb == [1, 1]) ? [hc, rc - hc] : (tb == [0, 1]) ? + \[tr, rc - tr] : (tb == [1, 0]) ? [rc - br, br] : [tr, br] + let [tz, bz] += [-tc, bc] + endif + call s:WinGotoID(t:DChar.wid[k]) + call s:WinExecute('let dfl[k] = s:GetDiffLines(tz, tl - 1) + dfl[k] + + \s:GetDiffLines(bl + 1, bz)') + endfor + if empty(dfl[1]) && empty(dfl[2]) | return dfl | endif + if index(values(dfl), []) != -1 + " 3. if no dfl found in either, set a reference line and search dfl + let [ek, fk] = empty(dfl[1]) ? [1, 2] : [2, 1] + let rl = {} + let [rl[ek], rl[fk]] = [{'l': 1, 'h': t:DChar.lcc[ek].ll}, + \{'l': 1, 'h': t:DChar.lcc[fk].ll}] + if !a:init + if 0 < lh[fk].l + let [rl[ek].l, rl[fk].l] = [t:DChar.dfl[ek][lh[fk].l - 1], + \t:DChar.dfl[fk][lh[fk].l - 1]] + endif + if 0 < lh[fk].h + let [rl[ek].h, rl[fk].h] = [t:DChar.dfl[ek][-lh[fk].h], + \t:DChar.dfl[fk][-lh[fk].h]] + endif + endif + let sd = dfl[fk][0] - rl[fk].l < rl[fk].h - dfl[fk][-1] + call s:WinGotoID(t:DChar.wid[fk]) + call s:WinExecute('let dc = len(sd ? + \s:GetDiffLines(rl[fk].l, dfl[fk][0] - 1) : + \s:GetDiffLines(dfl[fk][-1] + 1, rl[fk].h))') + let fc = len(dfl[fk]) + call s:WinGotoID(t:DChar.wid[ek]) + call s:WinExecute('let dfl[ek] = + \s:SearchDiffLines(sd, sd ? rl[ek].l : rl[ek].h, dc + fc)') + let dfl[ek] = sd ? dfl[ek][-fc :] : dfl[ek][: fc - 1] + call s:WinGotoID(t:DChar.wid[ks[1]]) + else + " 4. set reference lines using the closest line of previous dfl + let rl = [[1, 1], [t:DChar.lcc[1].ll, t:DChar.lcc[2].ll]] + if !a:init && index(values(t:DChar.dfl), []) == -1 + let rx = [] + for k in ks + let rx += ((0 < lh[k].l) ? [lh[k].l - 1] : [0]) + + \((0 < lh[k].h) ? [-lh[k].h] : [-1]) + endfor + let rl += map(rx, + \'[t:DChar.dfl[1][v:val], t:DChar.dfl[2][v:val]]') + endif + " 5. select a reference line which is closest to new dfl + let ds = map(copy(rl), 'abs(v:val[0] - (dfl[1][0] + dfl[1][-1]) / 2) + + \abs(v:val[1] - (dfl[2][0] + dfl[2][-1]) / 2)') + let ci = index(ds, min(ds)) + let cl = {'1': rl[ci][0], '2': rl[ci][1]} + " 6. get # of dfl (+/-) between reference and top/bottom lines + let tb = {} + for k in ks + let [tl, bl, xl] = [dfl[k][0], dfl[k][-1], cl[k]] + let td = (tl <= xl) ? [tl, xl, 1] : [xl, tl, -1] + let bd = (bl <= xl) ? [bl, xl, 1] : [xl, bl, -1] + call s:WinGotoID(t:DChar.wid[k]) + call s:WinExecute('let tb[k] = + \[(len(s:GetDiffLines(td[0], td[1])) - 1) * td[2], + \(len(s:GetDiffLines(bd[0], bd[1])) - 1) * bd[2]]') + endfor + " 7. search and adjust dfl above/below the top/bottom in each window + let dx = {'1': [0, 0, len(dfl[2])], '2': [0, 0, len(dfl[1])]} + let [tx, bx] = [tb[1][0] - tb[2][0], tb[1][1] - tb[2][1]] + let k = (tx < 0) ? 1 : (tx > 0) ? 2 : 0 + if k != 0 | let dx[k][0] = abs(tx) | endif + let k = (bx < 0) ? 2 : (bx > 0) ? 1 : 0 + if k != 0 | let dx[k][1] = abs(bx) | endif + for k in ks + call s:WinGotoID(t:DChar.wid[k]) + call s:WinExecute('let [td, bd] = + \[s:SearchDiffLines(0, dfl[k][0] - 1, dx[k][0]), + \s:SearchDiffLines(1, dfl[k][-1] + 1, dx[k][1])]') + let [tx, bx] = + \[min([dx[k][2], len(td)]), min([dx[k][2], len(bd)])] + let dfl[k] = td[: tx - 1] + dfl[k] + bd[-bx :] + endfor + endif + " 8. merge with previous dfl when 0 < dfp + if !a:init && index(values(t:DChar.dfl), []) == -1 && 0 < t:DChar.dfp + for k in ks + call filter(dfl[k], 'index(t:DChar.dfl[k], v:val) == -1') + let dfl[k] = empty(dfl[k]) ? t:DChar.dfl[k] : + \(t:DChar.dfl[k][-1] < dfl[k][0]) ? t:DChar.dfl[k] + dfl[k] : + \(dfl[k][-1] < t:DChar.dfl[k][0]) ? dfl[k] + t:DChar.dfl[k] : + \filter(copy(t:DChar.dfl[k]), 'v:val < dfl[k][0]') + dfl[k] + + \filter(copy(t:DChar.dfl[k]), 'dfl[k][-1] < v:val') + endfor + endif + return dfl +endfunction + +function! s:SearchDiffLines(sd, sl, sc) abort + " a:sd = direction (1:down, 0:up), a:sl = start line, a:sc = count + let dl = [] + if 0 < a:sc + let sl = a:sl + if a:sd + while sl <= line('$') + let fl = foldclosedend(sl) + if fl != -1 | let sl = fl + 1 | endif + let dl += s:GetDiffLines(sl, min([sl + a:sc - 1, line('$')])) + if a:sc <= len(dl) | let dl = dl[: a:sc - 1] | break | endif + let sl += a:sc + endwhile + else + while 1 <= sl + let fl = foldclosed(sl) + if fl != -1 | let sl = fl - 1 | endif + let dl = s:GetDiffLines(max([sl - a:sc + 1, 1]), sl) + dl + if a:sc <= len(dl) | let dl = dl[-a:sc :] | break | endif + let sl -= a:sc + endwhile + endif + endif + return dl +endfunction + +function! s:GetDiffLines(sl, el) abort + return (a:sl > a:el) ? [] : + \filter(filter(range(a:sl, a:el), 'foldlevel(v:val) == 0'), + \'index([s:DiffHL.C.id, s:DiffHL.T.id], diff_hlID(v:val, 1)) != -1') +endfunction + +function! s:GetLineColCnr(key) abort + let lcc = {} + for k in (a:key == 1) ? [2, 1] : [1, 2] + call s:WinGotoID(t:DChar.wid[k]) + call s:WinExecute('let lcc[k] = + \{"tl": line("w0"), "bl": line("w$"), "ll": line("$"), + \"cl": line("."), "cc": col("."), "cn": s:Changenr(), "ig": 0}') + call s:WinExecute('let [tl, bl] = + \[foldclosedend(lcc[k].tl), foldclosed(lcc[k].bl)]') + if tl != -1 | let lcc[k].tl = tl | endif + if bl != -1 | let lcc[k].bl = bl | endif + endfor + return lcc +endfunction + +function! s:ScrollDiffChar(key) abort + if !exists('t:DChar') || t:DChar.wid[a:key] != win_getid() + return + endif + let lcc = s:GetLineColCnr(a:key) + let scl = 0 + for k in [1, 2] + " check if a scroll happens in either window with no change on both + let scl += (t:DChar.lcc[k].cn != lcc[k].cn) ? -1 : + \([t:DChar.lcc[k].tl, t:DChar.lcc[k].bl] != + \[lcc[k].tl, lcc[k].bl]) ? 1 : 0 + let [t:DChar.lcc[k].tl, t:DChar.lcc[k].bl] = [lcc[k].tl, lcc[k].bl] + endfor + if 0 < scl + let dfl = s:FocusDiffLines(a:key, 0) + if t:DChar.dfl != dfl + " reset/show DChar lines on dfl changes + if t:DChar.dfp < 0 + let ddl = filter(copy(t:DChar.dfl[a:key]), + \'index(dfl[a:key], v:val) == -1') + if !empty(ddl) | call diffchar#ResetDiffChar(ddl) | endif + endif + let adl = filter(copy(dfl[a:key]), + \'index(t:DChar.dfl[a:key], v:val) == -1') + let t:DChar.dfl = dfl + if !empty(adl) | call diffchar#ShowDiffChar(adl) | endif + endif + endif +endfunction + +function! s:HighlightDiffChar(key, lec) abort + let hn = len(t:DChar.hgp) + for k in (a:key == 1) ? [2, 1] : [1, 2] + if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[k]) | endif + for [l, ec] in items(a:lec[k]) + if has_key(t:DChar.mid[k], l) | continue | endif + let t:DChar.hlc[k][l] = ec + " collect all the column positions per highlight group + let hc = {} + let cn = 0 + for [e, c] in ec + if e == 'c' + let h = t:DChar.hgp[cn % hn] + let cn += 1 + elseif e == 'a' + let h = s:DCharHL.A + elseif e == 'd' + if c == [0, 0] | continue | endif + let h = s:DCharHL.E + endif + if !has_key(hc, h) | let hc[h] = [] | endif + let hc[h] += [[l, c[0], c[1] - c[0] + 1]] + endfor + let t:DChar.mid[k][l] = [s:Matchaddpos(s:DCharHL.C, [[l]], -5, -1, + \{'window': t:DChar.wid[k]})] + for [h, c] in items(hc) + let t:DChar.mid[k][l] += map(range(0, len(c) - 1, 8), + \'s:Matchaddpos(h, c[v:val : v:val + 7], -3, -1, + \{"window": t:DChar.wid[k]})') + endfor + endfor + endfor +endfunction + +function! s:ClearDiffChar(key, lines) abort + for k in (a:key == 1) ? [2, 1] : [1, 2] + if win_id2win(t:DChar.wid[k]) != 0 + if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[k]) | endif + for l in a:lines[k] + silent! call map(t:DChar.mid[k][l], + \'s:Matchdelete(v:val, t:DChar.wid[k])') + endfor + endif + for l in a:lines[k] + unlet t:DChar.mid[k][l] + unlet t:DChar.hlc[k][l] + endfor + endfor +endfunction + +function! s:ShiftDiffChar(key, lines, shift) abort + let [lid, hlc, cks] = [[], {}, {}] + for l in filter(copy(a:lines), 'has_key(t:DChar.mid[a:key], v:val)') + let lid += [[l, t:DChar.mid[a:key][l]]] + let hlc[l + a:shift] = t:DChar.hlc[a:key][l] + let cks[l + a:shift] = t:DChar.cks[a:key][l] + unlet t:DChar.mid[a:key][l] + unlet t:DChar.hlc[a:key][l] + unlet t:DChar.cks[a:key][l] + endfor + call extend(t:DChar.mid[a:key], + \s:ShiftMatchaddLines(t:DChar.wid[a:key], lid, a:shift)) + call extend(t:DChar.hlc[a:key], hlc) + call extend(t:DChar.cks[a:key], cks) +endfunction + +function! s:ShiftMatchaddLines(wid, lid, shift) abort + let lid = {} + let gm = s:Getmatches(a:wid) + for [l, id] in a:lid + let mx = filter(copy(gm), 'index(id, v:val.id) != -1') + call map(copy(mx), 's:Matchdelete(v:val.id, a:wid)') + let lid[l + a:shift] = map(reverse(mx), 's:Matchaddpos(v:val.group, + \map(filter(items(v:val), "v:val[0] =~ ''^pos\\d\\+$''"), + \"[v:val[1][0] + a:shift] + v:val[1][1 :]"), v:val.priority, -1, + \{"window": a:wid})') + endfor + return lid +endfunction + +function! s:UpdateDiffChar(key, event) abort + " a:event : 0 = TextChanged/InsertLeave, 1 = DiffUpdated + if mode(1) != 'n' || !exists('t:DChar') || + \!empty(filter(values(t:DChar.wid), '!getwinvar(v:val, "&diff")')) + return + endif + if !s:VF.DiffUpdated | call s:RedrawDiffChar(a:key, 1) | return | endif + " try to redraw updated DChar lines at the last DiffUpdated which comes + " just after TextChanged or InsertLeave if text changed + if a:event == 1 + if t:DChar.lcc[a:key].ig == 0 + if t:DChar.lcc[a:key].cn == s:Changenr() + call s:RedrawDiffChar(a:key, 0) + else + " in case of e:, DiffUpdated happens 3 times, + " first, all dfl not diff highlighed but no line diff folded, + " then wait for the next two events + if &foldmethod == 'diff' && !empty(t:DChar.dfl[a:key]) && + \empty(filter(copy(t:DChar.dfl[a:key]), + \'diff_hlID(v:val, 1) != 0')) && + \empty(filter(range(t:DChar.dfl[a:key][0], + \t:DChar.dfl[a:key][-1]), '0 < foldlevel(v:val)')) + let t:DChar.lcc[a:key].ig += 1 + endif + endif + elseif t:DChar.lcc[a:key].ig == 1 " wait for another one + let t:DChar.lcc[a:key].ig += 1 + elseif t:DChar.lcc[a:key].ig == 2 " the last one came then redraw + let t:DChar.lcc[a:key].ig = 0 + call s:RedrawDiffChar(a:key, 1) + endif + else + if &diffopt =~ 'internal' && empty(&diffexpr) + if t:DChar.lcc[a:key].cn != s:Changenr() + let t:DChar.lcc[a:key].ig = 2 " wait for the next/last one + endif + else + call s:RedrawDiffChar(a:key, 1) " DiffUpdated not happen next + endif + endif +endfunction + +function! s:RedrawDiffChar(key, txtcg) abort + " a:txtcg : 0 = for text unchanged, 1 = for text changed + let ll = t:DChar.lcc[a:key].ll + let t:DChar.lcc = s:GetLineColCnr(a:key) + let cfl = s:FocusDiffLines(a:key, 1) + if a:txtcg + " compare between previous and current DChar and diff lines + " using checksum and find ones to be deleted, added, and shifted + let lnd = t:DChar.lcc[a:key].ll - ll + let bk = (a:key == 1) ? 2 : 1 + let [pfa, pfb] = [t:DChar.dfl[a:key], t:DChar.dfl[bk]] + let [cfa, cfb] = [cfl[a:key], cfl[bk]] + let m = min([len(pfa), len(cfa)]) + if pfa == cfa + let ddl = [] + for s in range(m) + if pfb[s] != cfb[s] || get(t:DChar.cks[a:key], pfa[s]) != + \s:ChecksumStr(getline(cfa[s])) + let ddl += [pfa[s]] + endif + endfor + let adl = ddl + let sdl = [] + else + let s = 0 + while s < m && pfa[s] == cfa[s] && pfb[s] == cfb[s] && + \get(t:DChar.cks[a:key], pfa[s]) == + \s:ChecksumStr(getline(cfa[s])) + let s += 1 + endwhile + let e = -1 + let m -= s + while e >= -m && pfa[e] + lnd == cfa[e] && pfb[e] == cfb[e] && + \get(t:DChar.cks[a:key], pfa[e]) == + \s:ChecksumStr(getline(cfa[e])) + let e -= 1 + endwhile + let ddl = pfa[s : e] + let adl = cfa[s : e] + let sdl = (lnd != 0 && e < -1) ? pfa[e + 1 :] : [] + endif + " redraw updated DChar lines + if 0 < t:DChar.dpv.pv | call s:ClearDiffCharPair(a:key) | endif + if !empty(ddl) | call diffchar#ResetDiffChar(ddl) | endif + let t:DChar.dfl = cfl + if !empty(sdl) | call s:ShiftDiffChar(a:key, sdl, lnd) | endif + if !empty(adl) | call diffchar#ShowDiffChar(adl) | endif + else + " reset dfl and redraw all DChar lines on text unchanged + " (diffupdate and diffopt changes) + let do = split(&diffopt, ',') + let igc = (index(do, 'icase') != -1) + let igs = (index(do, 'iwhiteall') != -1) ? 1 : + \(index(do, 'iwhite') != -1) ? 2 : + \(index(do, 'iwhiteeol') != -1) ? 3 : 0 + if [t:DChar.dfl, t:DChar.igc, t:DChar.igs] != [cfl, igc, igs] + call diffchar#ResetDiffChar() + let [t:DChar.dfl, t:DChar.igc, t:DChar.igs] = [cfl, igc, igs] + call diffchar#ShowDiffChar() + endif + endif +endfunction + +function! diffchar#JumpDiffChar(dir, pos) abort + " a:dir : 0 = backward, 1 = forward / a:pos : 0 = start, 1 = end + if !exists('t:DChar') | return | endif + for k in [1, 2, 0] + if k == 0 | return | endif + if t:DChar.wid[k] == win_getid() | break | endif + endfor + let [ln, co] = [line('.'), col('.')] + if co == col('$') " empty line + if !a:dir | let co = 0 | endif + else + if a:pos + let co += len(strcharpart(getline(ln)[co - 1 :], 0, 1)) - 1 + endif + endif + if has_key(t:DChar.hlc[k], ln) && + \(a:dir ? co < t:DChar.hlc[k][ln][-1][1][a:pos] : + \co > t:DChar.hlc[k][ln][0][1][a:pos]) + " found in current line + let hc = filter(map(copy(t:DChar.hlc[k][ln]), 'v:val[1][a:pos]'), + \a:dir ? 'co < v:val' : 'co > v:val') + let co = hc[a:dir ? 0 : -1] + else + if t:DChar.dfp == 0 + " try to find the next highlighted line + let hl = filter(map(keys(t:DChar.hlc[k]), 'eval(v:val)'), + \a:dir ? 'ln < v:val' : 'ln > v:val') + if empty(hl) | return | endif + let ln = a:dir ? min(hl) : max(hl) + else + " try to find the next diff line and then apply hlc or scroll + let cp = [line('.'), col('.')] + while 1 + let dl = s:SearchDiffLines(a:dir, a:dir ? ln + 1 : ln - 1, 1) + if empty(dl) | noautocmd call cursor(cp) | return | endif + let ln = dl[0] + if has_key(t:DChar.hlc[k], ln) | break | endif + noautocmd call cursor(ln, 0) + call s:ScrollDiffChar(k) + if has_key(t:DChar.hlc[k], ln) | break | endif + endwhile + endif + let co = t:DChar.hlc[k][ln][a:dir ? 0 : -1][1][a:pos] + endif + " set a dummy cursor position to adjust the start/end + if 0 < t:DChar.dpv.pv + call s:ClearDiffCharPair(k) + if [a:dir, a:pos] == [1, 0] " forward/start : rightmost + let [t:DChar.lcc[k].cl, t:DChar.lcc[k].cc] = [ln, col('$')] + elseif [a:dir, a:pos] == [0, 1] " backward/end : leftmost + let [t:DChar.lcc[k].cl, t:DChar.lcc[k].cc] = [ln, 0] + endif + endif + call cursor(ln, co) +endfunction + +function! s:ShowDiffCharPair(key) abort + if mode(1) != 'n' || !exists('t:DChar') || + \t:DChar.wid[a:key] != win_getid() + return + endif + let [pl, pc, pn] = [t:DChar.lcc[a:key].cl, t:DChar.lcc[a:key].cc, + \t:DChar.lcc[a:key].cn] + let [cl, cc] = [line('.'), col('.')] + if cc == col('$') | let cc = 0 | endif + let [t:DChar.lcc[a:key].cl, t:DChar.lcc[a:key].cc] = [cl, cc] + if pn != s:Changenr() | return | endif " do nothing on TextChanged + if !empty(t:DChar.dpv.ch) + " pair highlight exists + let [hl, hi] = t:DChar.dpv.ch.lc + let hc = t:DChar.hlc[a:key][hl][hi][1] + " inside the highlight, do nothing + if cl == hl && hc[0] <= cc && cc <= hc[1] | return | endif + call s:ClearDiffCharPair(a:key) " outside, clear it + endif + if has_key(t:DChar.hlc[a:key], cl) + let hu = filter(map(copy(t:DChar.hlc[a:key][cl]), + \'[v:key, v:val[1]]'), 'v:val[1][0] <= cc && cc <= v:val[1][1]') + if !empty(hu) + " for 2 contineous 'd', check if cursor moved forward or backward + let ix = (len(hu) == 1) ? 0 : (cl == pl) ? cc < pc : cl < pl + call s:HighlightDiffCharPair(a:key, cl, hu[ix][0]) + endif + endif +endfunction + +function! s:HighlightDiffCharPair(key, line, col) abort + let [ak, bk] = (a:key == 1) ? [1, 2] : [2, 1] + let [al, bl] = [a:line, t:DChar.dfl[bk][index(t:DChar.dfl[ak], a:line)]] + " set a pair cursor position (line, colnum) and match id + let t:DChar.dpv.ch.lc = [al, a:col] + let t:DChar.dpv.ch.bk = bk + " show a cursor-like highlight at the corresponding position + let bc = t:DChar.hlc[bk][bl][a:col][1] + if bc != [0, 0] + let [pos, len] = [bc[0], bc[1] - bc[0] + 1] + if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[bk]) | endif + let t:DChar.dpv.ch.id = s:Matchaddpos(s:DCharHL.c, [[bl, pos, len]], + \-1, -1, {'window': t:DChar.wid[bk]}) + if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[ak]) | endif + else + let t:DChar.dpv.ch.id = -1 " no cursor hl on empty line + endif + call execute(['augroup diffchar2', 'autocmd!', + \'autocmd WinLeave call s:ClearDiffCharPair(' . ak . ')', 'augroup END']) + if t:DChar.dpv.pv < 2 | return | endif + " show the corresponding unit in echo or popup-window + let at = getbufline(t:DChar.bnr[ak], al)[0] + let bt = getbufline(t:DChar.bnr[bk], bl)[0] + let [ae, ac] = t:DChar.hlc[ak][al][a:col] + if ae == 'c' + let hl = t:DChar.hgp[(count(map(t:DChar.hlc[ak][al][: a:col], + \'v:val[0]'), 'c') - 1) % len(t:DChar.hgp)] + let [tb, tx, te] = ['', bt[bc[0] - 1 : bc[1] - 1], ''] + elseif ae == 'd' + let hl = s:DCharHL.A + let [tb, tx, te] = [(1 < bc[0]) ? '<' : '', + \bt[bc[0] - 1 : bc[1] - 1], (bc[1] < len(bt)) ? '>' : ''] + elseif ae == 'a' + let hl = s:DCharHL.D + let [tb, tx, te] = [(1 < ac[0]) ? '>' : '', + \repeat((t:DChar.dpv.pv == 2 && s:VF.StrikeAttr) ? ' ' : + \(&fillchars =~ 'diff') ? matchstr(&fillchars, 'diff:\zs.') : '-', + \strwidth(at[ac[0] - 1 : ac[1] - 1])), + \(ac[1] < len(at)) ? '<' : ''] + endif + if t:DChar.dpv.pv == 2 + call execute(['echon tb', 'echohl ' . hl, 'echon tx', 'echohl None', + \'echon te'], '') + elseif t:DChar.dpv.pv == 3 || t:DChar.dpv.pv == 4 + if t:DChar.dpv.pv == 4 | let mp = getmousepos() | endif + if s:VF.PopupWindow + call popup_move(t:DChar.dpv.pw, (t:DChar.dpv.pv == 3) ? + \{'line': 'cursor+1', 'col': 'cursor'} : + \{'line': mp.screenrow, 'col': mp.screencol}) + call popup_settext(t:DChar.dpv.pw, tb . tx . te) + call popup_show(t:DChar.dpv.pw) + elseif s:VF.FloatingWindow + call nvim_win_set_config(t:DChar.dpv.pw.fw, + \extend((t:DChar.dpv.pv == 3) ? + \{'relative': 'cursor', 'row': 1, 'col': 0} : + \{'relative': 'editor', 'row': mp.screenrow, + \'col': mp.screencol}, + \{'width': strdisplaywidth(tb . tx . te)})) + call setbufline(t:DChar.dpv.pw.fb, 1, tb . tx . te) + call setwinvar(t:DChar.dpv.pw.fw, '&winblend', 0) + endif + endif +endfunction + +function! s:ClearDiffCharPair(key) abort + if !empty(t:DChar.dpv.ch) + let [bk, id] = [t:DChar.dpv.ch.bk, t:DChar.dpv.ch.id] + if id != -1 && win_id2win(t:DChar.wid[bk]) != 0 + if !s:VF.WinIDinMatch | call s:WinGotoID(t:DChar.wid[bk]) | endif + silent! call s:Matchdelete(id, t:DChar.wid[bk]) + if !s:VF.WinIDinMatch + call s:WinGotoID(t:DChar.wid[a:key]) + endif + endif + call execute(['augroup diffchar2', 'autocmd!', 'augroup END', + \'augroup! diffchar2']) + let t:DChar.dpv.ch = {} + endif + if t:DChar.dpv.pv == 2 | call execute('echo', '') + elseif t:DChar.dpv.pv == 3 || t:DChar.dpv.pv == 4 + if s:VF.PopupWindow | call popup_hide(t:DChar.dpv.pw) + elseif s:VF.FloatingWindow + call nvim_win_set_config(t:DChar.dpv.pw.fw, + \{'relative': 'editor', 'row': 0, 'col': 0, 'width': 1}) + call setbufline(t:DChar.dpv.pw.fb, 1, '') + call setwinvar(t:DChar.dpv.pw.fw, '&winblend', 100) + endif + endif +endfunction + +function! s:ToggleDiffCharPair(on) abort + if t:DChar.dpv.pv == 3 || t:DChar.dpv.pv == 4 + if s:VF.PopupWindow + let t:DChar.dpv.pw = a:on ? + \popup_create('', {'hidden': 1, 'scrollbar': 0, 'wrap': 0, + \'highlight': s:DCharHL.c}) : + \popup_close(t:DChar.dpv.pw) + elseif s:VF.FloatingWindow + if a:on + let t:DChar.dpv.pw.fb = nvim_create_buf(0, 1) + let t:DChar.dpv.pw.fw = nvim_open_win(t:DChar.dpv.pw.fb, 0, + \{'relative': 'editor', 'row': 0, 'col': 0, 'height': 1, + \'width': 1, 'focusable': 0, 'style': 'minimal'}) + call setbufline(t:DChar.dpv.pw.fb, 1, '') + call setwinvar(t:DChar.dpv.pw.fw, '&winblend', 100) + call setwinvar(t:DChar.dpv.pw.fw, '&winhighlight', + \'Normal:' . s:DCharHL.c) + else + call nvim_win_close(t:DChar.dpv.pw.fw, 1) + let t:DChar.dpv.pw = {'fb': -1, 'fw': -1} + endif + endif + endif +endfunction + +function! diffchar#CopyDiffCharPair(dir) abort + " a:dir : 0 = get, 1 = put + if !exists('t:DChar') | return | endif + for ak in [1, 2, 0] + if ak == 0 | return | endif + if t:DChar.wid[ak] == win_getid() | break | endif + endfor + let bk = (ak == 1) ? 2 : 1 + let un = -1 + if 0 < t:DChar.dpv.pv + if !empty(t:DChar.dpv.ch) | let [al, un] = t:DChar.dpv.ch.lc | endif + else + let [al, co] = [line('.'), col('.')] + if co == col('$') | let co = 0 | endif + if has_key(t:DChar.hlc[ak], al) + let hc = filter(map(copy(t:DChar.hlc[ak][al]), + \'[v:key, v:val[1]]'), + \'v:val[1][0] <= co && co <= v:val[1][1]') + if !empty(hc) | let un = hc[0][0] | endif + endif + endif + if un == -1 + call s:EchoWarning('Cursor is not on a difference unit!') + return + endif + let bl = t:DChar.dfl[bk][index(t:DChar.dfl[ak], al)] + let [ae, ac] = t:DChar.hlc[ak][al][un] + let [be, bc] = t:DChar.hlc[bk][bl][un] + let at = getbufline(t:DChar.bnr[ak], al)[0] + let bt = getbufline(t:DChar.bnr[bk], bl)[0] + let [x, y] = a:dir ? ['b', 'a'] : ['a', 'b'] " put : get + let s1 = (1 < {x}c[0]) ? {x}t[: {x}c[0] - 2] : '' + let s2 = ({x}e != 'a') ? {y}t[{y}c[0] - 1 : {y}c[1] - 1] : '' + if {x}e == 'd' && {x}c != [0, 0] + let ds = split({x}t[{x}c[0] - 1 : {x}c[1] - 1], '\zs') + let s2 = ((1 < {y}c[0]) ? ds[0] : '') . s2 . + \(({y}c[1] < len({y}t)) ? ds[-1] : '') + endif + let s3 = ({x}c[1] < len({x}t)) ? {x}t[{x}c[1] :] : '' + let ss = s1 . s2 . s3 + if a:dir " put + call s:WinGotoID(t:DChar.wid[bk]) + call s:WinExecute('noautocmd call setline(bl, ss)') + call s:WinExecute('call s:RedrawDiffChar(bk, 1)') + call s:WinGotoID(t:DChar.wid[ak]) + else " get + call setline(al, ss) + endif +endfunction + +function! diffchar#EchoDiffChar(lines, short) abort + if !exists('t:DChar') | return | endif + for ak in [1, 2, 0] + if ak == 0 | return | endif + if t:DChar.wid[ak] == win_getid() | break | endif + endfor + let bk = (ak == 1) ? 2 : 1 + let nw = max([&numberwidth - 1, len(string(line('$')))]) + let ec = [] + for al in a:lines + let gt = [] + if &number || &relativenumber + let gt += [[s:DCharHL.n, printf('%'. nw . 'd ', + \(&relativenumber ? abs(al - line('.')) : al))]] + endif + let at = getbufline(t:DChar.bnr[ak], al)[0] + if !has_key(t:DChar.hlc[ak], al) + if a:short | continue | endif + let gt += [['', empty(at) ? "\n" : at]] + else + let bl = t:DChar.dfl[bk][index(t:DChar.dfl[ak], al)] + let bt = getbufline(t:DChar.bnr[bk], bl)[0] + let hl = repeat('C', len(at)) + let tx = at + for an in range(len(t:DChar.hlc[ak][al]) - 1, 0, -1) + let [ae, ac] = t:DChar.hlc[ak][al][an] + " enclose highlight and text in '[+' and '+]' + " if strike not available + if ae == 'c' || ae == 'a' + let it = at[ac[0] - 1 : ac[1] - 1] + if !s:VF.StrikeAttr | let it = '[+' . it . '+]' | endif + let ih = repeat((ae == 'a') ? 'A' : 'T', len(it)) + let hl = ((1 < ac[0]) ? hl[: ac[0] - 2] : '') . ih . + \hl[ac[1] :] + let tx = ((1 < ac[0]) ? tx[: ac[0] - 2] : '') . it . + \tx[ac[1] :] + endif + " enclose corresponding changed/deleted units in '[-' and '-]' + " if strike not available, + " and insert them to highlight and text + if ae == 'c' || ae == 'd' + let bc = t:DChar.hlc[bk][bl][an][1] + let it = bt[bc[0] - 1 : bc[1] - 1] + if !s:VF.StrikeAttr | let it = '[-' . it . '-]' | endif + let ih = repeat('D', len(it)) + if ae == 'c' + let hl = ((1 < ac[0]) ? hl[: ac[0] - 2] : '') . ih . + \hl[ac[0] - 1 :] + let tx = ((1 < ac[0]) ? tx[: ac[0] - 2] : '') . it . + \tx[ac[0] - 1 :] + else + if ac[0] == 1 && bc[0] == 1 + let hl = ih . hl + let tx = it . tx + else + let ix = ac[0] + + \len(strcharpart(at[ac[0] - 1 :], 0, 1)) - 2 + let hl = hl[: ix] . ih . hl[ix + 1 :] + let tx = tx[: ix] . it . tx[ix + 1 :] + endif + endif + endif + endfor + let sm = a:short && &columns <= strdisplaywidth(tx) + let ix = 0 + let tn = 0 + for h in split(hl, '\%(\(.\)\1*\)\zs') + if h[0] == 'T' + let g = t:DChar.hgp[tn % len(t:DChar.hgp)] + let tn += 1 + else + let g = s:DCharHL[h[0]] + endif + let t = tx[ix : ix + len(h) - 1] + if sm && h[0] == 'C' + let s = split(t, '\zs') + if ix == 0 && 1 < len(s) && + \3 < strdisplaywidth(join(s[: -2], '')) + let t = '...' . s[-1] + elseif ix + len(h) == len(tx) && 1 < len(s) && + \3 < strdisplaywidth(join(s[1 :], '')) + let t = s[0] . '...' + elseif 2 < len(s) && + \3 < strdisplaywidth(join(s[1 : -2], '')) + let t = s[0] . '...' . s[-1] + endif + endif + let gt += [[g, t]] + let ix += len(h) + endfor + endif + let ec += ['echo ""'] + for [g, t] in gt + let ec += ['echohl ' . g, 'echon "' . escape(t, '"') . '"'] + endfor + let ec += ['echohl None'] + endfor + call execute(ec, '') +endfunction + +function! diffchar#DiffCharExpr() abort + let [f1, f2] = [readfile(v:fname_in), readfile(v:fname_new)] + call writefile(([f1, f2] == [['line1'], ['line2']]) ? ['1c1'] : + \(s:VF.DiffExecutable && len(f1) + len(f2) > 100) ? + \s:ExtDiffExpr(v:fname_in, v:fname_new) : + \s:IntDiffExpr(f1, f2), v:fname_out) +endfunction + +function! s:IntDiffExpr(f1, f2) abort + let [f1, f2] = [a:f1, a:f2] + let do = split(&diffopt, ',') + let save_igc = &ignorecase + let &ignorecase = (index(do, 'icase') != -1) + if index(do, 'iwhiteall') != -1 + for k in [1, 2] + call map(f{k}, 'substitute(v:val, "\\s\\+", "", "g")') + endfor + elseif index(do, 'iwhite') != -1 + for k in [1, 2] + call map(f{k}, 'substitute(v:val, "\\s\\+", " ", "g")') + call map(f{k}, 'substitute(v:val, "\\s\\+$", "", "")') + endfor + elseif index(do, 'iwhiteeol') != -1 + for k in [1, 2] + call map(f{k}, 'substitute(v:val, "\\s\\+$", "", "")') + endfor + endif + let dfcmd = [] + let [l1, l2] = [1, 1] + for ed in split(s:TraceDiffChar(f1, f2), '[+-]\+\zs', 1)[: -2] + let [qe, q1, q2] = [s:CountChar(ed, '='), s:CountChar(ed, '-'), + \s:CountChar(ed, '+')] + let [l1, l2] += [qe, qe] + let dfcmd += [((1 < q1) ? l1 . ',' : '') . (l1 + q1 - 1) . + \((q1 == 0) ? 'a' : (q2 == 0) ? 'd' : 'c') . + \((1 < q2) ? l2 . ',' : '') . (l2 + q2 - 1)] + let [l1, l2] += [q1, q2] + endfor + let &ignorecase = save_igc + return dfcmd +endfunction + +function! s:ExtDiffExpr(f1, f2) abort + let do = split(&diffopt, ',') + let dc = ['diff', '-a', '--binary', + \((index(do, 'icase') != -1) ? '-i' : ''), + \((index(do, 'iwhiteall') != -1) ? '-w' : + \(index(do, 'iwhite') != -1) ? '-b' : + \(index(do, 'iwhiteeol') != -1) ? '-Z' : ''), a:f1, a:f2] + let save_stmp = &shelltemp + let &shelltemp = 0 + let dt = systemlist(join(dc)) + let &shelltemp = save_stmp + return filter(dt, 'v:val[0] =~ "\\d"') +endfunction + +if s:VF.DiffOptionSet + function! diffchar#ToggleDiffModeSync(event) abort + " a:event : 0 = OptionSet diff, 1 = VimEnter + if !get(g:, 'DiffModeSync', 1) | return | endif + if s:VF.VOptionFixed + if a:event || v:option_old != v:option_new + call s:SwitchDiffChar(a:event || v:option_new) + endif + else + call s:SwitchDiffChar(a:event || &diff) + endif + endfunction +else + function! diffchar#SetDiffModeSync() abort + " DiffModeSync is triggered ON by FilterWritePost + if !get(g:, 'DiffModeSync', 1) | return | endif + if !exists('s:dmbuf') + " as a diff session, when FilterWritePos comes, current buf and + " other 1 or more buf should be diff mode + let s:dmbuf = map(filter(gettabinfo(tabpagenr())[0].windows, + \'getwinvar(v:val, "&diff")'), 'winbufnr(v:val)') + if index(s:dmbuf, bufnr('%')) == -1 || + \min(s:dmbuf) == max(s:dmbuf) + " not a diff session, then clear + unlet s:dmbuf + return + endif + " wait for the contineous 1 or more FilterWitePost (diff) or + " 1 ShellFilterPost (non diff) + call execute('autocmd! diffchar ShellFilterPost * + \ call s:ClearDiffModeSync()') + " prepare to complete sync just in case for accidents + let s:id = timer_start(0, function('s:CompleteDiffModeSync')) + endif + " check if all the FilterWritePost has come + if empty(filter(s:dmbuf, 'v:val != bufnr("%")')) + call s:CompleteDiffModeSync(0) + endif + endfunction + + function! s:CompleteDiffModeSync(id) abort + if exists('s:id') + if a:id == 0 | call timer_stop(s:id) | endif + unlet s:id + else + if exists('s:save_ch') && !empty(s:save_ch) + call execute('autocmd! diffchar CursorHold * call ' . + \s:save_ch) + call s:ChangeUTOpt(1) + else + call execute('autocmd! diffchar CursorHold *') + call s:ChangeUTOpt(0) + endif + silent call feedkeys("g\", 'n') + endif + call s:ClearDiffModeSync() + call timer_start(0, function('s:SwitchDiffChar')) + endfunction + + function! s:ClearDiffModeSync() abort + unlet s:dmbuf + call execute('autocmd! diffchar ShellFilterPost *') + endfunction + + function! s:ResetDiffModeSync() abort + " DiffModeSync is triggered OFF by CursorHold + if exists('t:DChar') && t:DChar.dsy && + \!empty(filter(values(t:DChar.wid), '!getwinvar(v:val, "&diff")')) + " if either or both of DChar win is now non-diff mode, + " reset it and show with current diff mode wins + call s:SwitchDiffChar(0) + endif + endfunction +endif + +function! s:SwitchDiffChar(on) abort + let cw = win_getid() + let aw = cw + if exists('t:DChar') && + \(a:on ? index(values(t:DChar.bnr), winbufnr(cw)) == -1 : + \index(values(t:DChar.wid), cw) != -1) + " diff mode ON on non-DChar buf || OFF on DChar win, try reset + for k in [1, 2] + if getwinvar(t:DChar.wid[k], '&diff') + let aw = t:DChar.wid[k] + call s:WinGotoID(aw) + call s:WinExecute('call diffchar#ResetDiffChar(1)') + call s:WinGotoID(cw) + break + endif + endfor + endif + if !exists('t:DChar') && get(g:, 'DiffModeSync', 1) + let aw = win_id2win(aw) + let dw = filter(map(range(aw, winnr('$')) + range(1, aw - 1), + \'win_getid(v:val)'), 'getwinvar(v:val, "&diff")') + if 1 < len(dw) + " 2 or more diff mode wins exists, try show + call s:WinGotoID(dw[0]) + call s:WinExecute('call diffchar#ShowDiffChar()') + call s:WinGotoID(cw) + endif + endif +endfunction + +function! s:WinClosedDiffChar() abort + " reset and show (if possible) DChar on WinClosed or BufWinLeave + for tn in range(1, tabpagenr('$')) + let dc = s:Gettabvar(tn, 'DChar') + if !empty(dc) + for k in [1, 2] + if s:VF.WinClosed ? dc.wid[k] == eval(expand('')) : + \dc.bnr[k] == eval(expand('')) + let cw = win_getid() + call s:WinGotoID(dc.wid[k]) + call s:WinExecute('call diffchar#ResetDiffChar(1)') + if dc.dsy + let dw = filter(gettabinfo(tn)[0].windows, + \'v:val != dc.wid[k] && + \winbufnr(v:val) == dc.bnr[k] && + \getwinvar(v:val, "&diff")') + if !empty(dw) + call s:WinGotoID(dw[0]) + call s:WinExecute('call diffchar#ShowDiffChar()') + endif + endif + call s:WinGotoID(cw) + return + endif + endfor + endif + endfor +endfunction + +function! s:RepairDiffChar() abort + " repair DChar whose win was accidentally closed on BufWinEnter/WinEnter + if exists('t:DChar') + let dc = t:DChar + let dw = filter(copy(dc.wid), 'win_id2win(v:val) != 0 && + \winbufnr(v:val) == dc.bnr[v:key] && getwinvar(v:val, "&diff")') + if len(dw) == 1 + let cw = win_getid() + call s:WinGotoID(values(dw)[0]) + call s:WinExecute('call diffchar#ResetDiffChar(1)') + if dc.dsy + call s:WinExecute('call diffchar#ShowDiffChar()') + endif + call s:WinGotoID(cw) + endif + endif +endfunction + +function! s:ChecksumStr(str) abort + return eval('0x' . sha256(a:str)[-4 :]) +endfunction + +function! s:EchoWarning(msg) abort + call execute(['echohl WarningMsg', 'echo a:msg', 'echohl None'], '') +endfunction + +if s:VF.CountString + let s:CountChar = function('count') +else + function! s:CountChar(str, chr) abort + return len(a:str) - len(substitute(a:str, a:chr, '', 'g')) + endfunction +endif + +if s:VF.GettabvarFixed + let s:Gettabvar = function('gettabvar') +else + function! s:Gettabvar(tp, var) abort + call gettabvar(a:tp, a:var) " call twice as a workaround + return gettabvar(a:tp, a:var) + endfunction +endif + +if s:VF.ChangenrFixed + let s:Changenr = function('changenr') +else + function! s:Changenr() abort + let ute = undotree().entries + for n in range(len(ute)) + if has_key(ute[n], 'curhead') + " if curhead exists, undotree().seq_cur should be this but not + " then changenr() returns a wrong number + return (0 < n) ? ute[n - 1].seq : 0 + endif + endfor + return changenr() + endfunction +endif + +if s:VF.WinExecute + function! s:WinGotoID(wid) abort + let s:WinExecute = function('win_execute', [a:wid]) + endfunction +else + function! s:WinGotoID(wid) abort + noautocmd call win_gotoid(a:wid) + endfunction + let s:WinExecute = function('execute') +endif + +if s:VF.WinIDinMatch + let s:Matchaddpos = function('matchaddpos') + let s:Matchdelete = function('matchdelete') + let s:Getmatches = function('getmatches') +else + function! s:Matchaddpos(grp, pos, pri, ...) abort + return matchaddpos(a:grp, a:pos, a:pri) + endfunction + + function! s:Matchdelete(id, ...) abort + return matchdelete(a:id) + endfunction + + function! s:Getmatches(...) abort + return getmatches() + endfunction +endif + +function! s:AdjustGlobalOption() abort + if !s:VF.DiffUpdated && !s:VF.DiffOptionSet + call s:ChangeUTOpt(exists('t:DChar') && t:DChar.dsy) + endif + call s:ToggleDiffHL(exists('t:DChar')) +endfunction + +if !s:VF.DiffUpdated + if s:VF.DiffOptionSet + function! s:FollowDiffOption() abort + if v:option_old != v:option_new + let cw = win_getid() + for dc in filter(map(range(1, tabpagenr('$')), + \'s:Gettabvar(v:val, "DChar")'), '!empty(v:val)') + call s:WinGotoID(dc.wid[1]) + call s:WinExecute('call s:RedrawDiffChar(1, 0)') + endfor + call s:WinGotoID(cw) + endif + endfunction + else + function! s:ChangeUTOpt(on) abort + if a:on && !exists('s:save_ut') + let s:save_ut = &updatetime + let &updatetime = 500 + elseif !a:on && exists('s:save_ut') + let &updatetime = s:save_ut + unlet s:save_ut + endif + endfunction + endif +endif + +function! s:ToggleDiffHL(on) abort + " dh: 0 = original, 1 = for single color, 2 = for multi color + for dh in values(s:DiffHL) + call execute(['highlight clear ' . dh.nm, 'highlight ' . dh.nm . ' ' . + \join(map(items(dh[!a:on ? 0 : (len(t:DChar.hgp) == 1) ? 1 : 2]), + \'join(v:val, "=")'))]) + endfor +endfunction + +let &cpoptions = s:save_cpo +unlet s:save_cpo + +" vim: ts=4 sw=4 diff --git a/.vim/bundle/vim-diffchar/doc/diffchar.txt b/.vim/bundle/vim-diffchar/doc/diffchar.txt new file mode 100644 index 0000000..132b67c --- /dev/null +++ b/.vim/bundle/vim-diffchar/doc/diffchar.txt @@ -0,0 +1,216 @@ +*diffchar.txt* Highlight the exact differences, based on characters and words +> + ____ _ ____ ____ _____ _ _ _____ ____ + | | | || || || || | | || _ || _ | + | _ || || __|| __|| || | | || | | || | || + | | | || || |__ | |__ | __|| |_| || |_| || |_||_ + | |_| || || __|| __|| | | || || __ | + | || || | | | | |__ | _ || _ || | | | + |____| |_||_| |_| |_____||_| |_||_| |_||_| |_| +< +Last Change: 2021/12/07 +Version: 8.91 +Author: Rick Howe (Takumi Ohtani) +Copyright: (c) 2014-2021 by Rick Howe + +----------------------------------------------------------------------------- +INTRODUCTION *diffchar* + +This plugin has been developed in order to make diff mode more useful. Vim +highlights all the text in between the first and last different characters on +a changed line. But this plugin will find the exact differences between them, +character by character - so called DiffChar. + +For example, in diff mode: ([|hl-DiffText|], <|hl-DiffAdd|>) +> + (window A) The [quick brown fox jumps over the lazy] dog. + (window B) The [lazy fox jumps over the quick brown] dog. +< +this plugin will exactly show the changed and added units: +> + (window A) The [quick] fox jumps over the [lazy] dog. + (window B) The [lazy] fox jumps over the [quick] dog. +< +This plugin will synchronously show/reset the highlights of the exact +differences as soon as the diff mode begins/ends. And the exact differences +will be kept updated while editing. + +This plugin shows the differences based on a |g:DiffUnit|. Its default is +'Word1' and it handles a \w\+ word and a \W character as a difference unit. +There are other types of word provided and you can also set 'Char' to compare +character by character. + +In diff mode, the corresponding |hl-DiffChange| lines are compared between two +windows. You can set a number of matching colors to a |g:DiffColors| to make +it easy to find the corresponding units between two windows. As a default, all +the changed units are highlighted with |hl-DiffText|. In addition, +|hl-DiffAdd| is always used for the added units and both the previous and next +character of the deleted units are shown in bold/underline. + +While showing the exact differences, when the cursor is moved on a difference +unit, you can see its corresponding unit highlighted with |hl-Cursor|, +|hl-TermCursor|, or similar one in another window, based on a +|g:DiffPairVisible|. If you change its default, the corresponding unit is +echoed in the command line or displayed in a popup/floating window just below +the cursor position or at the mouse position. + +You can use `]b` or `]e` to jump cursor to start or end position of the next +difference unit, and `[b` or `[e` to the start or end position of the previous +unit. Those keymaps are configurable in your vimrc and so on. + +Like line-based `:diffget`/`:diffput` and `do`/`dp` vim commands, you can use +`g` and `p` commands in normal mode to get and put each +difference unit, where the cursor is on, between 2 buffers and undo its +difference. + +When the diff mode begins, this plugin locally checks the |hl-DiffChange| +lines in the limited range of the current visible and its upper/lower lines of +a window. And each time a cursor is moved on to a different range upon +scrolling or searching, the new |hl-DiffChange| lines will be incrementally +checked in that range. Which means, independently of the file size, the number +of lines to be checked and then the time consumed are always constant. + +This plugin works on each tab page individually. You can use a tab page +variable (t:), instead of a global one (g:), to specify different options on +each tab page. Note that this plugin can not handle more than two diff mode +windows in a tab page. If it would happen, to prevent any trouble, all the +highlighted units are to be reset in the tab page. + +To find the exact differences, this plugin uses "An O(NP) Sequence Comparison +Algorithm" developed by S.Wu, et al., which always finds an optimum sequence. +But it takes time to check a long and dissimilar line. To improve the +performance, if there are so many diff units included in a line or it has +taken much time in a diff session, this plugin tries to use the external diff +command together if available. + +----------------------------------------------------------------------------- +OPTIONS *diffchar-options* + +|g:DiffUnit|, |t:DiffUnit| + A type of difference unit + 'Char' : any single character + 'Word1' : \w\+ word and any \W single character (default) + 'Word2' : non-space and space words + 'Word3' : \< or \> character class boundaries + 'CSV(,)' : separated by characters such as ',', ';', and '\t' + +|g:DiffColors|, |t:DiffColors| + Matching colors for changed units + 0 : |hl-DiffText| (default) + 1 : |hl-DiffText| + up to 3 other highlights + 2 : |hl-DiffText| + up to 7 other highlights + 3 : |hl-DiffText| + up to 15 other highlights + +|g:DiffPairVisible|, |t:DiffPairVisible| + Visibility of corresponding diff units + 0 : disable + 1 : highlight with |hl-Cursor| (default) + 2 : highlight with |hl-Cursor| + echo in the command line + 3 : highlight with |hl-Cursor| + popup/floating window at cursor position + 4 : highlight with |hl-Cursor| + popup/floating window at mouse position + +----------------------------------------------------------------------------- +KEYMAPS *diffchar-keymaps* + +JumpDiffCharPrevStart (default: `[b`) + Jump cursor to the start position of the previous difference unit + +JumpDiffCharNextStart (default: `]b`) + Jump cursor to the start position of the next difference unit + +JumpDiffCharPrevEnd (default: `[e`) + Jump cursor to the end position of the previous difference unit + +JumpDiffCharNextEnd (default: `]e`) + Jump cursor to the end position of the next difference unit + +GetDiffCharPair (default: `g`) + Get a corresponding difference unit from another buffer to undo difference + +PutDiffCharPair (default: `p`) + Put a corresponding difference unit to another buffer to undo difference + +----------------------------------------------------------------------------- +CHANGE HISTORY *diffchar-history* + +Update : 8.91 +* Updated to check a new WinClosed event (patch-8.2.3591) to appropriately + reset or repair the highlighted DiffChar units when a window is closed. + +Update : 8.9 +* Fixed not to overrule syntax highlighting. +* Fixed to successfully sync with diff mode even without patch-8.1.414. +* Changed the highlighting groups used in |g:DiffColors| option. + +Update : 8.8 +* Changed the highlighting groups used in |g:DiffColors| option, to completely + highlight a changed diff unit and to make each unit more visible. +* Changed to use |hl-Cursor|, |hl-TermCursor|, or similar one, as appropriate, + to highlight a corresponding diff unit, for |g:DiffPairVisible| option. +* Fixed to use proper |hl-Diff| highlighting groups even if they are linked. +* Optimized how to draw each unit and then improved performance. + +Update : 8.7 +* Enhanced |g:DiffPairVisible| option to show a corresponding diff unit as + well in a floating window on nvim, if its value is 3, and show a popup (not + a balloon) window at the mouse position, if its value is 4. +* Improved performance, use the external diff command together if available, + if there are so many diff units included in a line or it has taken much time + in a diff session. +* Removed |g:DiffMaxLines| option, and locally checks the limited number of + the |hl-DiffChange| lines and incrementally checks them upon scrolling or + searching. +* Removed |g:DiffModeSync| option, and always synchronize with the diff mode. +* `:SDChar`, `:RDChar`, `:TDChar`, and `:EDChar` commands are still available + but deprecated. + +Update : 8.6 +* Enhanced |g:DiffPairVisible| option to show a corresponding diff unit as a + popup-window just below the cursor position (available on patch-8.1.1391). + And changed its default as 1 (diff unit highlighting only). +* Fixed not to stop monitoring the changes of text and 'diffopt' option, + even if there is no |hl-DiffChange| line, when |g:DiffModeSync| option is + enabled. + +Update : 8.5 +* Enhanced to show a balloon on GUI and display a corresponding diff unit, + where the mouse is pointing, if |g:DiffPairVisible| option is enabled + (patch-8.1.647 is required to correctly display multibyte characters). +* Fixed to correctly handle |hl-DiffChange| lines while editing. + +Update : 8.4 +* Extended |g:DiffMaxLines| option to allow a negative value as multiples of + the window height and changed its default as -3. +* Fixed to reset all highlighted DiffChar units when more than two windows + become diff mode in a tab page. +* Deleted |g:DiffSplitTime| option. + +Update : 8.3 +* Fixed not to detect more |hl-DiffChange| lines than |g:DiffMaxLines| option. + +Update : 8.2 +* Fixed to correctly update the highlighted DiffChar units while editing when + a new internal diff is not specified in 'diffopt' option (patch-8.1.360). + +Update : 8.1 +* Fixed to properly detect |hl-DiffChange| lines even when all visible lines + of current window are in a closed fold if a |g:DiffMaxLines| option is + enabled. + +Update : 8.0 +* Introduced a |g:DiffMaxLines| option to dynamically detect a limited number + of |hl-DiffChange| lines, when the diff mode begins and whenever a cursor is + moved onto an undetected line. It enables to always take a minimum constant + time, independently of the file size. +* Enhanced to check a new DiffUpdated event (patch-8.1.397) to follow diff + updates and some changes of 'diffopt' option. +* Enhanced to support new iwhiteall and iwhiteeol of 'diffopt' option + (patch-8.1.360). +* Removed |g:DiffUpdate| option and merged it into |g:DiffModeSync|. +* Removed keymap for and , which toggle to show/reset the highlights. +* Changed to work in diff mode, not in non-diff mode. +* Removed a support for vim version 7.x. +* Changed not to set 'diffexpr' option when a new internal diff is specified + in 'diffopt' (patch-8.1.360). + + vim:tw=78:ts=8:ft=help:norl: diff --git a/.vim/bundle/vim-diffchar/doc/tags b/.vim/bundle/vim-diffchar/doc/tags new file mode 100644 index 0000000..f61b1ae --- /dev/null +++ b/.vim/bundle/vim-diffchar/doc/tags @@ -0,0 +1,5 @@ +diffchar diffchar.txt /*diffchar* +diffchar-history diffchar.txt /*diffchar-history* +diffchar-keymaps diffchar.txt /*diffchar-keymaps* +diffchar-options diffchar.txt /*diffchar-options* +diffchar.txt diffchar.txt /*diffchar.txt* diff --git a/.vim/bundle/vim-diffchar/plugin/diffchar.vim b/.vim/bundle/vim-diffchar/plugin/diffchar.vim new file mode 100644 index 0000000..9cb8f74 --- /dev/null +++ b/.vim/bundle/vim-diffchar/plugin/diffchar.vim @@ -0,0 +1,118 @@ +" diffchar.vim: Highlight the exact differences, based on characters and words +" +" ____ _ ____ ____ _____ _ _ _____ ____ +" | | | || || || || | | || _ || _ | +" | _ || || __|| __|| || | | || | | || | || +" | | | || || |__ | |__ | __|| |_| || |_| || |_||_ +" | |_| || || __|| __|| | | || || __ | +" | || || | | | | |__ | _ || _ || | | | +" |____| |_||_| |_| |_____||_| |_||_| |_||_| |_| +" +" Last Change: 2021/12/07 +" Version: 8.91 +" Author: Rick Howe (Takumi Ohtani) +" Copyright: (c) 2014-2021 by Rick Howe + +if exists('g:loaded_diffchar') || !has('diff') || v:version < 800 + finish +endif +let g:loaded_diffchar = 8.91 + +let s:save_cpo = &cpoptions +set cpo&vim + +" Commands +command! -range -bar SDChar + \ call diffchar#ShowDiffChar(range(, )) +command! -range -bar RDChar + \ call diffchar#ResetDiffChar(range(, )) +command! -range -bar TDChar + \ call diffchar#ToggleDiffChar(range(, )) +command! -range -bang -bar EDChar + \ call diffchar#EchoDiffChar(range(, ), 1) + +" Configurable Keymaps +for [key, plg, cmd] in [ + \['[b', 'JumpDiffCharPrevStart', + \':call diffchar#JumpDiffChar(0, 0)'], + \[']b', 'JumpDiffCharNextStart', + \':call diffchar#JumpDiffChar(1, 0)'], + \['[e', 'JumpDiffCharPrevEnd', + \':call diffchar#JumpDiffChar(0, 1)'], + \[']e', 'JumpDiffCharNextEnd', + \':call diffchar#JumpDiffChar(1, 1)'], + \['g', 'GetDiffCharPair', + \':call diffchar#CopyDiffCharPair(0)'], + \['p', 'PutDiffCharPair', + \':call diffchar#CopyDiffCharPair(1)']] + if !hasmapto(plg, 'n') && empty(maparg(key, 'n')) + if get(g:, 'DiffCharDoMapping', 1) + execute 'nmap ' . key . ' ' . plg + endif + endif + execute 'nnoremap ' plg . ' ' . cmd . '' +endfor + +" a type of difference unit +if !exists('g:DiffUnit') + let g:DiffUnit = 'Word1' " \w\+ word and any \W single character + " let g:DiffUnit = 'Word2' " non-space and space words + " let g:DiffUnit = 'Word3' " \< or \> character class boundaries + " let g:DiffUnit = 'Char' " any single character + " let g:DiffUnit = 'CSV(,)' " split characters +endif + +" matching colors for changed units +if !exists('g:DiffColors') + let g:DiffColors = 0 " always 1 color + " let g:DiffColors = 1 " up to 4 colors in fixed order + " let g:DiffColors = 2 " up to 8 colors in fixed order + " let g:DiffColors = 3 " up to 16 colors in fixed order + " let g:DiffColors = 4 " all available colors in fixed order + " let g:DiffColors = 100 " all colors in dynamic random order +endif + +" a visibility of corresponding diff units +if !exists('g:DiffPairVisible') + let g:DiffPairVisible = 1 " highlight + " let g:DiffPairVisible = 2 " highlight + echo + " let g:DiffPairVisible = 3 " highlight + popup/floating at cursor pos + " let g:DiffPairVisible = 4 " highlight + popup/floating at mouse pos + " let g:DiffPairVisible = 0 " disable +endif + +" Set this plugin's DiffCharExpr() to the diffexpr option if empty +" and when internal diff is not used +if !exists('g:DiffExpr') + let g:DiffExpr = 1 " enable + " let g:DiffExpr = 0 " disable +endif +if g:DiffExpr && empty(&diffexpr) && &diffopt !~ 'internal' + let &diffexpr = 'diffchar#DiffCharExpr()' +endif + +" an event group of this plugin +if has('patch-8.0.736') " OptionSet triggered with diff option + let g:DiffCharInitEvent = ['augroup diffchar', 'autocmd!', + \'autocmd OptionSet diff call diffchar#ToggleDiffModeSync(0)', + \'augroup END'] + call execute(g:DiffCharInitEvent) + if has('patch-8.1.1113') || has('nvim-0.4.0') + call execute('autocmd diffchar VimEnter * ++once + \ if &diff | call diffchar#ToggleDiffModeSync(1) | endif') + else + call execute('autocmd diffchar VimEnter * + \ if &diff | call diffchar#ToggleDiffModeSync(1) | endif | + \ autocmd! diffchar VimEnter') + endif +else + let g:DiffCharInitEvent = ['augroup diffchar', 'autocmd!', + \'autocmd FilterWritePost * call diffchar#SetDiffModeSync()', + \'augroup END'] + call execute(g:DiffCharInitEvent) +endif + +let &cpoptions = s:save_cpo +unlet s:save_cpo + +" vim: ts=4 sw=4 diff --git a/.vim/bundle/vim-easy-align/autoload/easy_align.vim b/.vim/bundle/vim-easy-align/autoload/easy_align.vim new file mode 100644 index 0000000..795ea31 --- /dev/null +++ b/.vim/bundle/vim-easy-align/autoload/easy_align.vim @@ -0,0 +1,1148 @@ +" Copyright (c) 2014 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists("g:loaded_easy_align") + finish +endif +let g:loaded_easy_align = 1 + +let s:cpo_save = &cpo +set cpo&vim + +let s:easy_align_delimiters_default = { +\ ' ': { 'pattern': ' ', 'left_margin': 0, 'right_margin': 0, 'stick_to_left': 0 }, +\ '=': { 'pattern': '===\|<=>\|\(&&\|||\|<<\|>>\)=\|=\~[#?]\?\|=>\|[:+/*!%^=><&|.?-]\?=[#?]\?', +\ 'left_margin': 1, 'right_margin': 1, 'stick_to_left': 0 }, +\ ':': { 'pattern': ':', 'left_margin': 0, 'right_margin': 1, 'stick_to_left': 1 }, +\ ',': { 'pattern': ',', 'left_margin': 0, 'right_margin': 1, 'stick_to_left': 1 }, +\ '|': { 'pattern': '|', 'left_margin': 1, 'right_margin': 1, 'stick_to_left': 0 }, +\ '.': { 'pattern': '\.', 'left_margin': 0, 'right_margin': 0, 'stick_to_left': 0 }, +\ '#': { 'pattern': '#\+', 'delimiter_align': 'l', 'ignore_groups': ['!Comment'] }, +\ '"': { 'pattern': '"\+', 'delimiter_align': 'l', 'ignore_groups': ['!Comment'] }, +\ '&': { 'pattern': '\\\@ winlen ? '..' : '' + + echon "\r" + let yet = 0 + for [hl, msg] in a:tokens + if empty(msg) | continue | endif + execute "echohl ". hl + let yet += len(msg) + if yet > winlen - len(ellipsis) + echon msg[ 0 : (winlen - len(ellipsis) - yet - 1) ] . ellipsis + break + else + echon msg + endif + endfor + finally + echohl None + let [&ruler, &showcmd] = xy + endtry +endfunction + +function! s:echon(l, n, r, d, o, warn) + let tokens = [ + \ ['Function', s:live ? ':LiveEasyAlign' : ':EasyAlign'], + \ ['ModeMsg', get(s:mode_labels, a:l, a:l)], + \ ['None', ' ']] + + if a:r == -1 | call add(tokens, ['Comment', '(']) | endif + call add(tokens, [a:n =~ '*' ? 'Repeat' : 'Number', a:n]) + call extend(tokens, a:r == 1 ? + \ [['Delimiter', '/'], ['String', a:d], ['Delimiter', '/']] : + \ [['Identifier', a:d == ' ' ? '\ ' : (a:d == '\' ? '\\' : a:d)]]) + if a:r == -1 | call extend(tokens, [['Normal', '_'], ['Comment', ')']]) | endif + call add(tokens, ['Statement', empty(a:o) ? '' : ' '.string(a:o)]) + if !empty(a:warn) + call add(tokens, ['WarningMsg', ' ('.a:warn.')']) + endif + + call s:echon_(tokens) + return join(map(tokens, 'v:val[1]'), '') +endfunction + +function! s:exit(msg) + call s:echon_([['ErrorMsg', a:msg]]) + throw 'exit' +endfunction + +function! s:ltrim(str) + return substitute(a:str, '^\s\+', '', '') +endfunction + +function! s:rtrim(str) + return substitute(a:str, '\s\+$', '', '') +endfunction + +function! s:trim(str) + return substitute(a:str, '^\s*\(.\{-}\)\s*$', '\1', '') +endfunction + +function! s:fuzzy_lu(key) + if has_key(s:known_options, a:key) + return a:key + endif + let key = tolower(a:key) + + " stl -> ^s.*_t.*_l.* + let regexp1 = '^' .key[0]. '.*' .substitute(key[1 : -1], '\(.\)', '_\1.*', 'g') + let matches = filter(keys(s:known_options), 'v:val =~ regexp1') + if len(matches) == 1 + return matches[0] + endif + + " stl -> ^s.*t.*l.* + let regexp2 = '^' . substitute(substitute(key, '-', '_', 'g'), '\(.\)', '\1.*', 'g') + let matches = filter(keys(s:known_options), 'v:val =~ regexp2') + + if empty(matches) + call s:exit("Unknown option key: ". a:key) + elseif len(matches) == 1 + return matches[0] + else + " Avoid ambiguity introduced by deprecated margin_left and margin_right + if sort(matches) == ['margin_left', 'margin_right', 'mode_sequence'] + return 'mode_sequence' + endif + if sort(matches) == ['ignore_groups', 'ignores'] + return 'ignore_groups' + endif + call s:exit("Ambiguous option key: ". a:key ." (" .join(matches, ', '). ")") + endif +endfunction + +function! s:shift(modes, cycle) + let item = remove(a:modes, 0) + if a:cycle || empty(a:modes) + call add(a:modes, item) + endif + return item +endfunction + +function! s:normalize_options(opts) + let ret = {} + for k in keys(a:opts) + let v = a:opts[k] + let k = s:fuzzy_lu(k) + " Backward-compatibility + if k == 'margin_left' | let k = 'left_margin' | endif + if k == 'margin_right' | let k = 'right_margin' | endif + if k == 'mode_sequence' | let k = 'align' | endif + let ret[k] = v + unlet v + endfor + return s:validate_options(ret) +endfunction + +function! s:compact_options(opts) + let ret = {} + for k in keys(a:opts) + let ret[s:shorthand[k]] = a:opts[k] + endfor + return ret +endfunction + +function! s:validate_options(opts) + for k in keys(a:opts) + let v = a:opts[k] + if index(s:known_options[k], type(v)) == -1 + call s:exit("Invalid type for option: ". k) + endif + unlet v + endfor + return a:opts +endfunction + +function! s:split_line(line, nth, modes, cycle, fc, lc, pattern, stick_to_left, ignore_unmatched, ignore_groups) + let mode = '' + + let string = a:lc ? + \ strpart(getline(a:line), a:fc - 1, a:lc - a:fc + 1) : + \ strpart(getline(a:line), a:fc - 1) + let idx = 0 + let nomagic = match(a:pattern, '\\v') > match(a:pattern, '\C\\[mMV]') + let pattern = '^.\{-}\s*\zs\('.a:pattern.(nomagic ? ')' : '\)') + let tokens = [] + let delims = [] + + " Phase 1: split + let ignorable = 0 + let token = '' + let phantom = 0 + while 1 + let matchidx = match(string, pattern, idx) + " No match + if matchidx < 0 | break | endif + let matchend = matchend(string, pattern, idx) + let spaces = matchstr(string, '\s'.(a:stick_to_left ? '*' : '\{-}'), matchend + (matchidx == matchend)) + + " Match, but empty + if len(spaces) + matchend - idx == 0 + let char = strpart(string, idx, 1) + if empty(char) | break | endif + let [match, part, delim] = [char, char, ''] + " Match + else + let match = strpart(string, idx, matchend - idx + len(spaces)) + let part = strpart(string, idx, matchidx - idx) + let delim = strpart(string, matchidx, matchend - matchidx) + endif + + let ignorable = s:highlighted_as(a:line, idx + len(part) + a:fc, a:ignore_groups) + if ignorable + let token .= match + else + let [pmode, mode] = [mode, s:shift(a:modes, a:cycle)] + call add(tokens, token . match) + call add(delims, delim) + let token = '' + endif + + let idx += len(match) + + " If the string is non-empty and ends with the delimiter, + " append an empty token to the list + if idx == len(string) + let phantom = 1 + break + endif + endwhile + + let leftover = token . strpart(string, idx) + if !empty(leftover) + let ignorable = s:highlighted_as(a:line, len(string) + a:fc - 1, a:ignore_groups) + call add(tokens, leftover) + call add(delims, '') + elseif phantom + call add(tokens, '') + call add(delims, '') + endif + let [pmode, mode] = [mode, s:shift(a:modes, a:cycle)] + + " Preserve indentation - merge first two tokens + if len(tokens) > 1 && empty(s:rtrim(tokens[0])) + let tokens[1] = tokens[0] . tokens[1] + call remove(tokens, 0) + call remove(delims, 0) + let mode = pmode + endif + + " Skip comment line + if ignorable && len(tokens) == 1 && a:ignore_unmatched + let tokens = [] + let delims = [] + " Append an empty item to enable right/center alignment of the last token + " - if the last token is not ignorable or ignorable but not the only token + elseif a:ignore_unmatched != 1 && + \ (mode ==? 'r' || mode ==? 'c') && + \ (!ignorable || len(tokens) > 1) && + \ a:nth >= 0 " includes -0 + call add(tokens, '') + call add(delims, '') + endif + + return [tokens, delims] +endfunction + +function! s:do_align(todo, modes, all_tokens, all_delims, fl, ll, fc, lc, nth, recur, dict) + let mode = a:modes[0] + let lines = {} + let min_indent = -1 + let max = { 'pivot_len2': 0, 'token_len': 0, 'just_len': 0, 'delim_len': 0, + \ 'indent': 0, 'tokens': 0, 'strip_len': 0 } + let d = a:dict + let [f, fx] = s:parse_filter(d.filter) + + " Phase 1 + for line in range(a:fl, a:ll) + let snip = a:lc > 0 ? getline(line)[a:fc-1 : a:lc-1] : getline(line) + if f == 1 && snip !~ fx + continue + elseif f == -1 && snip =~ fx + continue + endif + + if !has_key(a:all_tokens, line) + " Split line into the tokens by the delimiters + let [tokens, delims] = s:split_line( + \ line, a:nth, copy(a:modes), a:recur == 2, + \ a:fc, a:lc, d.pattern, + \ d.stick_to_left, d.ignore_unmatched, d.ignore_groups) + + " Remember tokens for subsequent recursive calls + let a:all_tokens[line] = tokens + let a:all_delims[line] = delims + else + let tokens = a:all_tokens[line] + let delims = a:all_delims[line] + endif + + " Skip empty lines + if empty(tokens) + continue + endif + + " Calculate the maximum number of tokens for a line within the range + let max.tokens = max([max.tokens, len(tokens)]) + + if a:nth > 0 " Positive N-th + if len(tokens) < a:nth + continue + endif + let nth = a:nth - 1 " make it 0-based + else " -0 or Negative N-th + if a:nth == 0 && mode !=? 'l' + let nth = len(tokens) - 1 + else + let nth = len(tokens) + a:nth + endif + if empty(delims[len(delims) - 1]) + let nth -= 1 + endif + + if nth < 0 || nth == len(tokens) + continue + endif + endif + + let prefix = nth > 0 ? join(tokens[0 : nth - 1], '') : '' + let delim = delims[nth] + let token = s:rtrim( tokens[nth] ) + let token = s:rtrim( strpart(token, 0, len(token) - len(s:rtrim(delim))) ) + if empty(delim) && !exists('tokens[nth + 1]') && d.ignore_unmatched + continue + endif + + let indent = s:strwidth(matchstr(tokens[0], '^\s*')) + if min_indent < 0 || indent < min_indent + let min_indent = indent + endif + if mode ==? 'c' + let token .= substitute(matchstr(token, '^\s*'), '\t', repeat(' ', &tabstop), 'g') + endif + let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)] + let max.indent = max([max.indent, indent]) + let max.token_len = max([max.token_len, tw]) + let max.just_len = max([max.just_len, pw + tw]) + let max.delim_len = max([max.delim_len, s:strwidth(delim)]) + + if mode ==? 'c' + let pivot_len2 = pw * 2 + tw + if max.pivot_len2 < pivot_len2 + let max.pivot_len2 = pivot_len2 + endif + let max.strip_len = max([max.strip_len, s:strwidth(s:trim(token))]) + endif + let lines[line] = [nth, prefix, token, delim] + endfor + + " Phase 1-5: indentation handling (only on a:nth == 1) + if a:nth == 1 + let idt = d.indentation + if idt ==? 'd' + let indent = max.indent + elseif idt ==? 's' + let indent = min_indent + elseif idt ==? 'n' + let indent = 0 + elseif idt !=? 'k' + call s:exit('Invalid indentation: ' . idt) + end + + if idt !=? 'k' + let max.just_len = 0 + let max.token_len = 0 + let max.pivot_len2 = 0 + + for [line, elems] in items(lines) + let [nth, prefix, token, delim] = elems + + let tindent = matchstr(token, '^\s*') + while 1 + let len = s:strwidth(tindent) + if len < indent + let tindent .= repeat(' ', indent - len) + break + elseif len > indent + let tindent = tindent[0 : -2] + else + break + endif + endwhile + + let token = tindent . s:ltrim(token) + if mode ==? 'c' + let token = substitute(token, '\s*$', repeat(' ', indent), '') + endif + let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)] + let max.token_len = max([max.token_len, tw]) + let max.just_len = max([max.just_len, pw + tw]) + if mode ==? 'c' + let pivot_len2 = pw * 2 + tw + if max.pivot_len2 < pivot_len2 + let max.pivot_len2 = pivot_len2 + endif + endif + + let lines[line][2] = token + endfor + endif + endif + + " Phase 2 + for [line, elems] in items(lines) + let tokens = a:all_tokens[line] + let delims = a:all_delims[line] + let [nth, prefix, token, delim] = elems + + " Remove the leading whitespaces of the next token + if len(tokens) > nth + 1 + let tokens[nth + 1] = s:ltrim(tokens[nth + 1]) + endif + + " Pad the token with spaces + let [pw, tw] = [s:strwidth(prefix), s:strwidth(token)] + let rpad = '' + if mode ==? 'l' + let pad = repeat(' ', max.just_len - pw - tw) + if d.stick_to_left + let rpad = pad + else + let token = token . pad + endif + elseif mode ==? 'r' + let pad = repeat(' ', max.just_len - pw - tw) + let indent = matchstr(token, '^\s*') + let token = indent . pad . s:ltrim(token) + elseif mode ==? 'c' + let p1 = max.pivot_len2 - (pw * 2 + tw) + let p2 = max.token_len - tw + let pf1 = s:floor2(p1) + if pf1 < p1 | let p2 = s:ceil2(p2) + else | let p2 = s:floor2(p2) + endif + let strip = s:ceil2(max.token_len - max.strip_len) / 2 + let indent = matchstr(token, '^\s*') + let token = indent. repeat(' ', pf1 / 2) .s:ltrim(token). repeat(' ', p2 / 2) + let token = substitute(token, repeat(' ', strip) . '$', '', '') + + if d.stick_to_left + if empty(s:rtrim(token)) + let center = len(token) / 2 + let [token, rpad] = [strpart(token, 0, center), strpart(token, center)] + else + let [token, rpad] = [s:rtrim(token), matchstr(token, '\s*$')] + endif + endif + endif + let tokens[nth] = token + + " Pad the delimiter + let dpadl = max.delim_len - s:strwidth(delim) + let da = d.delimiter_align + if da ==? 'l' + let [dl, dr] = ['', repeat(' ', dpadl)] + elseif da ==? 'c' + let dl = repeat(' ', dpadl / 2) + let dr = repeat(' ', dpadl - dpadl / 2) + elseif da ==? 'r' + let [dl, dr] = [repeat(' ', dpadl), ''] + else + call s:exit('Invalid delimiter_align: ' . da) + endif + + " Before and after the range (for blockwise visual mode) + let cline = getline(line) + let before = strpart(cline, 0, a:fc - 1) + let after = a:lc ? strpart(cline, a:lc) : '' + + " Determine the left and right margin around the delimiter + let rest = join(tokens[nth + 1 : -1], '') + let nomore = empty(rest.after) + let ml = (empty(prefix . token) || empty(delim) && nomore) ? '' : d.ml + let mr = nomore ? '' : d.mr + + " Adjust indentation of the lines starting with a delimiter + let lpad = '' + if nth == 0 + let ipad = repeat(' ', min_indent - s:strwidth(token.ml)) + if mode ==? 'l' + let token = ipad . token + else + let lpad = ipad + endif + endif + + " Align the token + let aligned = join([lpad, token, ml, dl, delim, dr, mr, rpad], '') + let tokens[nth] = aligned + + " Update the line + let a:todo[line] = before.join(tokens, '').after + endfor + + if a:nth < max.tokens && (a:recur || len(a:modes) > 1) + call s:shift(a:modes, a:recur == 2) + return [a:todo, a:modes, a:all_tokens, a:all_delims, + \ a:fl, a:ll, a:fc, a:lc, a:nth + 1, a:recur, a:dict] + endif + return [a:todo] +endfunction + +function! s:input(str, default, vis) + if a:vis + normal! gv + redraw + execute "normal! \" + else + " EasyAlign command can be called without visual selection + redraw + endif + let got = input(a:str, a:default) + return got +endfunction + +function! s:atoi(str) + return (a:str =~ '^[0-9]\+$') ? str2nr(a:str) : a:str +endfunction + +function! s:shift_opts(opts, key, vals) + let val = s:shift(a:vals, 1) + if type(val) == 0 && val == -1 + call remove(a:opts, a:key) + else + let a:opts[a:key] = val + endif +endfunction + +function! s:interactive(range, modes, n, d, opts, rules, vis, bvis) + let mode = s:shift(a:modes, 1) + let n = a:n + let d = a:d + let ch = '' + let opts = s:compact_options(a:opts) + let vals = deepcopy(s:option_values) + let regx = 0 + let warn = '' + let undo = 0 + + while 1 + " Live preview + let rdrw = 0 + if undo + silent! undo + let undo = 0 + let rdrw = 1 + endif + if s:live && !empty(d) + let output = s:process(a:range, mode, n, d, s:normalize_options(opts), regx, a:rules, a:bvis) + let &undolevels = &undolevels " Break undo block + call s:update_lines(output.todo) + let undo = !empty(output.todo) + let rdrw = 1 + endif + if rdrw + if a:vis + normal! gv + endif + redraw + if a:vis | execute "normal! \" | endif + endif + call s:echon(mode, n, -1, regx ? '/'.d.'/' : d, opts, warn) + + let check = 0 + let warn = '' + + try + let c = getchar() + catch /^Vim:Interrupt$/ + let c = 27 + endtry + let ch = nr2char(c) + if c == 3 || c == 27 " CTRL-C / ESC + if undo + silent! undo + endif + throw 'exit' + elseif c == "\" + if !empty(d) + let d = '' + let regx = 0 + elseif len(n) > 0 + let n = strpart(n, 0, len(n) - 1) + endif + elseif c == 13 " Enter key + let mode = s:shift(a:modes, 1) + if has_key(opts, 'a') + let opts.a = mode . strpart(opts.a, 1) + endif + elseif ch == '-' + if empty(n) | let n = '-' + elseif n == '-' | let n = '' + else | let check = 1 + endif + elseif ch == '*' + if empty(n) | let n = '*' + elseif n == '*' | let n = '**' + elseif n == '**' | let n = '' + else | let check = 1 + endif + elseif empty(d) && ((c == 48 && len(n) > 0) || c > 48 && c <= 57) " Numbers + if n[0] == '*' | let check = 1 + else | let n = n . ch + end + elseif ch == "\" + call s:shift_opts(opts, 'da', vals['delimiter_align']) + elseif ch == "\" + call s:shift_opts(opts, 'idt', vals['indentation']) + elseif ch == "\" + let lm = s:input("Left margin: ", get(opts, 'lm', ''), a:vis) + if empty(lm) + let warn = 'Set to default. Input 0 to remove it' + silent! call remove(opts, 'lm') + else + let opts['lm'] = s:atoi(lm) + endif + elseif ch == "\" + let rm = s:input("Right margin: ", get(opts, 'rm', ''), a:vis) + if empty(rm) + let warn = 'Set to default. Input 0 to remove it' + silent! call remove(opts, 'rm') + else + let opts['rm'] = s:atoi(rm) + endif + elseif ch == "\" + call s:shift_opts(opts, 'iu', vals['ignore_unmatched']) + elseif ch == "\" + call s:shift_opts(opts, 'ig', vals['ignore_groups']) + elseif ch == "\" + if s:live + if !empty(d) + let ch = d + break + else + let s:live = 0 + endif + else + let s:live = 1 + endif + elseif c == "\" + let opts['stl'] = 1 + let opts['lm'] = 0 + elseif c == "\" + let opts['stl'] = 0 + let opts['lm'] = 1 + elseif c == "\" + let opts['lm'] = 0 + let opts['rm'] = 0 + elseif c == "\" + silent! call remove(opts, 'stl') + silent! call remove(opts, 'lm') + silent! call remove(opts, 'rm') + elseif ch == "\" || ch == "\" + let modes = tolower(s:input("Alignment ([lrc...][[*]*]): ", get(opts, 'a', mode), a:vis)) + if match(modes, '^[lrc]\+\*\{0,2}$') != -1 + let opts['a'] = modes + let mode = modes[0] + while mode != s:shift(a:modes, 1) + endwhile + else + silent! call remove(opts, 'a') + endif + elseif ch == "\" || ch == "\" + if s:live && regx && !empty(d) + break + endif + + let prompt = 'Regular expression: ' + let ch = s:input(prompt, '', a:vis) + if !empty(ch) && s:valid_regexp(ch) + let regx = 1 + let d = ch + if !s:live | break | endif + else + let warn = 'Invalid regular expression: '.ch + endif + elseif ch == "\" + let f = s:input("Filter (g/../ or v/../): ", get(opts, 'f', ''), a:vis) + let m = matchlist(f, '^[gv]/\(.\{-}\)/\?$') + if empty(f) + silent! call remove(opts, 'f') + elseif !empty(m) && s:valid_regexp(m[1]) + let opts['f'] = f + else + let warn = 'Invalid filter expression' + endif + elseif ch =~ '[[:print:]]' + let check = 1 + else + let warn = 'Invalid character' + endif + + if check + if empty(d) + if has_key(a:rules, ch) + let d = ch + if !s:live + if a:vis + execute "normal! gv\" + endif + break + endif + else + let warn = 'Unknown delimiter key: '.ch + endif + else + if regx + let warn = 'Press to finish' + else + if d == ch + break + else + let warn = 'Press '''.d.''' again to finish' + endif + end + endif + endif + endwhile + if s:live + let copts = call('s:summarize', output.summarize) + let s:live = 0 + let g:easy_align_last_command = s:echon('', n, regx, d, copts, '') + let s:live = 1 + end + return [mode, n, ch, opts, regx] +endfunction + +function! s:valid_regexp(regexp) + try + call matchlist('', a:regexp) + catch + return 0 + endtry + return 1 +endfunction + +function! s:test_regexp(regexp) + let regexp = empty(a:regexp) ? @/ : a:regexp + if !s:valid_regexp(regexp) + call s:exit('Invalid regular expression: '. regexp) + endif + return regexp +endfunction + +let s:shorthand_regex = + \ '\s*\%(' + \ .'\(lm\?[0-9]\+\)\|\(rm\?[0-9]\+\)\|\(iu[01]\)\|\(\%(s\%(tl\)\?[01]\)\|[<>]\)\|' + \ .'\(da\?[clr]\)\|\(\%(ms\?\|a\)[lrc*]\+\)\|\(i\%(dt\)\?[kdsn]\)\|\([gv]/.*/\)\|\(ig\[.*\]\)' + \ .'\)\+\s*$' + +function! s:parse_shorthand_opts(expr) + let opts = {} + let expr = substitute(a:expr, '\s', '', 'g') + let regex = '^'. s:shorthand_regex + + if empty(expr) + return opts + elseif expr !~ regex + call s:exit("Invalid expression: ". a:expr) + else + let match = matchlist(expr, regex) + for m in filter(match[ 1 : -1 ], '!empty(v:val)') + for key in ['lm', 'rm', 'l', 'r', 'stl', 's', '<', '>', 'iu', 'da', 'd', 'ms', 'm', 'ig', 'i', 'g', 'v', 'a'] + if stridx(tolower(m), key) == 0 + let rest = strpart(m, len(key)) + if key == 'i' | let key = 'idt' | endif + if key == 'g' || key == 'v' + let rest = key.rest + let key = 'f' + endif + + if key == 'idt' || index(['d', 'f', 'm', 'a'], key[0]) >= 0 + let opts[key] = rest + elseif key == 'ig' + try + let arr = eval(rest) + if type(arr) == 3 + let opts[key] = arr + else + throw 'Not an array' + endif + catch + call s:exit("Invalid ignore_groups: ". a:expr) + endtry + elseif key =~ '[<>]' + let opts['stl'] = key == '<' + else + let opts[key] = str2nr(rest) + endif + break + endif + endfor + endfor + endif + return s:normalize_options(opts) +endfunction + +function! s:parse_args(args) + if empty(a:args) + return ['', '', {}, 0] + endif + let n = '' + let ch = '' + let args = a:args + let cand = '' + let opts = {} + + " Poor man's option parser + let idx = 0 + while 1 + let midx = match(args, '\s*{.*}\s*$', idx) + if midx == -1 | break | endif + + let cand = strpart(args, midx) + try + let [l, r, c, k, s, d, n] = ['l', 'r', 'c', 'k', 's', 'd', 'n'] + let [L, R, C, K, S, D, N] = ['l', 'r', 'c', 'k', 's', 'd', 'n'] + let o = eval(cand) + if type(o) == 4 + let opts = o + if args[midx - 1 : midx] == '\ ' + let midx += 1 + endif + let args = strpart(args, 0, midx) + break + endif + catch + " Ignore + endtry + let idx = midx + 1 + endwhile + + " Invalid option dictionary + if len(substitute(cand, '\s', '', 'g')) > 2 && empty(opts) + call s:exit("Invalid option: ". cand) + else + let opts = s:normalize_options(opts) + endif + + " Shorthand option notation + let sopts = matchstr(args, s:shorthand_regex) + if !empty(sopts) + let args = strpart(args, 0, len(args) - len(sopts)) + let opts = extend(s:parse_shorthand_opts(sopts), opts) + endif + + " Has /Regexp/? + let matches = matchlist(args, '^\(.\{-}\)\s*/\(.*\)/\s*$') + + " Found regexp + if !empty(matches) + return [matches[1], s:test_regexp(matches[2]), opts, 1] + else + let tokens = matchlist(args, '^\([1-9][0-9]*\|-[0-9]*\|\*\*\?\)\?\s*\(.\{-}\)\?$') + " Try swapping n and ch + let [n, ch] = empty(tokens[2]) ? reverse(tokens[1:2]) : tokens[1:2] + + " Resolving command-line ambiguity + " '\ ' => ' ' + " '\' => ' ' + if ch =~ '^\\\s*$' + let ch = ' ' + " '\\' => '\' + elseif ch =~ '^\\\\\s*$' + let ch = '\' + endif + + return [n, ch, opts, 0] + endif +endfunction + +function! s:parse_filter(f) + let m = matchlist(a:f, '^\([gv]\)/\(.\{-}\)/\?$') + if empty(m) + return [0, ''] + else + return [m[1] == 'g' ? 1 : -1, m[2]] + endif +endfunction + +function! s:interactive_modes(bang) + return get(g:, + \ (a:bang ? 'easy_align_bang_interactive_modes' : 'easy_align_interactive_modes'), + \ (a:bang ? ['r', 'l', 'c'] : ['l', 'r', 'c'])) +endfunction + +function! s:alternating_modes(mode) + return a:mode ==? 'r' ? 'rl' : 'lr' +endfunction + +function! s:update_lines(todo) + for [line, content] in items(a:todo) + call setline(line, s:rtrim(content)) + endfor +endfunction + +function! s:parse_nth(n) + let n = a:n + let recur = 0 + if n == '*' | let [nth, recur] = [1, 1] + elseif n == '**' | let [nth, recur] = [1, 2] + elseif n == '-' | let nth = -1 + elseif empty(n) | let nth = 1 + elseif n == '0' || ( n != '-0' && n != string(str2nr(n)) ) + call s:exit('Invalid N-th parameter: '. n) + else + let nth = n + endif + return [nth, recur] +endfunction + +function! s:build_dict(delimiters, ch, regexp, opts) + if a:regexp + let dict = { 'pattern': a:ch } + else + if !has_key(a:delimiters, a:ch) + call s:exit('Unknown delimiter key: '. a:ch) + endif + let dict = copy(a:delimiters[a:ch]) + endif + call extend(dict, a:opts) + + let ml = get(dict, 'left_margin', ' ') + let mr = get(dict, 'right_margin', ' ') + if type(ml) == 0 | let ml = repeat(' ', ml) | endif + if type(mr) == 0 | let mr = repeat(' ', mr) | endif + call extend(dict, { 'ml': ml, 'mr': mr }) + + let dict.pattern = get(dict, 'pattern', a:ch) + let dict.delimiter_align = + \ get(dict, 'delimiter_align', get(g:, 'easy_align_delimiter_align', 'r'))[0] + let dict.indentation = + \ get(dict, 'indentation', get(g:, 'easy_align_indentation', 'k'))[0] + let dict.stick_to_left = + \ get(dict, 'stick_to_left', 0) + let dict.ignore_unmatched = + \ get(dict, 'ignore_unmatched', get(g:, 'easy_align_ignore_unmatched', 2)) + let dict.ignore_groups = + \ get(dict, 'ignore_groups', get(dict, 'ignores', s:ignored_syntax())) + let dict.filter = + \ get(dict, 'filter', '') + return dict +endfunction + +function! s:build_mode_sequence(expr, recur) + let [expr, recur] = [a:expr, a:recur] + let suffix = matchstr(a:expr, '\*\+$') + if suffix == '*' + let expr = expr[0 : -2] + let recur = 1 + elseif suffix == '**' + let expr = expr[0 : -3] + let recur = 2 + endif + return [tolower(expr), recur] +endfunction + +function! s:process(range, mode, n, ch, opts, regexp, rules, bvis) + let [nth, recur] = s:parse_nth((empty(a:n) && exists('g:easy_align_nth')) ? g:easy_align_nth : a:n) + let dict = s:build_dict(a:rules, a:ch, a:regexp, a:opts) + let [mode_sequence, recur] = s:build_mode_sequence( + \ get(dict, 'align', recur == 2 ? s:alternating_modes(a:mode) : a:mode), + \ recur) + + let ve = &virtualedit + set ve=all + let args = [ + \ {}, split(mode_sequence, '\zs'), + \ {}, {}, a:range[0], a:range[1], + \ a:bvis ? min([virtcol("'<"), virtcol("'>")]) : 1, + \ (!recur && a:bvis) ? max([virtcol("'<"), virtcol("'>")]) : 0, + \ nth, recur, dict ] + let &ve = ve + while len(args) > 1 + let args = call('s:do_align', args) + endwhile + + " todo: lines to update + " summarize: arguments to s:summarize + return { 'todo': args[0], 'summarize': [ a:opts, recur, mode_sequence ] } +endfunction + +function s:summarize(opts, recur, mode_sequence) + let copts = s:compact_options(a:opts) + let nbmode = s:interactive_modes(0)[0] + if !has_key(copts, 'a') && ( + \ (a:recur == 2 && s:alternating_modes(nbmode) != a:mode_sequence) || + \ (a:recur != 2 && (a:mode_sequence[0] != nbmode || len(a:mode_sequence) > 1)) + \ ) + call extend(copts, { 'a': a:mode_sequence }) + endif + return copts +endfunction + +function! s:align(bang, live, visualmode, first_line, last_line, expr) + " Heuristically determine if the user was in visual mode + if a:visualmode == 'command' + let vis = a:first_line == line("'<") && a:last_line == line("'>") + let bvis = vis && visualmode() == "\" + elseif empty(a:visualmode) + let vis = 0 + let bvis = 0 + else + let vis = 1 + let bvis = a:visualmode == "\" + end + let range = [a:first_line, a:last_line] + let modes = s:interactive_modes(a:bang) + let mode = modes[0] + let s:live = a:live + + let rules = s:easy_align_delimiters_default + if exists('g:easy_align_delimiters') + let rules = extend(copy(rules), g:easy_align_delimiters) + endif + + let [n, ch, opts, regexp] = s:parse_args(a:expr) + + let bypass_fold = get(g:, 'easy_align_bypass_fold', 0) + let ofm = &l:foldmethod + try + if bypass_fold | let &l:foldmethod = 'manual' | endif + + if empty(n) && empty(ch) || s:live + let [mode, n, ch, opts, regexp] = s:interactive(range, copy(modes), n, ch, opts, rules, vis, bvis) + endif + + if !s:live + let output = s:process(range, mode, n, ch, s:normalize_options(opts), regexp, rules, bvis) + call s:update_lines(output.todo) + let copts = call('s:summarize', output.summarize) + let g:easy_align_last_command = s:echon('', n, regexp, ch, copts, '') + endif + finally + if bypass_fold | let &l:foldmethod = ofm | endif + endtry +endfunction + +function! easy_align#align(bang, live, visualmode, expr) range + try + call s:align(a:bang, a:live, a:visualmode, a:firstline, a:lastline, a:expr) + catch /^\%(Vim:Interrupt\|exit\)$/ + if empty(a:visualmode) + echon "\r" + echon "\r" + else + normal! gv + endif + endtry +endfunction + +let &cpo = s:cpo_save +unlet s:cpo_save + diff --git a/.vim/bundle/vim-easy-align/doc/easy_align.txt b/.vim/bundle/vim-easy-align/doc/easy_align.txt new file mode 100644 index 0000000..5a82e5e --- /dev/null +++ b/.vim/bundle/vim-easy-align/doc/easy_align.txt @@ -0,0 +1,891 @@ +easy-align.txt easy-align Last change: December 14 2014 +EASY-ALIGN - TABLE OF CONTENTS *easyalign* *easy-align* *easy-align-toc* +============================================================================== + + vim-easy-align + Demo |easy-align-1| + Features |easy-align-2| + Installation |easy-align-3| + TLDR - One-minute guide |easy-align-4| + Usage |easy-align-5| + Concept of alignment rule |easy-align-5-1| + Execution models |easy-align-5-2| + 1. Using mappings |easy-align-5-2-1| + 2. Using :EasyAlign command |easy-align-5-2-2| + Interactive mode |easy-align-5-3| + Predefined alignment rules |easy-align-5-3-1| + Examples |easy-align-5-3-2| + Using regular expressions |easy-align-5-3-3| + Alignment options in interactive mode |easy-align-5-3-4| + Live interactive mode |easy-align-5-4| + Non-interactive mode |easy-align-5-5| + Partial alignment in blockwise-visual mode |easy-align-5-6| + Alignment options |easy-align-6| + List of options |easy-align-6-1| + Filtering lines |easy-align-6-2| + Examples |easy-align-6-2-1| + Ignoring delimiters in comments or strings |easy-align-6-3| + Ignoring unmatched lines |easy-align-6-4| + Aligning delimiters of different lengths |easy-align-6-5| + Adjusting indentation |easy-align-6-6| + Alignments over multiple occurrences of delimiters |easy-align-6-7| + Extending alignment rules |easy-align-6-8| + Examples |easy-align-6-8-1| + Other options |easy-align-7| + Disabling &foldmethod during alignment |easy-align-7-1| + Left/right/center mode switch in interactive mode |easy-align-7-2| + Advanced examples and use cases |easy-align-8| + Related work |easy-align-9| + Author |easy-align-10| + License |easy-align-11| + + +VIM-EASY-ALIGN *vim-easy-align* +============================================================================== + +A simple, easy-to-use Vim alignment plugin. + + + *easy-align-1* +DEMO *easy-align-demo* +============================================================================== + +Screencast: +https://raw.githubusercontent.com/junegunn/i/master/vim-easy-align.gif + +(Too fast? Slower GIF is {here}{1}) + +{1} https://raw.githubusercontent.com/junegunn/i/master/vim-easy-align-slow.gif + + + *easy-align-2* +FEATURES *easy-align-features* +============================================================================== + + - Easy to use + - Comes with a predefined set of alignment rules + - Provides a fast and intuitive interface + - Extensible + - You can define your own rules + - Supports arbitrary regular expressions + - Optimized for code editing + - Takes advantage of syntax highlighting feature to avoid unwanted + alignments + + + *easy-align-3* +INSTALLATION *easy-align-installation* +============================================================================== + +Use your favorite plugin manager. + +Using {vim-plug}{2}: +> + Plug 'junegunn/vim-easy-align' +< + {2} https://github.com/junegunn/vim-plug + + + *easy-align-4* +TLDR - ONE-MINUTE GUIDE *easy-align-tldr-one-minute-guide* +============================================================================== + +Add the following mappings to your .vimrc. + + *(EasyAlign)* +> + " Start interactive EasyAlign in visual mode (e.g. vip) + vmap (EasyAlign) + + " Start interactive EasyAlign for a motion/text object (e.g. gaip) + nmap ga (EasyAlign) +< +And with the following lines of text, +> + apple =red + grass+=green + sky-= blue +< +try these commands: + + - vip= + - `v`isual-select `i`nner `p`aragraph + - Start EasyAlign command () + - Align around `=` + - `gaip=` + - Start EasyAlign command (`ga`) for `i`nner `p`aragraph + - Align around `=` + +Notice that the commands are repeatable with `.` key if you have installed +{repeat.vim}{3}. Install {visualrepeat}{4} as well if you want to repeat in +visual mode. + + {3} https://github.com/tpope/vim-repeat + {4} https://github.com/vim-scripts/visualrepeat + + + *easy-align-5* +USAGE *easy-align-usage* +============================================================================== + + +< Concept of alignment rule >_________________________________________________~ + *easy-align-concept-of-alignment-rule* + *easy-align-5-1* + +Though easy-align can align lines of text around any delimiter, it provides +shortcuts for the most common use cases with the concept of "alignment rule". + +An alignment rule is a predefined set of options for common alignment tasks, +which is identified by a single character, DELIMITER KEY, such as , +`=`, `:`, `.`, `|`, `&`, `#`, and `,`. + +Think of it as a shortcut. Instead of writing regular expression and setting +several options, you can just type in a single character. + + +< Execution models >__________________________________________________________~ + *easy-align-execution-models* + *easy-align-5-2* + +There are two ways to use easy-align. + + +1. Using mappings~ + *easy-align-1-using-plug-mappings* + *easy-align-5-2-1* + +The recommended method is to use mappings as described earlier. + + *(LiveEasyAlign)* + + ----------------------+--------+----------------------------------------------------- + Mapping | Mode | Description ~ + ----------------------+--------+----------------------------------------------------- + (EasyAlign) | normal | Start interactive mode for a motion/text object + (EasyAlign) | visual | Start interactive mode for the selection + (LiveEasyAlign) | normal | Start live-interactive mode for a motion/text object + (LiveEasyAlign) | visual | Start live-interactive mode for the selection + ----------------------+--------+----------------------------------------------------- + + +2. Using :EasyAlign command~ + *easy-align-2-using-easyalign-command* + *easy-align-5-2-2* + + *:EasyAlign* + +If you prefer command-line or do not want to start interactive mode, you can +use `:EasyAlign` command instead. + + *:LiveEasyAlign* + + -------------------------------------------+----------------------------------------------- + Mode | Command ~ + -------------------------------------------+----------------------------------------------- + Interactive mode | `:EasyAlign[!] [OPTIONS]` + Live interactive mode | `:LiveEasyAlign[!] [...]` + Non-interactive mode (predefined rules) | `:EasyAlign[!] [N-th] DELIMITER_KEY [OPTIONS]` + Non-interactive mode (regular expressions) | `:EasyAlign[!] [N-th] /REGEXP/ [OPTIONS]` + -------------------------------------------+----------------------------------------------- + + +< Interactive mode >__________________________________________________________~ + *easy-align-interactive-mode* + *easy-align-5-3* + +The following sections will assume that you have (EasyAlign) mappings in +your .vimrc as below: +> + " Start interactive EasyAlign in visual mode (e.g. vip) + vmap (EasyAlign) + + " Start interactive EasyAlign for a motion/text object (e.g. gaip) + nmap ga (EasyAlign) +< +With these mappings, you can align text with only a few keystrokes. + + 1. key in visual mode, or `ga` followed by a motion or a text object to + start interactive mode + 2. Optional: Enter keys to select alignment mode (left, right, or center) + 3. Optional: N-th delimiter (default: 1) + - `1` Around the 1st occurrences of delimiters + - `2` Around the 2nd occurrences of delimiters + - ... + - `*` Around all occurrences of delimiters + - `**` Left-right alternating alignment around all delimiters + - `-` Around the last occurrences of delimiters (`-1`) + - `-2` Around the second to last occurrences of delimiters + - ... + 4. Delimiter key (a single keystroke; , `=`, `:`, `.`, `|`, `&`, `#`, `,`) + + +Predefined alignment rules~ + *easy-align-predefined-alignment-rules* + *easy-align-5-3-1* + + --------------+-------------------------------------------------------------------- + Delimiter key | Description/Use cases ~ + --------------+-------------------------------------------------------------------- + | General alignment around whitespaces + `=` | Operators containing equals sign ( `=` , `==,` `!=` , `+=` , `&&=` , ...) + `:` | Suitable for formatting JSON or YAML + `.` | Multi-line method chaining + `,` | Multi-line method arguments + `&` | LaTeX tables (matches `&` and `\\` ) + `#` | Ruby/Python comments + `"` | Vim comments + | Table markdown + --------------+-------------------------------------------------------------------- + + *g:easy_align_delimiters* + +You can override these default rules or define your own rules with +`g:easy_align_delimiters`, which will be described in {the later section}{5}. + + {5} https://github.com/junegunn/vim-easy-align#extending-alignment-rules + + +Examples~ + *easy-align-examples* + *easy-align-5-3-2* + + ------------------+------------------------------------+-------------------- + With visual map | Description | Equivalent command ~ + ------------------+------------------------------------+-------------------- + | Around 1st whitespaces | :'<,'>EasyAlign\ + 2 | Around 2nd whitespaces | :'<,'>EasyAlign2\ + - | Around the last whitespaces | :'<,'>EasyAlign-\ + -2 | Around the 2nd to last whitespaces | :'<,'>EasyAlign-2\ + : | Around 1st colon ( `key: value` ) | :'<,'>EasyAlign: + : | Around 1st colon ( `key : value` ) | :'<,'>EasyAlign:= | Around 1st operators with = | :'<,'>EasyAlign= + 3= | Around 3rd operators with = | :'<,'>EasyAlign3= + *= | Around all operators with = | :'<,'>EasyAlign*= + **= | Left-right alternating around = | :'<,'>EasyAlign**= + = | Right alignment around 1st = | :'<,'>EasyAlign!= + **= | Right-left alternating around = | :'<,'>EasyAlign!**= + ------------------+------------------------------------+-------------------- + + +Using regular expressions~ + *easy-align-using-regular-expressions* + *easy-align-5-3-3* + +Instead of finishing the command with a predefined delimiter key, you can type +in a regular expression after CTRL-/ or CTRL-X key. For example, if you want +to align text around all occurrences of numbers: + + - + - `*` + - CTRL-X + - `[0-9]\+` + + +Alignment options in interactive mode~ + *easy-align-alignment-options-in-interactive-mode* + *easy-align-5-3-4* + +While in interactive mode, you can set alignment options using special +shortcut keys listed below. The meaning of each option will be described in +{the following sections}{6}. + + --------+--------------------+--------------------------------------------------- + Key | Option | Values ~ + --------+--------------------+--------------------------------------------------- + CTRL-F | `filter` | Input string ( `[gv]/.*/?` ) + CTRL-I | `indentation` | shallow, deep, none, keep + CTRL-L | `left_margin` | Input number or string + CTRL-R | `right_margin` | Input number or string + CTRL-D | `delimiter_align` | left, center, right + CTRL-U | `ignore_unmatched` | 0, 1 + CTRL-G | `ignore_groups` | [], ["String'], ["Comment'], ["String', "Comment'] + CTRL-A | `align` | Input string ( `/[lrc]+\*{0,2}/` ) + | `stick_to_left` | `{ 'stick_to_left': 1, 'left_margin': 0 }` + | `stick_to_left` | `{ 'stick_to_left': 0, 'left_margin': 1 }` + | `*_margin` | `{ 'left_margin': 0, 'right_margin': 0 }` + --------+--------------------+--------------------------------------------------- + + {6} https://github.com/junegunn/vim-easy-align#alignment-options + + +< Live interactive mode >_____________________________________________________~ + *easy-align-live-interactive-mode* + *easy-align-5-4* + +If you're performing a complex alignment where multiple options should be +carefully adjusted, try "live interactive mode" where you can preview the +result of the alignment on-the-fly as you type in. + +Live interactive mode can be started with either (LiveEasyAlign) map or +`:LiveEasyAlign` command. Or you can switch to live interactive mode while in +ordinary interactive mode by pressing CTRL-P. (P for Preview) + +In live interactive mode, you have to type in the same delimiter (or CTRL-X on +regular expression) again to finalize the alignment. This allows you to +preview the result of the alignment and freely change the delimiter using +backspace key without leaving the interactive mode. + + +< Non-interactive mode >______________________________________________________~ + *easy-align-non-interactive-mode* + *easy-align-5-5* + +Instead of starting interactive mode, you can use declarative, non-interactive +`:EasyAlign` command. +> + " Using predefined alignment rules + " :EasyAlign[!] [N-th] DELIMITER_KEY [OPTIONS] + :EasyAlign : + :EasyAlign = + :EasyAlign *= + :EasyAlign 3\ + + " Using arbitrary regular expressions + " :EasyAlign[!] [N-th] /REGEXP/ [OPTIONS] + :EasyAlign /[:;]\+/ + :EasyAlign 2/[:;]\+/ + :EasyAlign */[:;]\+/ + :EasyAlign **/[:;]\+/ +< +A command can end with alignment options, {each of which will be discussed in +detail later}{6}, in Vim dictionary format. + + - `:EasyAlign * /[:;]\+/ { 'stick_to_left': 1, 'left_margin': 0 }` + +`stick_to_left` of 1 means that the matched delimiter should be positioned +right next to the preceding token, and `left_margin` of 0 removes the margin +on the left. So we get: +> + apple;: banana:: cake + data;; exchange:; format +< +Option names are fuzzy-matched, so you can write as follows: + + - `:EasyAlign * /[:;]\+/ { 'stl': 1, 'l': 0 }` + +You can even omit spaces between the arguments, so concisely (or cryptically): + + - `:EasyAlign*/[:;]\+/{'s':1,'l':0}` + +Nice. But let's make it even shorter. Option values can be written in +shorthand notation. + + - `:EasyAlign*/[:;]\+/` + `ignore_unmatched` | `iu[01]` + `ignore_groups` | `ig\[.*\]` + `align` | `a[lrc*]*` + `delimiter_align` | `d[lrc]` + `indentation` | `i[ksdn]` + -------------------+----------- + +For your information, the same operation can be done in interactive mode as +follows: + + - + - `*` + - + - CTRL-X + - `[:;]\+` + + {6} https://github.com/junegunn/vim-easy-align#alignment-options + + +< Partial alignment in blockwise-visual mode >________________________________~ + *easy-align-partial-alignment-in-blockwise-visual-mode* + *easy-align-5-6* + +In blockwise-visual mode (CTRL-V), EasyAlign command aligns only the selected +text in the block, instead of the whole lines in the range. + +Consider the following case where you want to align text around `=>` +operators. +> + my_hash = { :a => 1, + :aa => 2, + :aaa => 3 } +< +In non-blockwise visual mode (`v` / `V`), = won't work since the +assignment operator in the first line gets in the way. So we instead enter +blockwise-visual mode (CTRL-V), and select the text around`=>` operators, then +press =. +> + my_hash = { :a => 1, + :aa => 2, + :aaa => 3 } +< +However, in this case, we don't really need blockwise visual mode since the +same can be easily done using the negative N-th parameter: -= + + + *easy-align-6* +ALIGNMENT OPTIONS *easy-align-alignment-options* +============================================================================== + + +< List of options >___________________________________________________________~ + *easy-align-list-of-options* + *easy-align-6-1* + + -------------------+---------+-----------------------+-------------------------------------------------------- + Option | Type | Default | Description ~ + -------------------+---------+-----------------------+-------------------------------------------------------- + `filter` | string | | Line filtering expression: `g/../` or `v/../` + `left_margin` | number | 1 | Number of spaces to attach before delimiter + `left_margin` | string | `' '` | String to attach before delimiter + `right_margin` | number | 1 | Number of spaces to attach after delimiter + `right_margin` | string | `' '` | String to attach after delimiter + `stick_to_left` | boolean | 0 | Whether to position delimiter on the left-side + `ignore_groups` | list | ["String', "Comment'] | Delimiters in these syntax highlight groups are ignored + `ignore_unmatched` | boolean | 1 | Whether to ignore lines without matching delimiter + `indentation` | string | `k` | Indentation method (keep, deep, shallow, none) + `delimiter_align` | string | `r` | Determines how to align delimiters of different lengths + `align` | string | `l` | Alignment modes for multiple occurrences of delimiters + -------------------+---------+-----------------------+-------------------------------------------------------- + +There are 4 ways to set alignment options (from lowest precedence to highest): + + 1. Some option values can be set with corresponding global variables + 2. Option values can be specified in the definition of each alignment rule + 3. Option values can be given as arguments to `:EasyAlign` command + 4. Option values can be set in interactive mode using special shortcut keys + + *g:easy_align_ignore_groups* *g:easy_align_ignore_unmatched* + *g:easy_align_indentation* *g:easy_align_delimiter_align* + + -------------------+-----------------+-------------+-------------------------------- + Option name | Shortcut key | Abbreviated | Global variable ~ + -------------------+-----------------+-------------+-------------------------------- + `filter` | CTRL-F | `[gv]/.*/` | + `left_margin` | CTRL-L | `l[0-9]+` | + `right_margin` | CTRL-R | `r[0-9]+` | + `stick_to_left` | , | `<` or `>` | + `ignore_groups` | CTRL-G | `ig\[.*\]` | `g:easy_align_ignore_groups` + `ignore_unmatched` | CTRL-U | `iu[01]` | `g:easy_align_ignore_unmatched` + `indentation` | CTRL-I | `i[ksdn]` | `g:easy_align_indentation` + `delimiter_align` | CTRL-D | `d[lrc]` | `g:easy_align_delimiter_align` + `align` | CTRL-A | `a[lrc*]*` | + -------------------+-----------------+-------------+-------------------------------- + + +< Filtering lines >___________________________________________________________~ + *easy-align-filtering-lines* + *easy-align-6-2* + +With `filter` option, you can align lines that only match or do not match a +given pattern. There are several ways to set the pattern. + + 1. Press CTRL-F in interactive mode and type in `g/pat/` or `v/pat/` + 2. In command-line, it can be written in dictionary format: `{'filter': + 'g/pat/'}` + 3. Or in shorthand notation: `g/pat/` or `v/pat/` + +(You don't need to escape "/'s in the regular expression) + + +Examples~ + + *easy-align-6-2-1* +> + " Start interactive mode with filter option set to g/hello/ + EasyAlign g/hello/ + + " Start live interactive mode with filter option set to v/goodbye/ + LiveEasyAlign v/goodbye/ + + " Align the lines with 'hi' around the first colons + EasyAlign:g/hi/ +< + +< Ignoring delimiters in comments or strings >________________________________~ + *easy-align-ignoring-delimiters-in-comments-or-strings* + *easy-align-6-3* + +EasyAlign can be configured to ignore delimiters in certain syntax highlight +groups, such as code comments or strings. By default, delimiters that are +highlighted as code comments or strings are ignored. +> + " Default: + " If a delimiter is in a highlight group whose name matches + " any of the followings, it will be ignored. + let g:easy_align_ignore_groups = ['Comment', 'String'] +< +For example, the following paragraph +> + { + # Quantity of apples: 1 + apple: 1, + # Quantity of bananas: 2 + bananas: 2, + # Quantity of grape:fruits: 3 + 'grape:fruits': 3 + } +< +becomes as follows on : (or `:EasyAlign:`) +> + { + # Quantity of apples: 1 + apple: 1, + # Quantity of bananas: 2 + bananas: 2, + # Quantity of grape:fruits: 3 + 'grape:fruits': 3 + } +< +Naturally, this feature only works when syntax highlighting is enabled. + +You can change the default rule by using one of these 4 methods. + + 1. Press CTRL-G in interactive mode to switch groups + 2. Define global `g:easy_align_ignore_groups` list + 3. Define a custom rule in `g:easy_align_delimiters` with `ignore_groups` option + 4. Provide `ignore_groups` option to `:EasyAlign` command. e.g. `:EasyAlign:ig[]` + +For example if you set `ignore_groups` option to be an empty list, you get +> + { + # Quantity of apples: 1 + apple: 1, + # Quantity of bananas: 2 + bananas: 2, + # Quantity of grape: fruits: 3 + 'grape: fruits': 3 + } +< +If a pattern in `ignore_groups` is prepended by a `!`, it will have the +opposite meaning. For instance, if `ignore_groups` is given as `['!Comment']`, +delimiters that are not highlighted as Comment will be ignored during the +alignment. + + +< Ignoring unmatched lines >__________________________________________________~ + *easy-align-ignoring-unmatched-lines* + *easy-align-6-4* + +`ignore_unmatched` option determines how EasyAlign command processes lines +that do not have N-th delimiter. + + 1. In left-alignment mode, they are ignored + 2. In right or center-alignment mode, they are not ignored, and the last tokens + from those lines are aligned as well as if there is an invisible trailing + delimiter at the end of each line + 3. If `ignore_unmatched` is 1, they are ignored regardless of the alignment mode + 4. If `ignore_unmatched` is 0, they are not ignored regardless of the mode + +Let's take an example. When we align the following code block around the (1st) +colons, +> + { + apple: proc { + this_line_does_not_have_a_colon + }, + bananas: 2, + grapefruits: 3 + } +< +this is usually what we want. +> + { + apple: proc { + this_line_does_not_have_a_colon + }, + bananas: 2, + grapefruits: 3 + } +< +However, we can override this default behavior by setting `ignore_unmatched` +option to zero using one of the following methods. + + 1. Press CTRL-U in interactive mode to toggle `ignore_unmatched` option + 2. Set the global `g:easy_align_ignore_unmatched` variable to 0 + 3. Define a custom alignment rule with `ignore_unmatched` option set to 0 + 4. Provide `ignore_unmatched` option to `:EasyAlign` command. e.g. + `:EasyAlign:iu0` + +Then we get, +> + { + apple: proc { + this_line_does_not_have_a_colon + }, + bananas: 2, + grapefruits: 3 + } +< + +< Aligning delimiters of different lengths >__________________________________~ + *easy-align-aligning-delimiters-of-different-lengths* + *easy-align-6-5* + +Global `g:easy_align_delimiter_align` option and rule-wise/command-wise +`delimiter_align` option determines how matched delimiters of different +lengths are aligned. +> + apple = 1 + banana += apple + cake ||= banana +< +By default, delimiters are right-aligned as follows. +> + apple = 1 + banana += apple + cake ||= banana +< +However, with `:EasyAlign=dl`, delimiters are left-aligned. +> + apple = 1 + banana += apple + cake ||= banana +< +And on `:EasyAlign=dc`, center-aligned. +> + apple = 1 + banana += apple + cake ||= banana +< +In interactive mode, you can change the option value with CTRL-D key. + + +< Adjusting indentation >_____________________________________________________~ + *easy-align-adjusting-indentation* + *easy-align-6-6* + +By default :EasyAlign command keeps the original indentation of the lines. But +then again we have `indentation` option. See the following example. +> + # Lines with different indentation + apple = 1 + banana = 2 + cake = 3 + daisy = 4 + eggplant = 5 + + # Default: _k_eep the original indentation + # :EasyAlign= + apple = 1 + banana = 2 + cake = 3 + daisy = 4 + eggplant = 5 + + # Use the _s_hallowest indentation among the lines + # :EasyAlign=is + apple = 1 + banana = 2 + cake = 3 + daisy = 4 + eggplant = 5 + + # Use the _d_eepest indentation among the lines + # :EasyAlign=id + apple = 1 + banana = 2 + cake = 3 + daisy = 4 + eggplant = 5 + + # Indentation: _n_one + # :EasyAlign=in + apple = 1 + banana = 2 + cake = 3 + daisy = 4 + eggplant = 5 +< +In interactive mode, you can change the option value with CTRL-I key. + + +< Alignments over multiple occurrences of delimiters >________________________~ + *easy-align-alignments-over-multiple-occurrences-of-delimiters* + *easy-align-6-7* + +As stated above, "N-th" parameter is used to target specific occurrences of +the delimiter when it appears multiple times in each line. + +To recap: +> + " Left-alignment around the FIRST occurrences of delimiters + :EasyAlign = + + " Left-alignment around the SECOND occurrences of delimiters + :EasyAlign 2= + + " Left-alignment around the LAST occurrences of delimiters + :EasyAlign -= + + " Left-alignment around ALL occurrences of delimiters + :EasyAlign *= + + " Left-right ALTERNATING alignment around all occurrences of delimiters + :EasyAlign **= + + " Right-left ALTERNATING alignment around all occurrences of delimiters + :EasyAlign! **= +< +In addition to these, you can fine-tune alignments over multiple occurrences +of the delimiters with "align' option. (The option can also be set in +interactive mode with the special key CTRL-A) +> + " Left alignment over the first two occurrences of delimiters + :EasyAlign = { 'align': 'll' } + + " Right, left, center alignment over the 1st to 3rd occurrences of delimiters + :EasyAlign = { 'a': 'rlc' } + + " Using shorthand notation + :EasyAlign = arlc + + " Right, left, center alignment over the 2nd to 4th occurrences of delimiters + :EasyAlign 2=arlc + + " (*) Repeating alignments (default: l, r, or c) + " Right, left, center, center, center, center, ... + :EasyAlign *=arlc + + " (**) Alternating alignments (default: lr or rl) + " Right, left, center, right, left, center, ... + :EasyAlign **=arlc + + " Right, left, center, center, center, ... repeating alignment + " over the 3rd to the last occurrences of delimiters + :EasyAlign 3=arlc* + + " Right, left, center, right, left, center, ... alternating alignment + " over the 3rd to the last occurrences of delimiters + :EasyAlign 3=arlc** +< + +< Extending alignment rules >_________________________________________________~ + *easy-align-extending-alignment-rules* + *easy-align-6-8* + +Although the default rules should cover the most of the use cases, you can +extend the rules by setting a dictionary named `g:easy_align_delimiters`. + +You may refer to the definitions of the default alignment rules {here}{7}. + +{7} https://github.com/junegunn/vim-easy-align/blob/2.9.6/autoload/easy_align.vim#L32-L46 + + +Examples~ + + *easy-align-6-8-1* +> + let g:easy_align_delimiters = { + \ '>': { 'pattern': '>>\|=>\|>' }, + \ '/': { + \ 'pattern': '//\+\|/\*\|\*/', + \ 'delimiter_align': 'l', + \ 'ignore_groups': ['!Comment'] }, + \ ']': { + \ 'pattern': '[[\]]', + \ 'left_margin': 0, + \ 'right_margin': 0, + \ 'stick_to_left': 0 + \ }, + \ ')': { + \ 'pattern': '[()]', + \ 'left_margin': 0, + \ 'right_margin': 0, + \ 'stick_to_left': 0 + \ }, + \ 'd': { + \ 'pattern': ' \(\S\+\s*[;=]\)\@=', + \ 'left_margin': 0, + \ 'right_margin': 0 + \ } + \ } +< + + *easy-align-7* +OTHER OPTIONS *easy-align-other-options* +============================================================================== + + +< Disabling &foldmethod during alignment >____________________________________~ + *easy-align-disabling-foldmethod-during-alignment* + *easy-align-7-1* + + *g:easy_align_bypass_fold* + +{It is reported}{8} that 'foldmethod' value of `expr` or `syntax` can +significantly slow down the alignment when editing a large, complex file with +many folds. To alleviate this issue, EasyAlign provides an option to +temporarily set 'foldmethod' to `manual` during the alignment task. In order +to enable this feature, set `g:easy_align_bypass_fold` switch to 1. +> + let g:easy_align_bypass_fold = 1 +< + {8} https://github.com/junegunn/vim-easy-align/issues/14 + + +< Left/right/center mode switch in interactive mode >_________________________~ + *easy-align-left-right-center-mode-switch-in-interactive-mode* + *easy-align-7-2* + +In interactive mode, you can choose the alignment mode you want by pressing +enter keys. The non-bang command, `:EasyAlign` starts in left-alignment mode +and changes to right and center mode as you press enter keys, while the bang +version first starts in right-alignment mode. + + - `:EasyAlign` + - Left, Right, Center + - `:EasyAlign!` + - Right, Left, Center + +If you do not prefer this default mode transition, you can define your own +settings as follows. + + *g:easy_align_interactive_modes* *g:easy_align_bang_interactive_modes* +> + let g:easy_align_interactive_modes = ['l', 'r'] + let g:easy_align_bang_interactive_modes = ['c', 'r'] +< + + *easy-align-8* +ADVANCED EXAMPLES AND USE CASES *easy-align-advanced-examples-and-use-cases* +============================================================================== + +See {EXAMPLES.md}{9} for more examples. + + {9} https://github.com/junegunn/vim-easy-align/blob/master/EXAMPLES.md + + + *easy-align-9* +RELATED WORK *easy-align-related-work* +============================================================================== + + - {DrChip's Alignment Tool for Vim}{10} + - {Tabular}{11} + + {10} http://www.drchip.org/astronaut/vim/align.html + {11} https://github.com/godlygeek/tabular + + + *easy-align-10* +AUTHOR *easy-align-author* +============================================================================== + +{Junegunn Choi}{12} + + {12} https://github.com/junegunn + + + *easy-align-11* +LICENSE *easy-align-license* +============================================================================== + +MIT + +============================================================================== +vim:tw=78:sw=2:ts=2:ft=help:norl:nowrap: diff --git a/.vim/bundle/vim-easy-align/doc/tags b/.vim/bundle/vim-easy-align/doc/tags new file mode 100644 index 0000000..0411c5f --- /dev/null +++ b/.vim/bundle/vim-easy-align/doc/tags @@ -0,0 +1,84 @@ +:EasyAlign easy_align.txt /*:EasyAlign* +:LiveEasyAlign easy_align.txt /*:LiveEasyAlign* +(EasyAlign) easy_align.txt /*(EasyAlign)* +(LiveEasyAlign) easy_align.txt /*(LiveEasyAlign)* +easy-align easy_align.txt /*easy-align* +easy-align-1 easy_align.txt /*easy-align-1* +easy-align-1-using-plug-mappings easy_align.txt /*easy-align-1-using-plug-mappings* +easy-align-10 easy_align.txt /*easy-align-10* +easy-align-11 easy_align.txt /*easy-align-11* +easy-align-2 easy_align.txt /*easy-align-2* +easy-align-2-using-easyalign-command easy_align.txt /*easy-align-2-using-easyalign-command* +easy-align-3 easy_align.txt /*easy-align-3* +easy-align-4 easy_align.txt /*easy-align-4* +easy-align-5 easy_align.txt /*easy-align-5* +easy-align-5-1 easy_align.txt /*easy-align-5-1* +easy-align-5-2 easy_align.txt /*easy-align-5-2* +easy-align-5-2-1 easy_align.txt /*easy-align-5-2-1* +easy-align-5-2-2 easy_align.txt /*easy-align-5-2-2* +easy-align-5-3 easy_align.txt /*easy-align-5-3* +easy-align-5-3-1 easy_align.txt /*easy-align-5-3-1* +easy-align-5-3-2 easy_align.txt /*easy-align-5-3-2* +easy-align-5-3-3 easy_align.txt /*easy-align-5-3-3* +easy-align-5-3-4 easy_align.txt /*easy-align-5-3-4* +easy-align-5-4 easy_align.txt /*easy-align-5-4* +easy-align-5-5 easy_align.txt /*easy-align-5-5* +easy-align-5-6 easy_align.txt /*easy-align-5-6* +easy-align-6 easy_align.txt /*easy-align-6* +easy-align-6-1 easy_align.txt /*easy-align-6-1* +easy-align-6-2 easy_align.txt /*easy-align-6-2* +easy-align-6-2-1 easy_align.txt /*easy-align-6-2-1* +easy-align-6-3 easy_align.txt /*easy-align-6-3* +easy-align-6-4 easy_align.txt /*easy-align-6-4* +easy-align-6-5 easy_align.txt /*easy-align-6-5* +easy-align-6-6 easy_align.txt /*easy-align-6-6* +easy-align-6-7 easy_align.txt /*easy-align-6-7* +easy-align-6-8 easy_align.txt /*easy-align-6-8* +easy-align-6-8-1 easy_align.txt /*easy-align-6-8-1* +easy-align-7 easy_align.txt /*easy-align-7* +easy-align-7-1 easy_align.txt /*easy-align-7-1* +easy-align-7-2 easy_align.txt /*easy-align-7-2* +easy-align-8 easy_align.txt /*easy-align-8* +easy-align-9 easy_align.txt /*easy-align-9* +easy-align-adjusting-indentation easy_align.txt /*easy-align-adjusting-indentation* +easy-align-advanced-examples-and-use-cases easy_align.txt /*easy-align-advanced-examples-and-use-cases* +easy-align-aligning-delimiters-of-different-lengths easy_align.txt /*easy-align-aligning-delimiters-of-different-lengths* +easy-align-alignment-options easy_align.txt /*easy-align-alignment-options* +easy-align-alignment-options-in-interactive-mode easy_align.txt /*easy-align-alignment-options-in-interactive-mode* +easy-align-alignments-over-multiple-occurrences-of-delimiters easy_align.txt /*easy-align-alignments-over-multiple-occurrences-of-delimiters* +easy-align-author easy_align.txt /*easy-align-author* +easy-align-concept-of-alignment-rule easy_align.txt /*easy-align-concept-of-alignment-rule* +easy-align-demo easy_align.txt /*easy-align-demo* +easy-align-disabling-foldmethod-during-alignment easy_align.txt /*easy-align-disabling-foldmethod-during-alignment* +easy-align-examples easy_align.txt /*easy-align-examples* +easy-align-execution-models easy_align.txt /*easy-align-execution-models* +easy-align-extending-alignment-rules easy_align.txt /*easy-align-extending-alignment-rules* +easy-align-features easy_align.txt /*easy-align-features* +easy-align-filtering-lines easy_align.txt /*easy-align-filtering-lines* +easy-align-ignoring-delimiters-in-comments-or-strings easy_align.txt /*easy-align-ignoring-delimiters-in-comments-or-strings* +easy-align-ignoring-unmatched-lines easy_align.txt /*easy-align-ignoring-unmatched-lines* +easy-align-installation easy_align.txt /*easy-align-installation* +easy-align-interactive-mode easy_align.txt /*easy-align-interactive-mode* +easy-align-left-right-center-mode-switch-in-interactive-mode easy_align.txt /*easy-align-left-right-center-mode-switch-in-interactive-mode* +easy-align-license easy_align.txt /*easy-align-license* +easy-align-list-of-options easy_align.txt /*easy-align-list-of-options* +easy-align-live-interactive-mode easy_align.txt /*easy-align-live-interactive-mode* +easy-align-non-interactive-mode easy_align.txt /*easy-align-non-interactive-mode* +easy-align-other-options easy_align.txt /*easy-align-other-options* +easy-align-partial-alignment-in-blockwise-visual-mode easy_align.txt /*easy-align-partial-alignment-in-blockwise-visual-mode* +easy-align-predefined-alignment-rules easy_align.txt /*easy-align-predefined-alignment-rules* +easy-align-related-work easy_align.txt /*easy-align-related-work* +easy-align-tldr-one-minute-guide easy_align.txt /*easy-align-tldr-one-minute-guide* +easy-align-toc easy_align.txt /*easy-align-toc* +easy-align-usage easy_align.txt /*easy-align-usage* +easy-align-using-regular-expressions easy_align.txt /*easy-align-using-regular-expressions* +easyalign easy_align.txt /*easyalign* +g:easy_align_bang_interactive_modes easy_align.txt /*g:easy_align_bang_interactive_modes* +g:easy_align_bypass_fold easy_align.txt /*g:easy_align_bypass_fold* +g:easy_align_delimiter_align easy_align.txt /*g:easy_align_delimiter_align* +g:easy_align_delimiters easy_align.txt /*g:easy_align_delimiters* +g:easy_align_ignore_groups easy_align.txt /*g:easy_align_ignore_groups* +g:easy_align_ignore_unmatched easy_align.txt /*g:easy_align_ignore_unmatched* +g:easy_align_indentation easy_align.txt /*g:easy_align_indentation* +g:easy_align_interactive_modes easy_align.txt /*g:easy_align_interactive_modes* +vim-easy-align easy_align.txt /*vim-easy-align* diff --git a/.vim/bundle/vim-easy-align/plugin/easy_align.vim b/.vim/bundle/vim-easy-align/plugin/easy_align.vim new file mode 100644 index 0000000..c71af4e --- /dev/null +++ b/.vim/bundle/vim-easy-align/plugin/easy_align.vim @@ -0,0 +1,142 @@ +" Copyright (c) 2014 Junegunn Choi +" +" MIT License +" +" Permission is hereby granted, free of charge, to any person obtaining +" a copy of this software and associated documentation files (the +" "Software"), to deal in the Software without restriction, including +" without limitation the rights to use, copy, modify, merge, publish, +" distribute, sublicense, and/or sell copies of the Software, and to +" permit persons to whom the Software is furnished to do so, subject to +" the following conditions: +" +" The above copyright notice and this permission notice shall be +" included in all copies or substantial portions of the Software. +" +" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +" EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +" NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +" LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +" OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +" WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +if exists("g:loaded_easy_align_plugin") + finish +endif +let g:loaded_easy_align_plugin = 1 + +command! -nargs=* -range -bang EasyAlign ,call easy_align#align(0, 0, 'command', ) +command! -nargs=* -range -bang LiveEasyAlign ,call easy_align#align(0, 1, 'command', ) + +let s:last_command = 'EasyAlign' + +function! s:abs(v) + return a:v >= 0 ? a:v : - a:v +endfunction + +function! s:remember_visual(mode) + let s:last_visual = [a:mode, s:abs(line("'>") - line("'<")), s:abs(col("'>") - col("'<"))] +endfunction + +function! s:repeat_visual() + let [mode, ldiff, cdiff] = s:last_visual + let cmd = 'normal! '.mode + if ldiff > 0 + let cmd .= ldiff . 'j' + endif + + let ve_save = &virtualedit + try + if mode == "\" + if cdiff > 0 + let cmd .= cdiff . 'l' + endif + set virtualedit+=block + endif + execute cmd.":\=g:easy_align_last_command\\" + call s:set_repeat() + finally + if ve_save != &virtualedit + let &virtualedit = ve_save + endif + endtry +endfunction + +function! s:repeat_in_visual() + if exists('g:easy_align_last_command') + call s:remember_visual(visualmode()) + call s:repeat_visual() + endif +endfunction + +function! s:set_repeat() + silent! call repeat#set("\(EasyAlignRepeat)") +endfunction + +function! s:generic_easy_align_op(type, vmode, live) + if !&modifiable + if a:vmode + normal! gv + endif + return + endif + let sel_save = &selection + let &selection = "inclusive" + + if a:vmode + let vmode = a:type + let [l1, l2] = ["'<", "'>"] + call s:remember_visual(vmode) + else + let vmode = '' + let [l1, l2] = [line("'["), line("']")] + unlet! s:last_visual + endif + + try + let range = l1.','.l2 + if get(g:, 'easy_align_need_repeat', 0) + execute range . g:easy_align_last_command + else + execute range . "call easy_align#align(0, a:live, vmode, '')" + end + call s:set_repeat() + finally + let &selection = sel_save + endtry +endfunction + +function! s:easy_align_op(type, ...) + call s:generic_easy_align_op(a:type, a:0, 0) +endfunction + +function! s:live_easy_align_op(type, ...) + call s:generic_easy_align_op(a:type, a:0, 1) +endfunction + +function! s:easy_align_repeat() + if exists('s:last_visual') + call s:repeat_visual() + else + try + let g:easy_align_need_repeat = 1 + normal! . + finally + unlet! g:easy_align_need_repeat + endtry + endif +endfunction + +nnoremap (EasyAlign) :set opfunc=easy_align_opg@ +vnoremap (EasyAlign) :call easy_align_op(visualmode(), 1) +nnoremap (LiveEasyAlign) :set opfunc=live_easy_align_opg@ +vnoremap (LiveEasyAlign) :call live_easy_align_op(visualmode(), 1) + +" vim-repeat support +nnoremap (EasyAlignRepeat) :call easy_align_repeat() +vnoremap (EasyAlignRepeat) :call repeat_in_visual() + +" Backward-compatibility (deprecated) +nnoremap (EasyAlignOperator) :set opfunc=easy_align_opg@ + diff --git a/.vim/bundle/vim-fswitch/doc/fswitch.txt b/.vim/bundle/vim-fswitch/doc/fswitch.txt new file mode 100644 index 0000000..688f7ef --- /dev/null +++ b/.vim/bundle/vim-fswitch/doc/fswitch.txt @@ -0,0 +1,605 @@ +*fswitch.txt* For Vim version 7.2 and above Last change: 2009 Mar 23 + + --------------- + File Switcher + --------------- + +Author: Derek Wyatt (derek at myfirstnamemylastname dot org) + + *fswitch-copyright* +Copyright: The VIM LICENSE applies to fswitch.vim, and fswitch.txt + (see |copyright|) except use "fswitch" instead of "Vim". + No warranty, express or implied. + Use At-Your-Own-Risk! + +============================================================================== + *fswitch* *fswitch-contents* +1. Contents~ + + 1. Contents .............................: |fswitch-contents| + 2. About ................................: |fswitch-about| + 3. Features .............................: |fswitch-features| + 4. Setup ................................: |fswitch-setup| + 5. Configuration ........................: |fswitch-configure| + 6. "Creating" the Alternate File ........: |fswitch-altcreate| + 7. Useful Mappings ......................: |fswitch-mappings| + 8. FSwitch() ............................: |fswitch-function| + 9. FSReturnCompanionFilenameString().....: |fswitch-getcompanion| + 10. FSReturnReadableCompanionFilename()...: |fswitch-getreadablecomp| + 11. The Default Settings .................: |fswitch-defaults| + 12. Examples .............................: |fswitch-examples| + 13. Troubleshooting ......................: |fswitch-trouble| + A. Change History .......................: |fswitch-changes| + +============================================================================== + *fswitch-about* +2. About~ + +FSwitch is designed to allow you to switch between companion files of source +code (e.g. "cpp" files and their corresponding "h" files). The source for +this came from a home-grown script that was influenced later by the +"Alternate" (a.vim) script. + +The original intention was to modify the existing a.vim script to do what the +home-grown version could do (choose to open the other file in an existing +window) but it was a rather complex script and modification looked difficult +so the choice was made to simply move the home-grown script forward a couple +of notches and produce a new plugin. This doc file is twice the length of the +actual code at this point :) + +============================================================================== + *fswitch-features* +3. Features~ + +FSwitch has the following features: + + - Switches between a file and its companion file + - Ability to create a new file using a preferential location + - Simple configuration using buffer-local variables + - It's got a really long doc file (... seriously, why is this thing so + bloody long?) + - Umm... other stuff? + +============================================================================== + *fswitch-setup* +4. Setup~ + +Most of the behaviour of FSwitch is customized via buffer-local variables. +You set up the variables with auto commands: +> + au! BufEnter *.cpp let b:fswitchdst = 'hpp,h' | let b:fswitchlocs = '../inc' +< +That |:autocmd| will set the 'fswitchdst' and 'fswitchlocs' variables when the +|BufEnter| event takes place on a file whose name matches {*.cpp} (e.g. when +you enter the buffer containing the {MyFile.cpp} file). + +The variables above state that the alternate file to {MyFile.cpp} are +{MyFile.hpp} and {MyFile.h} preferred in that order, and located in the {inc} +directory at the same level as the current directory. + +That should get you there but there's more capability here if you want. To +get that move on to |fswitch-configure|. + +============================================================================== + *fswitch-configure* +5. Configuration~ + + + *'fswitchdst'* +'fswitchdst' string (default depends on file in current buffer) + local to buffer + + The 'fswitchdst' variable denotes the file extension that is the + target extension of the current file's companion file. For example: +> + :let b:fswitchdst = 'cpp,cxx,C' +< + The above specifies that the current buffer's file has a companion + filename which can be found by replacing the current extension with + {cpp}, {cxx} or {C}. The extensions will be tried in this order and + the first match wins. + + 'fswitchdst' is taken relative to directories that are found in the + 'fswitchlocs' variable. + + *'fswitchlocs'* +'fswitchlocs' string (default depends on file in current buffer) + local to buffer + + The 'fswitchlocs' variable contains a set of directives that indicate + directory names that should be formulated when trying to find the + alternate file. For example: +> + " Create the destination path by substituting any + " 'include' string from the pathname with 'src' + :let b:fswitchlocs = 'reg:/include/src/' + + " First try adding the relative path '../src' to the path + " in which the file in the buffer exists and if that fails + " then try using 'source' instead + :let b:fswitchlocs = 'rel:../src,source' + + " Same as above but leaving off the optional 'rel:' + :let b:fswitchlocs = '../src,../source' +< + The following types of directives are understood: + + *fswitch_reg* + reg:~ + A regular expression. The regular expression takes the form: +> + {delim}{pat}{delim}{globsub}{delim} +< + Where: + + {delim} is something that doesn't appear in {pat} or + {globsub} used to delimit the {pat} and {globsub} + + {pat} is a standard pattern to search on + + {globsub} is a substitution string that will be run through + the |glob()| function. + + *fswitch_rel* + rel:~ + A relative path. The {rel:} is actually optional. If you + leave this off, then FSwitch will assume that the string is + denoting a relative path. + + *fswitch_ifrel* + ifrel:~ + Takes the same form as {:reg} but the {globsub} part of the + directive is a relative path. The relative path is only used + if the {pat} matches the existing path of the buffer. + + *fswitch_abs* + abs:~ + An absolute path. I have no idea why you'd ever want to do + this, but it's there if you want it. + + *fswitch_ifabs* + ifabs:~ + Takes the same form as {:reg} but the {globsub} part of the + directive is an absolute path. The absolute path is only used + if the {pat} matches the existing path of the buffer. + + Why use the "if" variants? + + Here's the situation: You've got the following file: +> + ~/code/MyFile.h +< + And you've set the following locations: +> + For .h -> reg:/include/src/,../src,./ + For .cpp -> reg:/src/include/,../include,./ +< + Here's what happens when run the following commands: +> + FSwitch('%') + # Creates a new file ~/src/MyFile.cpp due to the first + # relative path in the list for .h + FSwitch('%') + # Creates a new file ~/include/MyFile.h due to the first + # regular expression in the list for .cpp +< + The problem is that you've unconditionally said you want to use + {../src} for the alternate file but in reality you probably wanted to + use {./}. If you use {:ifrel} instead then you can say that you only + want to use {../src} if the path to the current buffer contains + {/include/} or something like that. If you did this FSwitch would not + have taken {../src} for the new file but would have chosen {./} + + So the "right" setup is: +> + For .h -> reg:/include/src/,ifrel:|/include/|../src|,./ + For .cpp -> reg:/src/include/,ifrel:|/src/|../include|,./ +< + *'fswitchdisablegloc'* +'fsdisablegloc' + string (default off) + local to buffer + + Disables the appending of the default global locations to the local + buffer definition. Normally when processing alternate file locations + FSwitch will append some default values to the list of locations. If + you define this variable then this will not happen. + + The default locations are currently set to "./" or ".\" depending on + what slash your configuration evaluates to. + + *'fswitchfnames'* +'fswitchfnames' string (default depends on file in current buffer) + local to buffer + + The 'fswitchfnames' variable contains a comma-separated list + of possible substitution patterns over the base filename (without path + and extension) that should be formulated when trying to find the + alternate file. The format of the substitution pattern is the same + as |fswitch-reg|. + + This may be useful when the companion file may can be formulated by + adding a suffix to the base filename. For instance, when the companion + of MyClass.java is MyClassTest.java. + + For example: +> + " Create the destination filename by appending 'Test' + " to the filename + :let b:fswitchfnames = '/$/Test/' + + " Create the destination filename by removing 'Test' + " from the end of the filename. + :let b:fswitchfnames = '/Test$//' +< + *'fswitchnonewfiles'* +'fsnonewfiles' + string (default off) + local to buffer and global + + This variable is both global and local. If you want to disable the + creation of the alternate file when it doesn't already exist you can + choose to do this on a per-extension basis or globally. Set the + global one to shut it off all the time and use the buffer version to + shut it off locally. + + *'fsneednomatch'* +'fsneednomatch' + string (default off) + local to buffer and global + + Normally when doing a regular expression alteration of the path (see + {reg:} in 'fswitchdst' the pattern you're going to substitute the + value with must actually match in the string. When it doesn't matter + whether or not that the match actually takes place, you can set this + value. + + If you do set this then the failure to match actually results in + nothing happening at all. So if the right filename exists in the same + directory as the one you're switching from then that's the one that + will be switched to. + + Example: +> + + If the b:fswitchlocs is set to + + reg:/src/include/,include + + and + + # This is the file we're editing + ~/code/program/myfile.c + + # These choices exist for the header file + ~/code/program/myfile.h + ~/code/program/include/myfile.h +< + Then the first substitution will result in the first header file being + chosen, not the second. + +============================================================================== + *fswitch-altcreate* +6. "Creating" the Alternate File~ + +If the file being switched to does not exist, and 'fsnonewfiles' has not been +set, then it will be created as a new, unwritten buffer. If there are +multiple possibilities here, FSwitch prefers the first possible match. For +example if the current buffer has a filename called {/code/src/a/b/MyFile.cpp} +and has the following set: +> + let b:fswitchdst = 'h,hpp' + let b:fswitchlocs = 'reg:/src/include/,../include,../inc' +< +then the created filename will be {/code/include/a/b/MyFile.cpp}. + +As stated, this file hasn't actually been written to yet so you could easily +delete the buffer and there's no harm done but you also may not be able to +write the buffer very easily if the directory hierarchy doesn't yet exist. In +this case, it's quite helpful to define a mapping for easily creating the +directory for you: +> + nmap md :!mkdir -p %:p:h +< +Then it's pretty easy to create the directory before writing the file. + +============================================================================== + *fswitch-mappings* +7. Useful Mappings~ + +I didn't bother putting mappings into the script directly as this might have +caused conflicts and I don't know how to avoid those. I use the following +mappings myself: + + - Switch to the file and load it into the current window > + nmap of :FSHere +< + - Switch to the file and load it into the window on the right > + nmap ol :FSRight +< + - Switch to the file and load it into a new window split on the right > + nmap oL :FSSplitRight +< + - Switch to the file and load it into the window on the left > + nmap oh :FSLeft +< + - Switch to the file and load it into a new window split on the left > + nmap oH :FSSplitLeft +< + - Switch to the file and load it into the window above > + nmap ok :FSAbove +< + - Switch to the file and load it into a new window split above > + nmap oK :FSSplitAbove +< + - Switch to the file and load it into the window below > + nmap oj :FSBelow +< + - Switch to the file and load it into a new window split below > + nmap oJ :FSSplitBelow +< +============================================================================== + *FSwitch()* +8. FSwitch()~ + +The main work is done by the FSwitch() function. The reason it's documented +here is because you can use it to do something more interesting if you wish. +As it stands now, you get the "Split Above and Switch" functionality by +calling FSwitch() like this: +> + FSwitch('%', 'split \| wincmd k') +< +There's probably not much to stop anyone from doing something more interesting +in the second argument. If this string is non-empty then it will be run +through an |:execute| call. + +============================================================================== + *fswitch-getcompanion* *FSReturnCompanionFilenameString()* + +9. FSReturnCompanionFilenameString()~ + +This function is used by |FSwitch()| to return the pathname to the preferred +companion file. In this case, the file need not actually exist on the +filesystem but would be the one created if you chose to do so. As an +example: +> + let path = FSReturnCompanionFilenameString('%') +< +The resultant path string contains the preferred companion file or nothing if +no preferred file could be discovered. + +============================================================================== + *fswitch-getreadablecomp* *FSReturnReadableCompanionFilename()* + +10. FSReturnReadableCompanionFilename()~ + +This function returns the companion file, but the companion file must be +readable on the filesystem for it to be successfully returned. +> + let path = FSReturnReadableCompanionFilename('%') +< +The resultant path string contains the preferred companion file or nothing if +no preferred file could be found on the filesystem. + +In order to see what created the need for this function, see +|fswitch-example3|. + +============================================================================== + *fswitch-defaults* +11. The Default Settings~ + +By default FSwitch handles {c}, {cc}, {cpp}, {cxx} and {C}. Note that the +difference between {c} and {C} is only case sensitivity. This may mean that +weird stuff happens if your OS is case insensitive. + +Also NOTE that when you use {cxx/hxx} or {C/H}, god kills a puppy. +Consider that next time you want to do such a silly thing. + +For *.h files: +> + let b:fswitchdst = 'c,cpp' + let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/,../src' +< + +For *.hpp files: +> + let b:fswitchdst = 'cpp' + let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/,../src' +< + +For *.hxx files: +> + let b:fswitchdst = 'cxx' + let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/,../src' +< + +For *.H files: +> + let b:fswitchdst = 'C' + let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/,../src' +< + +For *.c +> + let b:fswitchdst = 'h' + let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include' +< + +For *.cpp +> + let b:fswitchdst = 'hpp,h' + let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include' +< + +For *.cxx +> + let b:fswitchdst = 'hxx' + let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include' +< + +For *.C +> + let b:fswitchdst = 'H' + let b:fswitchlocs = 'reg:/src/include/,reg:|src|include/**|,../include' +< + +============================================================================== + *fswitch-examples* +12. Examples~ + *fswitch-example1* +Let's say you have a C++ codebase and it has the following properties (this +level of insanity is a bit high but versions that are only slightly saner +exist in real life): + + - Source files with {.cpp}, {.cc} and {.C} extensions + - Header files with {.h} extensions + - Source files and header files in the same directory + - Source files in the {src} directory and include files in the + {include} directory + - Source files in the {src} directory and include files in the + {include/name/space} directory (i.e. subdirectories denoted by the + namespace). + - Source files in {src/name/space} and header files in + {include/name/space} (i.e. subdirectories denoted by the namespace). + +As a final part to this, the "new" way of doing things in this source tree is +to put header files in a directory noted by namespace and to do the same with +source files and to name source files with a {cpp} extension. + +In order to switch between files organized like this, you could specify the +following: +> + augroup mycppfiles + au! + au BufEnter *.h let b:fswitchdst = 'cpp,cc,C' + au BufEnter *.h let b:fswitchlocs = 'reg:/include/src/,reg:/include.*/src/' + augroup END +< +Here the setting of b:fswitchdst to {cpp,cc,C} handles the different C++ +extensions, and prefers to use {cpp} and will create new files with that +extension. + +The fswitchlocs setting allows for the following: + + reg:/include/src/~ + + Take the pathname to the file in the current buffer and + substitute "src" for "include". This handles the following + possibilities: + + - Files are in {include} and {src} respectively + - Files are in {include/name/space} and {src/name/space} + respectively + + reg:/include.*/src/~ + + Take the pathname to the file in the current buffer and + substitute "src" for "include.*". This handles the following + possibility: + + - Files are in {include/name/space} and {src} respectively + + ./~ + This one's a hiddden option. The default location is the + current directory already so we don't explicitly have to state + this, but it is the last possibility: + + - Files are in the same directory + + *fswitch-example2* +Here we'll just show a quick example of making use of the globbing aspect of +the system. Let's say you're working on a {cpp} file and you want to find the +matching header file, and you have your destination and locations set to the +following: +> + let b:fswitchdst = 'h' + let b:fswitchlocs = 'reg:|src|include/**|' +> +then if you have the a file {src/MyFile.cpp} then this will find the file +{include/name/space/MyFile.h}. + + *fswitch-example3* +At work I'm a Windows C++ programmer and at home I'm a OS X Objective-C +programmer. There's a problem with this... C++ and Objective-C both use the +same extension for header files ({.h}). + +At home I want to be able to use the XCode command line builder in the +'makeprg' setting when I'm working on the code. I would like this to be set +when I am on a {.m} file or its companion {.h} file. This is done with the +following function: +> + function! SetMakeForXCode(filename) + let isObjCFile = 0 + let ext = expand(a:filename . ":e") + if ext == 'm' || ext == 'mm' + let isObjCFile = 1 + elseif ext == 'h' + " Find the companion file + let companionfile = FSReturnReadableCompanionFilename('%') + " For some reason expand() doesn't work on the next line + let companionext = substitute(companionfile, '.*\.', '', '') + if companionext == 'm' || companionext == 'mm' + let isObjCFile = 1 + endif + endif + if isObjCFile == 1 + setl makeprg=xcodebuild\ -configuration\ Debug + endif + endfunction +< +Yup, this could have been easier by using the 'filetype' or using some sort of +|grep| call but I wanted to use this particular hammer. :) I'll probably end +up switching it to use the 'filetype' instead in the end... + +============================================================================== + *fswitch-trouble* +13. TroubleShooting~ + *fswitch-empty* +You may get the following error: +> + Alternate has evaluated to nothing. See :h fswitch-empty for more info. +< +It can happen... This is probably due to the fact that you've got a nicely +strict set of rules for your locations. With |fswitch-reg| and +|fswitch-ifrel| and |fswitch-ifabs| you can get rather specific about whether +or not anything actually happens. If you aren't letting anything really +happen, it's not going to happen and you're going to end up with an empty +path. + +============================================================================== + *fswitch-changes* +A. Change History~ + +0.9.5 + - Modified the autocommands to handle the myriad different + formulations of C++ files per Hong Xu's request. See: + https://github.com/derekwyatt/vim-fswitch/pull/3 + +0.9.4 + - Added fixes from Alexey Radkov to handle the 'sb' and 'spr' option + settings as well as the 'switchbuf' option. See: + https://github.com/derekwyatt/vim-fswitch/pull/2 + - Made small fix to Alexey's fix to keep the 'sb' and 'spr' shadow + variable local to the buffer and unlet them at the end. + - I made a quick regression check on this update but did not test + Alexey's features directly - I trust that he did that :) + +0.9.3 + - Made sure that there's a check for 7.0 (Thanks Timon Kelter) + +0.9.2 + - Fix for the splitting commands (Thanks Michael Henry) + +0.9.1 + - Added :ifrel (|fswitch_ifrel|) + - Added :ifabs (|fswitch_ifabs|) + - Added |FSReturnReadableCompanionFilename()| + - Added |FSReturnCompanionFilenameString()| + - Changed default settings for .h to use :ifrel instead of :rel + - Changed default settings for .c and .cpp to use :ifrel instead of + :rel + +0.9.0 + - Initial release + +vim:tw=78:sts=8:ts=8:sw=8:noet:ft=help: diff --git a/.vim/bundle/vim-fswitch/doc/tags b/.vim/bundle/vim-fswitch/doc/tags new file mode 100644 index 0000000..7b28907 --- /dev/null +++ b/.vim/bundle/vim-fswitch/doc/tags @@ -0,0 +1,34 @@ +'fsneednomatch' fswitch.txt /*'fsneednomatch'* +'fswitchdisablegloc' fswitch.txt /*'fswitchdisablegloc'* +'fswitchdst' fswitch.txt /*'fswitchdst'* +'fswitchfnames' fswitch.txt /*'fswitchfnames'* +'fswitchlocs' fswitch.txt /*'fswitchlocs'* +'fswitchnonewfiles' fswitch.txt /*'fswitchnonewfiles'* +FSReturnCompanionFilenameString() fswitch.txt /*FSReturnCompanionFilenameString()* +FSReturnReadableCompanionFilename() fswitch.txt /*FSReturnReadableCompanionFilename()* +FSwitch() fswitch.txt /*FSwitch()* +fswitch fswitch.txt /*fswitch* +fswitch-about fswitch.txt /*fswitch-about* +fswitch-altcreate fswitch.txt /*fswitch-altcreate* +fswitch-changes fswitch.txt /*fswitch-changes* +fswitch-configure fswitch.txt /*fswitch-configure* +fswitch-contents fswitch.txt /*fswitch-contents* +fswitch-copyright fswitch.txt /*fswitch-copyright* +fswitch-defaults fswitch.txt /*fswitch-defaults* +fswitch-empty fswitch.txt /*fswitch-empty* +fswitch-example1 fswitch.txt /*fswitch-example1* +fswitch-example2 fswitch.txt /*fswitch-example2* +fswitch-example3 fswitch.txt /*fswitch-example3* +fswitch-examples fswitch.txt /*fswitch-examples* +fswitch-features fswitch.txt /*fswitch-features* +fswitch-getcompanion fswitch.txt /*fswitch-getcompanion* +fswitch-getreadablecomp fswitch.txt /*fswitch-getreadablecomp* +fswitch-mappings fswitch.txt /*fswitch-mappings* +fswitch-setup fswitch.txt /*fswitch-setup* +fswitch-trouble fswitch.txt /*fswitch-trouble* +fswitch.txt fswitch.txt /*fswitch.txt* +fswitch_abs fswitch.txt /*fswitch_abs* +fswitch_ifabs fswitch.txt /*fswitch_ifabs* +fswitch_ifrel fswitch.txt /*fswitch_ifrel* +fswitch_reg fswitch.txt /*fswitch_reg* +fswitch_rel fswitch.txt /*fswitch_rel* diff --git a/.vim/bundle/vim-fswitch/plugin/fswitch.vim b/.vim/bundle/vim-fswitch/plugin/fswitch.vim new file mode 100644 index 0000000..a0396c8 --- /dev/null +++ b/.vim/bundle/vim-fswitch/plugin/fswitch.vim @@ -0,0 +1,364 @@ +" ============================================================================ +" File: fswitch.vim +" +" Description: Vim global plugin that provides decent companion source file +" switching +" +" Maintainer: Derek Wyatt +" +" Last Change: March 23rd 2009 +" +" License: This program is free software. It comes without any warranty, +" to the extent permitted by applicable law. You can redistribute +" it and/or modify it under the terms of the Do What The Fuck You +" Want To Public License, Version 2, as published by Sam Hocevar. +" See http://sam.zoy.org/wtfpl/COPYING for more details. +" ============================================================================ + +if exists("g:disable_fswitch") + finish +endif + +if v:version < 700 + echoerr "FSwitch requires Vim 7.0 or higher." + finish +endif + +" Version +let s:fswitch_version = '0.9.5' + +" Get the path separator right +let s:os_slash = &ssl == 0 && (has("win16") || has("win32") || has("win64")) ? '\' : '/' + +" Default locations - appended to buffer locations unless otherwise specified +let s:fswitch_global_locs = '.' . s:os_slash + +" +" s:SetVariables +" +" There are two variables that need to be set in the buffer in order for things +" to work correctly. Because we're using an autocmd to set things up we need to +" be sure that the user hasn't already set them for us explicitly so we have +" this function just to check and make sure. If the user's autocmd runs after +" ours then they will override the value anyway. +" +function! s:SetVariables(dst, locs) + if !exists("b:fswitchdst") + let b:fswitchdst = a:dst + endif + if !exists("b:fswitchlocs") + let b:fswitchlocs = a:locs + endif +endfunction + +" +" s:FSGetLocations +" +" Return the list of possible locations +" +function! s:FSGetLocations() + let locations = [] + if exists("b:fswitchlocs") + let locations = split(b:fswitchlocs, ',') + endif + if !exists("b:fsdisablegloc") || b:fsdisablegloc == 0 + let locations += split(s:fswitch_global_locs, ',') + endif + + return locations +endfunction + +" +" s:FSGetExtensions +" +" Return the list of destination extensions +" +function! s:FSGetExtensions() + return split(b:fswitchdst, ',') +endfunction + +" +" s:FSGetFilenameMutations +" +" Return the list of possible filename mutations +" +function! s:FSGetFilenameMutations() + if !exists("b:fswitchfnames") + " For backward-compatibility out default mutation is an identity. + return ['/^//'] + else + return split(b:fswitchfnames, ',') + endif +endfunction + +" +" s:FSGetMustMatch +" +" Return a boolean on whether or not the regex must match +" +function! s:FSGetMustMatch() + let mustmatch = 1 + if exists("b:fsneednomatch") && b:fsneednomatch != 0 + let mustmatch = 0 + endif + + return mustmatch +endfunction + +" +" s:FSGetFullPathToDirectory +" +" Given the filename, return the fully qualified directory portion +" +function! s:FSGetFullPathToDirectory(filename) + return expand(a:filename . ':p:h') +endfunction + +" +" s:FSGetFileExtension +" +" Given the filename, returns the extension +" +function! s:FSGetFileExtension(filename) + return expand(a:filename . ':e') +endfunction + +" +" s:FSGetFileNameWithoutExtension +" +" Given the filename, returns just the file name without the path or extension +" +function! s:FSGetFileNameWithoutExtension(filename) + return expand(a:filename . ':t:r') +endfunction + +" +" s:FSMutateFilename +" +" Takes a filename and a filename mutation directive and applies the mutation +" to it. +function! s:FSMutateFilename(filename, directive) + let separator = strpart(a:directive, 0, 1) + let dirparts = split(strpart(a:directive, 1), separator) + if len(dirparts) < 2 || len(dirparts) > 3 + throw 'Bad mutation directive "' . a:directive . '".' + else + let flags = '' + if len(dirparts) == 3 + let flags = dirparts[2] + endif + return substitute(a:filename, dirparts[0], dirparts[1], flags) + endif +endfunction + +" +" s:FSGetAlternateFilename +" +" Takes the path, name and extension of the file in the current buffer and +" applies the location to it. If the location is a regular expression pattern +" then it will split that up and apply it accordingly. If the location pattern +" is actually an explicit relative path or an implicit one (default) then it +" will simply apply that to the file directly. +" +function! s:FSGetAlternateFilename(filepath, filename, newextension, location, mustmatch) + let parts = split(a:location, ':') + let cmd = 'rel' + let directive = parts[0] + if len(parts) == 2 + let cmd = parts[0] + let directive = parts[1] + endif + if cmd == 'reg' || cmd == 'ifrel' || cmd == 'ifabs' + if strlen(directive) < 3 + throw 'Bad directive "' . a:location . '".' + else + let separator = strpart(directive, 0, 1) + let dirparts = split(strpart(directive, 1), separator) + if len(dirparts) < 2 || len(dirparts) > 3 + throw 'Bad directive "' . a:location . '".' + else + let part1 = dirparts[0] + let part2 = dirparts[1] + let flags = '' + if len(dirparts) == 3 + let flags = dirparts[2] + endif + if cmd == 'reg' + if a:mustmatch == 1 && match(a:filepath, part1) == -1 + let path = "" + else + let path = substitute(a:filepath, part1, part2, flags) . s:os_slash . + \ a:filename . '.' . a:newextension + endif + elseif cmd == 'ifrel' + if match(a:filepath, part1) == -1 + let path = "" + else + let path = a:filepath . s:os_slash . part2 . + \ s:os_slash . a:filename . '.' . a:newextension + endif + elseif cmd == 'ifabs' + if match(a:filepath, part1) == -1 + let path = "" + else + let path = part2 . s:os_slash . a:filename . '.' . a:newextension + endif + endif + endif + endif + elseif cmd == 'rel' + let path = a:filepath . s:os_slash . directive . s:os_slash . a:filename . '.' . a:newextension + elseif cmd == 'abs' + let path = directive . s:os_slash . a:filename . '.' . a:newextension + endif + + return simplify(path) +endfunction + +" +" s:FSReturnCompanionFilename +" +" This function will return a path that is the best candidate for the companion +" file to switch to. If mustBeReadable == 1 when then the companion file will +" only be returned if it is readable on the filesystem, otherwise it will be +" returned so long as it is non-empty. +" +function! s:FSReturnCompanionFilename(filename, mustBeReadable) + let fullpath = s:FSGetFullPathToDirectory(a:filename) + let ext = s:FSGetFileExtension(a:filename) + let justfile = s:FSGetFileNameWithoutExtension(a:filename) + let extensions = s:FSGetExtensions() + let filenameMutations = s:FSGetFilenameMutations() + let locations = s:FSGetLocations() + let mustmatch = s:FSGetMustMatch() + let newpath = '' + for currentExt in extensions + for loc in locations + for filenameMutation in filenameMutations + let mutatedFilename = s:FSMutateFilename(justfile, filenameMutation) + let newpath = s:FSGetAlternateFilename(fullpath, mutatedFilename, currentExt, loc, mustmatch) + if a:mustBeReadable == 0 && newpath != '' + return newpath + elseif a:mustBeReadable == 1 + let newpath = glob(newpath) + if filereadable(newpath) + return newpath + endif + endif + endfor + endfor + endfor + + return newpath +endfunction + +" +" FSReturnReadableCompanionFilename +" +" This function will return a path that is the best candidate for the companion +" file to switch to, so long as that file actually exists on the filesystem and +" is readable. +" +function! FSReturnReadableCompanionFilename(filename) + return s:FSReturnCompanionFilename(a:filename, 1) +endfunction + +" +" FSReturnCompanionFilenameString +" +" This function will return a path that is the best candidate for the companion +" file to switch to. The file does not need to actually exist on the +" filesystem in order to qualify as a proper companion. +" +function! FSReturnCompanionFilenameString(filename) + return s:FSReturnCompanionFilename(a:filename, 0) +endfunction + +" +" FSwitch +" +" This is the only externally accessible function and is what we use to switch +" to the alternate file. +" +function! FSwitch(filename, precmd) + if !exists("b:fswitchdst") || strlen(b:fswitchdst) == 0 + throw 'b:fswitchdst not set - read :help fswitch' + endif + if (!exists("b:fswitchlocs") || strlen(b:fswitchlocs) == 0) && + \ (!exists("b:fsdisablegloc") || b:fsdisablegloc == 0) + throw "There are no locations defined (see :h fswitchlocs and :h fsdisablegloc)" + endif + let newpath = FSReturnReadableCompanionFilename(a:filename) + let openfile = 1 + if !filereadable(newpath) + if exists("b:fsnonewfiles") || exists("g:fsnonewfiles") + let openfile = 0 + else + let newpath = FSReturnCompanionFilenameString(a:filename) + endif + endif + if &switchbuf =~ "^use" + let i = 1 + let bufnum = winbufnr(i) + while bufnum != -1 + let filename = fnamemodify(bufname(bufnum), ':p') + if filename == newpath + execute ":sbuffer " . filename + return + endif + let i += 1 + let bufnum = winbufnr(i) + endwhile + endif + if openfile == 1 + if newpath != '' + if strlen(a:precmd) != 0 + execute a:precmd + endif + let s:fname = fnameescape(newpath) + + if (strlen(bufname(s:fname))) > 0 + execute 'buffer ' . s:fname + else + execute 'edit ' . s:fname + endif + else + echoerr "Alternate has evaluated to nothing. See :h fswitch-empty for more info." + endif + else + echoerr "No alternate file found. 'fsnonewfiles' is set which denies creation." + endif +endfunction + +" +" The autocmds we set up to set up the buffer variables for us. +" +augroup fswitch_au_group + au! + au BufEnter *.c call s:SetVariables('h', 'reg:/src/include/,reg:|src|include/**|,ifrel:|/src/|../include|') + au BufEnter *.cc call s:SetVariables('hh', 'reg:/src/include/,reg:|src|include/**|,ifrel:|/src/|../include|') + au BufEnter *.cpp call s:SetVariables('hpp,h', 'reg:/src/include/,reg:|src|include/**|,ifrel:|/src/|../include|') + au BufEnter *.cxx call s:SetVariables('hxx', 'reg:/src/include/,reg:|src|include/**|,ifrel:|/src/|../include|') + au BufEnter *.C call s:SetVariables('H', 'reg:/src/include/,reg:|src|include/**|,ifrel:|/src/|../include|') + au BufEnter *.m call s:SetVariables('h', 'reg:/src/include/,reg:|src|include/**|,ifrel:|/src/|../include|') + au BufEnter *.h call s:SetVariables('c,cpp,m', 'reg:/include/src/,reg:/include.*/src/,ifrel:|/include/|../src|') + au BufEnter *.hh call s:SetVariables('cc', 'reg:/include/src/,reg:/include.*/src/,ifrel:|/include/|../src|') + au BufEnter *.hpp call s:SetVariables('cpp', 'reg:/include/src/,reg:/include.*/src/,ifrel:|/include/|../src|') + au BufEnter *.hxx call s:SetVariables('cxx', 'reg:/include/src/,reg:/include.*/src/,ifrel:|/include/|../src|') + au BufEnter *.H call s:SetVariables('C', 'reg:/include/src/,reg:/include.*/src/,ifrel:|/include/|../src|') +augroup END + +" +" The mappings used to do the good work +" +com! FSHere :call FSwitch('%', '') +com! FSRight :call FSwitch('%', 'wincmd l') +com! FSSplitRight :call FSwitch('%', 'let curspr=&spr | set nospr | vsplit | wincmd l | if curspr | set spr | endif') +com! FSLeft :call FSwitch('%', 'wincmd h') +com! FSSplitLeft :call FSwitch('%', 'let curspr=&spr | set nospr | vsplit | if curspr | set spr | endif') +com! FSAbove :call FSwitch('%', 'wincmd k') +com! FSSplitAbove :call FSwitch('%', 'let cursb=&sb | set nosb | split | if cursb | set sb | endif') +com! FSBelow :call FSwitch('%', 'wincmd j') +com! FSSplitBelow :call FSwitch('%', 'let cursb=&sb | set nosb | split | wincmd j | if cursb | set sb | endif') +com! FSTab :call FSwitch('%', 'tabedit') + diff --git a/.vim/bundle/vim-gnupg/plugin/gnupg.vim b/.vim/bundle/vim-gnupg/plugin/gnupg.vim new file mode 100644 index 0000000..da5768e --- /dev/null +++ b/.vim/bundle/vim-gnupg/plugin/gnupg.vim @@ -0,0 +1,1459 @@ +" Name: gnupg.vim +" Last Change: 2018 Jun 22 +" Maintainer: James McCoy +" Original Author: Markus Braun +" Summary: Vim plugin for transparent editing of gpg encrypted files. +" License: This program is free software; you can redistribute it and/or +" modify it under the terms of the GNU General Public License +" as published by the Free Software Foundation; either version +" 2 of the License, or (at your option) any later version. +" See http://www.gnu.org/copyleft/gpl-2.0.txt +" +" Section: Documentation {{{1 +" +" Description: {{{2 +" +" This script implements transparent editing of gpg encrypted files. The +" filename must have a ".gpg", ".pgp" or ".asc" suffix. When opening such +" a file the content is decrypted, when opening a new file the script will +" ask for the recipients of the encrypted file. The file content will be +" encrypted to all recipients before it is written. The script turns off +" viminfo, swapfile, and undofile to increase security. +" +" Installation: {{{2 +" +" Copy the gnupg.vim file to the $HOME/.vim/plugin directory. +" Refer to ':help add-plugin', ':help add-global-plugin' and ':help +" runtimepath' for more details about Vim plugins. +" +" From "man 1 gpg-agent": +" +" ... +" You should always add the following lines to your .bashrc or whatever +" initialization file is used for all shell invocations: +" +" GPG_TTY=`tty` +" export GPG_TTY +" +" It is important that this environment variable always reflects the out‐ +" put of the tty command. For W32 systems this option is not required. +" ... +" +" Most distributions provide software to ease handling of gpg and gpg-agent. +" Examples are keychain or seahorse. +" +" If there are specific actions that should take place when editing a +" GnuPG-managed buffer, an autocmd for the User event and GnuPG pattern can +" be defined. For example, the following will set 'textwidth' to 72 for all +" GnuPG-encrypted buffers: +" +" autocmd User GnuPG setl textwidth=72 +" +" This will be triggered before any BufRead or BufNewFile autocmds, and +" therefore will not take precedence over settings specific to any filetype +" that may get set. +" +" Commands: {{{2 +" +" :GPGEditRecipients +" Opens a scratch buffer to change the list of recipients. Recipients that +" are unknown (not in your public key) are highlighted and have +" a prepended "!". Closing the buffer makes the changes permanent. +" +" :GPGViewRecipients +" Prints the list of recipients. +" +" :GPGEditOptions +" Opens a scratch buffer to change the options for encryption (symmetric, +" asymmetric, signing). Closing the buffer makes the changes permanent. +" WARNING: There is no check of the entered options, so you need to know +" what you are doing. +" +" :GPGViewOptions +" Prints the list of options. +" +" Variables: {{{2 +" +" g:GPGExecutable +" If set used as gpg executable. If unset, defaults to +" "gpg --trust-model always" if "gpg" is available, falling back to +" "gpg2 --trust-model always" if not. +" +" g:GPGUseAgent +" If set to 0 a possible available gpg-agent won't be used. Defaults to 1. +" +" g:GPGPreferSymmetric +" If set to 1 symmetric encryption is preferred for new files. Defaults to 0. +" +" g:GPGPreferArmor +" If set to 1 armored data is preferred for new files. Defaults to 0 +" unless a "*.asc" file is being edited. +" +" g:GPGPreferSign +" If set to 1 signed data is preferred for new files. Defaults to 0. +" +" g:GPGDefaultRecipients +" If set, these recipients are used as defaults when no other recipient is +" defined. This variable is a Vim list. Default is unset. +" +" g:GPGPossibleRecipients +" If set, these contents are loaded into the recipients dialog. This +" allows to add commented lines with possible recipients to the list, +" which can be uncommented to select the actual recipients. Default is +" unset. Example: +" +" let g:GPGPossibleRecipients=[ +" \"Example User ", +" \"Other User " +" \] +" +" +" g:GPGUsePipes +" If set to 1, use pipes instead of temporary files when interacting with +" gnupg. When set to 1, this can cause terminal-based gpg agents to not +" display correctly when prompting for passwords. Defaults to 0. +" +" g:GPGHomedir +" If set, specifies the directory that will be used for GPG's homedir. +" This corresponds to gpg's --homedir option. This variable is a Vim +" string. Default is unset. +" +" g:GPGFilePattern +" If set, overrides the default set of file patterns that determine +" whether this plugin will be activated. Defaults to +" '*.\(gpg\|asc\|pgp\)'. +" +" Known Issues: {{{2 +" +" In some cases gvim can't decrypt files + +" This is caused by the fact that a running gvim has no TTY and thus gpg is +" not able to ask for the passphrase by itself. This is a problem for Windows +" and Linux versions of gvim and could not be solved unless a "terminal +" emulation" is implemented for gvim. To circumvent this you have to use any +" combination of gpg-agent and a graphical pinentry program: +" +" - gpg-agent only: +" you need to provide the passphrase for the needed key to gpg-agent +" in a terminal before you open files with gvim which require this key. +" +" - pinentry only: +" you will get a popup window every time you open a file that needs to +" be decrypted. +" +" - gpgagent and pinentry: +" you will get a popup window the first time you open a file that +" needs to be decrypted. +" +" If you're using Vim <7.4.959, after the plugin runs any external command, +" Vim will no longer be able to yank to/paste from the X clipboard or +" primary selections. This is caused by a workaround for a different bug +" where Vim no longer recognizes the key codes for keys such as the arrow +" keys after running GnuPG. See the discussion at +" https://github.com/jamessan/vim-gnupg/issues/36 for more details. +" +" Credits: {{{2 +" +" - Mathieu Clabaut for inspirations through his vimspell.vim script. +" - Richard Bronosky for patch to enable ".pgp" suffix. +" - Erik Remmelzwaal for patch to enable windows support and patient beta +" testing. +" - Lars Becker for patch to make gpg2 working. +" - Thomas Arendsen Hein for patch to convert encoding of gpg output. +" - Karl-Heinz Ruskowski for patch to fix unknown recipients and trust model +" and patient beta testing. +" - Giel van Schijndel for patch to get GPG_TTY dynamically. +" - Sebastian Luettich for patch to fix issue with symmetric encryption an set +" recipients. +" - Tim Swast for patch to generate signed files. +" - James Vega for patches for better '*.asc' handling, better filename +" escaping and better handling of multiple keyrings. +" +" Section: Plugin header {{{1 + +" guard against multiple loads {{{2 +if (exists("g:loaded_gnupg") || &cp || exists("#GnuPG")) + finish +endif +let g:loaded_gnupg = '2.6.1-dev' +let s:GPGInitRun = 0 + +" check for correct vim version {{{2 +if (v:version < 702) + echohl ErrorMsg | echo 'plugin gnupg.vim requires Vim version >= 7.2' | echohl None + finish +endif + +" Section: Autocmd setup {{{1 + +if (!exists("g:GPGFilePattern")) + let g:GPGFilePattern = '*.\(gpg\|asc\|pgp\)' +endif + +augroup GnuPG + autocmd! + + " do the decryption + exe "autocmd BufReadCmd " . g:GPGFilePattern . " call s:GPGInit(1) |" . + \ " call s:GPGDecrypt(1)" + exe "autocmd FileReadCmd " . g:GPGFilePattern . " call s:GPGInit(0) |" . + \ " call s:GPGDecrypt(0)" + + " convert all text to encrypted text before writing + " We check for GPGCorrespondingTo to avoid triggering on writes in GPG Options/Recipient windows + exe "autocmd BufWriteCmd,FileWriteCmd " . g:GPGFilePattern . " if !exists('b:GPGCorrespondingTo') |" . + \ " call s:GPGInit(0) |" . + \ " call s:GPGEncrypt() |" . + \ " endif" +augroup END + +" Section: Constants {{{1 + +let s:GPGMagicString = "\t \t" +let s:keyPattern = '\%(0x\)\=[[:xdigit:]]\{8,16}' + +" Section: Highlight setup {{{1 + +highlight default link GPGWarning WarningMsg +highlight default link GPGError ErrorMsg +highlight default link GPGHighlightUnknownRecipient ErrorMsg + +" Section: Functions {{{1 + +" Function: s:shellescape(s[, dict]) {{{2 +" +" Calls shellescape(), also taking into account 'shellslash' +" when on Windows and using $COMSPEC as the shell. +" +" Recognized keys are: +" special - Passed through as special argument for Vim's shellescape() +" cygpath - When true and s:useCygpath is true, adjust the path to work with +" Gpg4win from cygwin +" +" Returns: shellescaped string +" +function s:shellescape(s, ...) + let opts = a:0 ? a:1 : {} + let special = get(opts, 'special', 0) + let cygpath = get(opts, 'cygpath', 0) + let s = a:s + if cygpath && s:useCygpath + let s = matchstr(system('cygpath -am '.shellescape(s)), '[^\x0a\x0d]*') + endif + if exists('+shellslash') && &shell == $COMSPEC + let ssl = &shellslash + set noshellslash + + let escaped = shellescape(s, special) + + let &shellslash = ssl + else + let escaped = shellescape(s, special) + endif + + return escaped +endfunction + +" Function: s:unencrypted() {{{2 +" +" Determines if the buffer corresponds to an existing, unencrypted file and, +" if so, warns the user that GPG functionality has been disabled. +" +" Returns: true if current buffer corresponds to an existing, unencrypted file +function! s:unencrypted() + if (exists("b:GPGEncrypted") && b:GPGEncrypted == 0) + echohl GPGWarning + echom "File is not encrypted, all GPG functions disabled!" + echohl None + return 1 + endif + + return 0 +endfunction + +" Function: s:GPGInit(bufread) {{{2 +" +" initialize the plugin +" The bufread argument specifies whether this was called due to BufReadCmd +" +function s:GPGInit(bufread) + call s:GPGDebug(3, printf(">>>>>>>> Entering s:GPGInit(%d)", a:bufread)) + + " For FileReadCmd, we're reading the contents into another buffer. If that + " buffer is also destined to be encrypted, then these settings will have + " already been set, otherwise don't set them since it limits the + " functionality of the cleartext buffer. + if a:bufread + " we don't want a swap file, as it writes unencrypted data to disk + setl noswapfile + + " if persistent undo is present, disable it for this buffer + if exists('+undofile') + setl noundofile + endif + + " first make sure nothing is written to ~/.viminfo while editing + " an encrypted file. + set viminfo= + endif + + " the rest only has to be run once + if s:GPGInitRun + return + endif + + " check what gpg command to use + if (!exists("g:GPGExecutable")) + if executable("gpg") + let g:GPGExecutable = "gpg --trust-model always" + else + let g:GPGExecutable = "gpg2 --trust-model always" + endif + endif + + " check if gpg-agent is allowed + if (!exists("g:GPGUseAgent")) + let g:GPGUseAgent = 1 + endif + + " check if symmetric encryption is preferred + if (!exists("g:GPGPreferSymmetric")) + let g:GPGPreferSymmetric = 0 + endif + + " check if signed files are preferred + if (!exists("g:GPGPreferSign")) + let g:GPGPreferSign = 0 + endif + + " start with empty default recipients if none is defined so far + if (!exists("g:GPGDefaultRecipients")) + let g:GPGDefaultRecipients = [] + endif + + if (!exists("g:GPGPossibleRecipients")) + let g:GPGPossibleRecipients = [] + endif + + + " prefer not to use pipes since it can garble gpg agent display + if (!exists("g:GPGUsePipes")) + let g:GPGUsePipes = 0 + endif + + " allow alternate gnupg homedir + if (!exists('g:GPGHomedir')) + let g:GPGHomedir = '' + endif + + " print version + call s:GPGDebug(1, "gnupg.vim ". g:loaded_gnupg) + + let s:GPGCommand = g:GPGExecutable + + " don't use tty in gvim except for windows: we get their a tty for free. + " FIXME find a better way to avoid an error. + " with this solution only --use-agent will work + if (has("gui_running") && !has("gui_win32")) + let s:GPGCommand .= " --no-tty" + endif + + " setup shell environment for unix and windows + let s:shellredirsave = &shellredir + let s:shellsave = &shell + let s:shelltempsave = &shelltemp + " noshelltemp isn't currently supported on Windows, but it doesn't cause any + " errors and this future proofs us against requiring changes if Windows + " gains noshelltemp functionality + let s:shelltemp = !g:GPGUsePipes + if (has("unix")) + " unix specific settings + let s:shellredir = ">%s 2>&1" + let s:shell = '/bin/sh' + let s:stderrredirnull = '2>/dev/null' + else + " windows specific settings + let s:shellredir = '>%s' + let s:shell = &shell + let s:stderrredirnull = '2>nul' + endif + + call s:GPGDebug(3, "shellredirsave: " . s:shellredirsave) + call s:GPGDebug(3, "shellsave: " . s:shellsave) + call s:GPGDebug(3, "shelltempsave: " . s:shelltempsave) + + call s:GPGDebug(3, "shell: " . s:shell) + call s:GPGDebug(3, "shellcmdflag: " . &shellcmdflag) + call s:GPGDebug(3, "shellxquote: " . &shellxquote) + call s:GPGDebug(3, "shellredir: " . s:shellredir) + call s:GPGDebug(3, "stderrredirnull: " . s:stderrredirnull) + + call s:GPGDebug(3, "shell implementation: " . resolve(s:shell)) + + " find the supported algorithms + let output = s:GPGSystem({ 'level': 2, 'args': '--version' }) + + let gpgversion = substitute(output, '^gpg (GnuPG) \([0-9]\+\.\d\+\).*', '\1', '') + let s:GPGPubkey = substitute(output, ".*Pubkey: \\(.\\{-}\\)\n.*", "\\1", "") + let s:GPGCipher = substitute(output, ".*Cipher: \\(.\\{-}\\)\n.*", "\\1", "") + let s:GPGHash = substitute(output, ".*Hash: \\(.\\{-}\\)\n.*", "\\1", "") + let s:GPGCompress = substitute(output, ".*Compress.\\{-}: \\(.\\{-}\\)\n.*", "\\1", "") + let s:GPGHome = matchstr(output, '.*Home: \zs.\{-}\ze\r\=\n') + + let s:useCygpath = 0 + if has('win32unix') && s:GPGHome =~ '\a:[/\\]' + call s:GPGDebug(1, 'Enabling use of cygpath') + let s:useCygpath = 1 + endif + + " determine if gnupg can use the gpg-agent + if (str2float(gpgversion) >= 2.1 || (exists("$GPG_AGENT_INFO") && g:GPGUseAgent == 1)) + if (!exists("$GPG_TTY") && !has("gui_running")) + " Need to determine the associated tty by running a command in the + " shell. We can't use system() here because that doesn't run in a shell + " connected to a tty, so it's rather useless. + " + " Save/restore &modified so the buffer isn't incorrectly marked as + " modified just by detecting the correct tty value. + " Do the &undolevels dance so the :read and :delete don't get added into + " the undo tree, as the user needn't be aware of these. + let [mod, levels] = [&l:modified, &undolevels] + set undolevels=-1 + silent read !tty + let $GPG_TTY = getline('.') + silent delete + let [&l:modified, &undolevels] = [mod, levels] + " redraw is needed since we're using silent to run !tty, c.f. :help :! + redraw! + if (v:shell_error) + let $GPG_TTY = "" + echohl GPGWarning + echom "$GPG_TTY is not set and the `tty` command failed! gpg-agent might not work." + echohl None + endif + endif + let s:GPGCommand .= " --use-agent" + else + let s:GPGCommand .= " --no-use-agent" + endif + + call s:GPGDebug(2, "public key algorithms: " . s:GPGPubkey) + call s:GPGDebug(2, "cipher algorithms: " . s:GPGCipher) + call s:GPGDebug(2, "hashing algorithms: " . s:GPGHash) + call s:GPGDebug(2, "compression algorithms: " . s:GPGCompress) + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGInit()") + let s:GPGInitRun = 1 +endfunction + + +" Function: s:GPGDecrypt(bufread) {{{2 +" +" decrypt the buffer and find all recipients of the encrypted file +" The bufread argument specifies whether this was called due to BufReadCmd +" +function s:GPGDecrypt(bufread) + call s:GPGDebug(3, printf(">>>>>>>> Entering s:GPGDecrypt(%d)", a:bufread)) + + " get the filename of the current buffer + let filename = resolve(expand(":p")) + + " clear GPGRecipients and GPGOptions + if type(g:GPGDefaultRecipients) == type([]) + let b:GPGRecipients = copy(g:GPGDefaultRecipients) + else + let b:GPGRecipients = [] + echohl GPGWarning + echom "g:GPGDefaultRecipients is not a Vim list, please correct this in your vimrc!" + echohl None + endif + let b:GPGOptions = [] + + " file name minus extension + let autocmd_filename = fnamemodify(filename, ':r') + + " File doesn't exist yet, so nothing to decrypt + if !filereadable(filename) + if !a:bufread + redraw! + echohl GPGError + echom "E484: Can't open file" filename + echohl None + return + endif + + " Allow the user to define actions for GnuPG buffers + silent doautocmd User GnuPG + silent execute ':doautocmd BufNewFile ' . fnameescape(autocmd_filename) + call s:GPGDebug(2, 'called BufNewFile autocommand for ' . autocmd_filename) + + set buftype=acwrite + " Remove the buffer name ... + silent 0file + " ... so we can force it to be absolute + exe 'silent file' fnameescape(filename) + + " This is a new file, so force the user to edit the recipient list if + " they open a new file and public keys are preferred + if (g:GPGPreferSymmetric == 0) + call s:GPGEditRecipients() + endif + + return + endif + + " Only let this if the file actually exists, otherwise GPG functionality + " will be disabled when editing a buffer that doesn't yet have a backing + " file + let b:GPGEncrypted = 0 + + " find the recipients of the file + let cmd = { 'level': 3 } + let cmd.args = '--verbose --decrypt --list-only --dry-run --no-use-agent --logger-fd 1 ' . s:shellescape(filename, { 'cygpath': 1 }) + let output = s:GPGSystem(cmd) + + " Suppress the "N more lines" message when editing a file, not when reading + " the contents of a file into a buffer + let silent = a:bufread ? 'silent ' : '' + + let asymmPattern = 'gpg: public key is ' . s:keyPattern + " check if the file is symmetric/asymmetric encrypted + if (match(output, "gpg: encrypted with [[:digit:]]\\+ passphrase") >= 0) + " file is symmetric encrypted + let b:GPGEncrypted = 1 + call s:GPGDebug(1, "this file is symmetric encrypted") + + let b:GPGOptions += ["symmetric"] + + " find the used cipher algorithm + let cipher = substitute(output, ".*gpg: \\([^ ]\\+\\) encrypted data.*", "\\1", "") + if (match(s:GPGCipher, "\\<" . cipher . "\\>") >= 0) + let b:GPGOptions += ["cipher-algo " . cipher] + call s:GPGDebug(1, "cipher-algo is " . cipher) + else + echohl GPGWarning + echom "The cipher " . cipher . " is not known by the local gpg command. Using default!" + echo + echohl None + endif + elseif (match(output, asymmPattern) >= 0) + " file is asymmetric encrypted + let b:GPGEncrypted = 1 + call s:GPGDebug(1, "this file is asymmetric encrypted") + + let b:GPGOptions += ["encrypt"] + + " find the used public keys + let start = match(output, asymmPattern) + while (start >= 0) + let start = start + strlen("gpg: public key is ") + let recipient = matchstr(output, s:keyPattern, start) + call s:GPGDebug(1, "recipient is " . recipient) + " In order to support anonymous communication, GnuPG allows eliding + " information in the encryption metadata specifying what keys the file + " was encrypted to (c.f., --throw-keyids and --hidden-recipient). In + " that case, the recipient(s) will be listed as having used a key of all + " zeroes. + " Since this will obviously never actually be in a keyring, only try to + " convert to an ID or add to the recipients list if it's not a hidden + " recipient. + if recipient !~? '^0x0\+$' + let name = s:GPGNameToID(recipient) + if !empty(name) + let b:GPGRecipients += [name] + call s:GPGDebug(1, "name of recipient is " . name) + else + let b:GPGRecipients += [recipient] + echohl GPGWarning + echom "The recipient \"" . recipient . "\" is not in your public keyring!" + echohl None + end + end + let start = match(output, asymmPattern, start) + endwhile + else + " file is not encrypted + let b:GPGEncrypted = 0 + call s:GPGDebug(1, "this file is not encrypted") + echohl GPGWarning + echom "File is not encrypted, all GPG functions disabled!" + echohl None + endif + + let bufname = b:GPGEncrypted ? autocmd_filename : filename + if a:bufread + silent execute ':doautocmd BufReadPre ' . fnameescape(bufname) + call s:GPGDebug(2, 'called BufReadPre autocommand for ' . bufname) + else + silent execute ':doautocmd FileReadPre ' . fnameescape(bufname) + call s:GPGDebug(2, 'called FileReadPre autocommand for ' . bufname) + endif + + if b:GPGEncrypted + " check if the message is armored + if (match(output, "gpg: armor header") >= 0) + call s:GPGDebug(1, "this file is armored") + let b:GPGOptions += ["armor"] + endif + + " finally decrypt the buffer content + " since even with the --quiet option passphrase typos will be reported, + " we must redirect stderr (using shell temporarily) + call s:GPGDebug(1, "decrypting file") + let cmd = { 'level': 1, 'ex': silent . 'read ++edit !' } + let cmd.args = '--quiet --decrypt ' . s:shellescape(filename, { 'special': 1, 'cygpath': 1 }) + call s:GPGExecute(cmd) + + if (v:shell_error) " message could not be decrypted + echohl GPGError + let blackhole = input("Message could not be decrypted! (Press ENTER)") + echohl None + " Only wipeout the buffer if we were creating one to start with. + " FileReadCmd just reads the content into the existing buffer + if a:bufread + silent bwipeout! + endif + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGDecrypt()") + return + endif + if a:bufread + " Ensure the buffer is only saved by using our BufWriteCmd + set buftype=acwrite + " Always set the buffer name to the absolute path, otherwise Vim won't + " track the correct buffer name when changing directories (due to + " buftype=acwrite). + exe 'file' fnameescape(filename) + endif + else + execute silent 'read' fnameescape(filename) + endif + + if a:bufread + " In order to make :undo a no-op immediately after the buffer is read, + " we need to do this dance with 'undolevels'. Actually discarding the undo + " history requires performing a change after setting 'undolevels' to -1 and, + " luckily, we have one we need to do (delete the extra line from the :r + " command) + let levels = &undolevels + set undolevels=-1 + " :lockmarks doesn't actually prevent '[,'] from being overwritten, so we + " need to manually set them ourselves instead + silent 1delete + 1mark [ + $mark ] + let &undolevels = levels + " The buffer should be readonly if + " - 'readonly' is already set (e.g., when using view/vim -R) + " - permissions don't allow writing + let &readonly = &readonly || (filereadable(filename) && filewritable(filename) == 0) + silent execute ':doautocmd BufReadPost ' . fnameescape(bufname) + call s:GPGDebug(2, 'called BufReadPost autocommand for ' . bufname) + else + silent execute ':doautocmd FileReadPost ' . fnameescape(bufname) + call s:GPGDebug(2, 'called FileReadPost autocommand for ' . bufname) + endif + + if b:GPGEncrypted + " Allow the user to define actions for GnuPG buffers + silent doautocmd User GnuPG + + " refresh screen + redraw! + endif + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGDecrypt()") +endfunction + +" Function: s:GPGEncrypt() {{{2 +" +" encrypts the buffer to all previous recipients +" +function s:GPGEncrypt() + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGEncrypt()") + + " FileWriteCmd is only called when a portion of a buffer is being written to + " disk. Since Vim always sets the '[,'] marks to the part of a buffer that + " is being written, that can be used to determine whether BufWriteCmd or + " FileWriteCmd triggered us. + if [line("'["), line("']")] == [1, line('$')] + let auType = 'BufWrite' + else + let auType = 'FileWrite' + endif + + " file name minus extension + let autocmd_filename = expand(':p:r') + + silent exe ':doautocmd '. auType .'Pre '. fnameescape(autocmd_filename) + call s:GPGDebug(2, 'called '. auType .'Pre autocommand for ' . autocmd_filename) + + " guard for unencrypted files + if (exists("b:GPGEncrypted") && b:GPGEncrypted == 0) + echohl GPGError + let blackhole = input("Message could not be encrypted! (Press ENTER)") + echohl None + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGEncrypt()") + return + endif + + let filename = resolve(expand(':p')) + " initialize GPGOptions if not happened before + if (!exists("b:GPGOptions") || empty(b:GPGOptions)) + let b:GPGOptions = [] + if (exists("g:GPGPreferSymmetric") && g:GPGPreferSymmetric == 1) + let b:GPGOptions += ["symmetric"] + let b:GPGRecipients = [] + else + let b:GPGOptions += ["encrypt"] + endif + " Fallback to preference by filename if the user didn't indicate + " their preference. + let preferArmor = get(g:, 'GPGPreferArmor', -1) + if (preferArmor >= 0 && preferArmor) || filename =~ '\.asc$' + let b:GPGOptions += ["armor"] + endif + if (exists("g:GPGPreferSign") && g:GPGPreferSign == 1) + let b:GPGOptions += ["sign"] + endif + call s:GPGDebug(1, "no options set, so using default options: " . string(b:GPGOptions)) + endif + + " built list of options + let options = "" + for option in b:GPGOptions + let options = options . " --" . option . " " + endfor + + if (!exists('b:GPGRecipients')) + let b:GPGRecipients = [] + endif + + " check here again if all recipients are available in the keyring + let recipients = s:GPGCheckRecipients(b:GPGRecipients) + + " check if there are unknown recipients and warn + if !empty(recipients.unknown) + echohl GPGWarning + echom "Please use GPGEditRecipients to correct!!" + echo + echohl None + + " Let user know whats happend and copy known_recipients back to buffer + let dummy = input("Press ENTER to quit") + endif + + " built list of recipients + let options .= ' ' . join(map(recipients.valid, '"-r ".v:val'), ' ') + + " encrypt the buffer + let destfile = tempname() + let cmd = { 'level': 1, 'ex': "'[,']write !" } + let cmd.args = '--quiet --no-encrypt-to ' . options + let cmd.redirect = '>' . s:shellescape(destfile, { 'special': 1, 'cygpath': 1 }) + silent call s:GPGExecute(cmd) + + if (v:shell_error) " message could not be encrypted + " Command failed, so clean up the tempfile + call delete(destfile) + echohl GPGError + let blackhole = input("Message could not be encrypted! (Press ENTER)") + echohl None + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGEncrypt()") + return + endif + + if rename(destfile, filename) + " Rename failed, so clean up the tempfile + call delete(destfile) + echohl GPGError + echom printf("\"%s\" E212: Can't open file for writing", filename) + echohl None + return + endif + + if auType == 'BufWrite' + if expand('%:p') == filename + setl nomodified + endif + setl buftype=acwrite + let &readonly = filereadable(filename) && filewritable(filename) == 0 + endif + + silent exe ':doautocmd '. auType .'Post '. fnameescape(autocmd_filename) + call s:GPGDebug(2, 'called '. auType .'Post autocommand for ' . autocmd_filename) + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGEncrypt()") +endfunction + +" Function: s:GPGViewRecipients() {{{2 +" +" echo the recipients +" +function s:GPGViewRecipients() + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGViewRecipients()") + + " guard for unencrypted files + if (exists("b:GPGEncrypted") && b:GPGEncrypted == 0) + echohl GPGWarning + echom "File is not encrypted, all GPG functions disabled!" + echohl None + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGViewRecipients()") + return + endif + + let recipients = s:GPGCheckRecipients(b:GPGRecipients) + + echo 'This file has following recipients (Unknown recipients have a prepended "!"):' + if empty(recipients.valid) + echohl GPGError + echo 'There are no known recipients!' + echohl None + else + echo join(map(recipients.valid, 's:GPGIDToName(v:val)'), "\n") + endif + + if !empty(recipients.unknown) + echohl GPGWarning + echo join(map(recipients.unknown, '"!".v:val'), "\n") + echohl None + endif + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGViewRecipients()") +endfunction + +" Function: s:GPGEditRecipients() {{{2 +" +" create a scratch buffer with all recipients to add/remove recipients +" +function s:GPGEditRecipients() + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGEditRecipients()") + + if s:unencrypted() + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGEditRecipients()") + return + endif + + " only do this if it isn't already a GPGRecipients_* buffer + if (!exists('b:GPGCorrespondingTo')) + + " save buffer name + let buffername = bufnr("%") + let editbuffername = "GPGRecipients_" . buffername + + " check if this buffer exists + if (!bufexists(editbuffername)) + " create scratch buffer + execute 'silent! split ' . fnameescape(editbuffername) + + " add a autocommand to regenerate the recipients after a write + autocmd BufHidden,BufUnload,BufWriteCmd call s:GPGFinishRecipientsBuffer() + else + if (bufwinnr(editbuffername) >= 0) + " switch to scratch buffer window + execute 'silent! ' . bufwinnr(editbuffername) . "wincmd w" + else + " split scratch buffer window + execute 'silent! sbuffer ' . fnameescape(editbuffername) + + " add a autocommand to regenerate the recipients after a write + autocmd BufHidden,BufUnload,BufWriteCmd call s:GPGFinishRecipientsBuffer() + endif + + " empty the buffer + silent %delete + endif + + " Mark the buffer as a scratch buffer + setlocal buftype=acwrite + setlocal bufhidden=hide + setlocal noswapfile + setlocal nowrap + setlocal nobuflisted + setlocal nonumber + + " so we know for which other buffer this edit buffer is + let b:GPGCorrespondingTo = buffername + + " put some comments to the scratch buffer + silent put ='GPG: ----------------------------------------------------------------------' + silent put ='GPG: Please edit the list of recipients, one recipient per line.' + silent put ='GPG: Unknown recipients have a prepended \"!\".' + silent put ='GPG: Lines beginning with \"GPG:\" are removed automatically.' + silent put ='GPG: Data after recipients between and including \"(\" and \")\" is ignored.' + silent put ='GPG: Closing this buffer commits changes.' + silent put ='GPG: ----------------------------------------------------------------------' + + " get the recipients + let recipients = s:GPGCheckRecipients(getbufvar(b:GPGCorrespondingTo, "GPGRecipients")) + + " if there are no known or unknown recipients, use the default ones + if (empty(recipients.valid) && empty(recipients.unknown)) + if (type(g:GPGDefaultRecipients) == type([])) + let recipients = s:GPGCheckRecipients(g:GPGDefaultRecipients) + else + echohl GPGWarning + echom "g:GPGDefaultRecipients is not a Vim list, please correct this in your vimrc!" + echohl None + endif + endif + + " put the recipients in the scratch buffer + for name in recipients.valid + let name = s:GPGIDToName(name) + silent put =name + endfor + + " put the unknown recipients in the scratch buffer + let syntaxPattern = '' + if !empty(recipients.unknown) + let flaggedNames = map(recipients.unknown, '"!".v:val') + call append('$', flaggedNames) + let syntaxPattern = '\(' . join(flaggedNames, '\|') . '\)' + endif + + for line in g:GPGPossibleRecipients + silent put ='GPG: '.line + endfor + + " define highlight + if (has("syntax") && exists("g:syntax_on")) + highlight clear GPGUnknownRecipient + if !empty(syntaxPattern) + execute 'syntax match GPGUnknownRecipient "' . syntaxPattern . '"' + highlight link GPGUnknownRecipient GPGHighlightUnknownRecipient + endif + + syntax match GPGComment "^GPG:.*$" + execute 'syntax match GPGComment "' . s:GPGMagicString . '.*$"' + highlight clear GPGComment + highlight link GPGComment Comment + endif + + " delete the empty first line + silent 1delete + + " jump to the first recipient + silent $ + + endif + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGEditRecipients()") +endfunction + +" Function: s:GPGFinishRecipientsBuffer() {{{2 +" +" create a new recipient list from RecipientsBuffer +" +function s:GPGFinishRecipientsBuffer() + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGFinishRecipientsBuffer()") + + if s:unencrypted() + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGFinishRecipientsBuffer()") + return + endif + + " go to buffer before doing work + if (bufnr("%") != expand("")) + " switch to scratch buffer window + execute 'silent! ' . bufwinnr(expand(":p")) . "wincmd w" + endif + + " delete the autocommand + autocmd! * + + " get the recipients from the scratch buffer + let recipients = [] + let lines = getline(1,"$") + for recipient in lines + let matches = matchlist(recipient, '^\(.\{-}\)\%(' . s:GPGMagicString . '(ID:\s\+\(' . s:keyPattern . '\)\s\+.*\)\=$') + + let recipient = matches[2] ? matches[2] : matches[1] + + " delete all spaces at beginning and end of the recipient + " also delete a '!' at the beginning of the recipient + let recipient = substitute(recipient, "^[[:space:]!]*\\(.\\{-}\\)[[:space:]]*$", "\\1", "") + + " delete comment lines + let recipient = substitute(recipient, "^GPG:.*$", "", "") + + " only do this if the line is not empty + if !empty(recipient) + let gpgid = s:GPGNameToID(recipient) + if !empty(gpgid) + if (match(recipients, gpgid) < 0) + let recipients += [gpgid] + endif + else + if (match(recipients, recipient) < 0) + let recipients += [recipient] + echohl GPGWarning + echom "The recipient \"" . recipient . "\" is not in your public keyring!" + echohl None + endif + endif + endif + endfor + + " write back the new recipient list to the corresponding buffer and mark it + " as modified. Buffer is now for sure an encrypted buffer. + call setbufvar(b:GPGCorrespondingTo, "GPGRecipients", recipients) + call setbufvar(b:GPGCorrespondingTo, "&mod", 1) + call setbufvar(b:GPGCorrespondingTo, "GPGEncrypted", 1) + + " check if there is any known recipient + if empty(recipients) + echohl GPGError + echom 'There are no known recipients!' + echohl None + endif + + " reset modified flag + setl nomodified + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGFinishRecipientsBuffer()") +endfunction + +" Function: s:GPGViewOptions() {{{2 +" +" echo the recipients +" +function s:GPGViewOptions() + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGViewOptions()") + + if s:unencrypted() + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGViewOptions()") + return + endif + + if (exists("b:GPGOptions")) + echo 'This file has following options:' + echo join(b:GPGOptions, "\n") + endif + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGViewOptions()") +endfunction + +" Function: s:GPGEditOptions() {{{2 +" +" create a scratch buffer with all recipients to add/remove recipients +" +function s:GPGEditOptions() + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGEditOptions()") + + if s:unencrypted() + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGEditOptions()") + return + endif + + " only do this if it isn't already a GPGOptions_* buffer + if (!exists('b:GPGCorrespondingTo')) + + " save buffer name + let buffername = bufnr("%") + let editbuffername = "GPGOptions_" . buffername + + " check if this buffer exists + if (!bufexists(editbuffername)) + " create scratch buffer + execute 'silent! split ' . fnameescape(editbuffername) + + " add a autocommand to regenerate the options after a write + autocmd BufHidden,BufUnload,BufWriteCmd call s:GPGFinishOptionsBuffer() + else + if (bufwinnr(editbuffername) >= 0) + " switch to scratch buffer window + execute 'silent! ' . bufwinnr(editbuffername) . "wincmd w" + else + " split scratch buffer window + execute 'silent! sbuffer ' . fnameescape(editbuffername) + + " add a autocommand to regenerate the options after a write + autocmd BufHidden,BufUnload,BufWriteCmd call s:GPGFinishOptionsBuffer() + endif + + " empty the buffer + silent %delete + endif + + " Mark the buffer as a scratch buffer + setlocal buftype=nofile + setlocal noswapfile + setlocal nowrap + setlocal nobuflisted + setlocal nonumber + + " so we know for which other buffer this edit buffer is + let b:GPGCorrespondingTo = buffername + + " put some comments to the scratch buffer + silent put ='GPG: ----------------------------------------------------------------------' + silent put ='GPG: THERE IS NO CHECK OF THE ENTERED OPTIONS!' + silent put ='GPG: YOU NEED TO KNOW WHAT YOU ARE DOING!' + silent put ='GPG: IF IN DOUBT, QUICKLY EXIT USING :x OR :bd.' + silent put ='GPG: Please edit the list of options, one option per line.' + silent put ='GPG: Please refer to the gpg documentation for valid options.' + silent put ='GPG: Lines beginning with \"GPG:\" are removed automatically.' + silent put ='GPG: Closing this buffer commits changes.' + silent put ='GPG: ----------------------------------------------------------------------' + + " put the options in the scratch buffer + let options = getbufvar(b:GPGCorrespondingTo, "GPGOptions") + + for option in options + silent put =option + endfor + + " delete the empty first line + silent 1delete + + " jump to the first option + silent $ + + " define highlight + if (has("syntax") && exists("g:syntax_on")) + syntax match GPGComment "^GPG:.*$" + highlight clear GPGComment + highlight link GPGComment Comment + endif + endif + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGEditOptions()") +endfunction + +" Function: s:GPGFinishOptionsBuffer() {{{2 +" +" create a new option list from OptionsBuffer +" +function s:GPGFinishOptionsBuffer() + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGFinishOptionsBuffer()") + + if s:unencrypted() + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGFinishOptionsBuffer()") + return + endif + + " go to buffer before doing work + if (bufnr("%") != expand("")) + " switch to scratch buffer window + execute 'silent! ' . bufwinnr(expand(":p")) . "wincmd w" + endif + + " clear options and unknownOptions + let options = [] + let unknownOptions = [] + + " delete the autocommand + autocmd! * + + " get the options from the scratch buffer + let lines = getline(1, "$") + for option in lines + " delete all spaces at beginning and end of the option + " also delete a '!' at the beginning of the option + let option = substitute(option, "^[[:space:]!]*\\(.\\{-}\\)[[:space:]]*$", "\\1", "") + " delete comment lines + let option = substitute(option, "^GPG:.*$", "", "") + + " only do this if the line is not empty + if (!empty(option) && match(options, option) < 0) + let options += [option] + endif + endfor + + " write back the new option list to the corresponding buffer and mark it + " as modified + call setbufvar(b:GPGCorrespondingTo, "GPGOptions", options) + call setbufvar(b:GPGCorrespondingTo, "&mod", 1) + + " reset modified flag + setl nomodified + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGFinishOptionsBuffer()") +endfunction + +" Function: s:GPGCheckRecipients(tocheck) {{{2 +" +" check if recipients are known +" Returns: dictionary of recipients, {'valid': [], 'unknown': []} +" +function s:GPGCheckRecipients(tocheck) + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGCheckRecipients()") + + let recipients = {'valid': [], 'unknown': []} + + if (type(a:tocheck) == type([])) + for recipient in a:tocheck + let gpgid = s:GPGNameToID(recipient) + if !empty(gpgid) + if (match(recipients.valid, gpgid) < 0) + call add(recipients.valid, gpgid) + endif + else + if (match(recipients.unknown, recipient) < 0) + call add(recipients.unknown, recipient) + echohl GPGWarning + echom "The recipient \"" . recipient . "\" is not in your public keyring!" + echohl None + endif + end + endfor + endif + + call s:GPGDebug(2, "recipients are: " . string(recipients.valid)) + call s:GPGDebug(2, "unknown recipients are: " . string(recipients.unknown)) + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGCheckRecipients()") + return recipients +endfunction + +" Function: s:GPGNameToID(name) {{{2 +" +" find GPG key ID corresponding to a name +" Returns: ID for the given name +" +function s:GPGNameToID(name) + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGNameToID()") + + " ask gpg for the id for a name + let cmd = { 'level': 2 } + let cmd.args = '--quiet --with-colons --fixed-list-mode --list-keys ' . s:shellescape(a:name) + let output = s:GPGSystem(cmd) + + " when called with "--with-colons" gpg encodes its output _ALWAYS_ as UTF-8, + " so convert it, if necessary + if (&encoding != "utf-8") + let output = iconv(output, "utf-8", &encoding) + endif + let lines = split(output, "\n") + + " parse the output of gpg + let pubseen = 0 + let counter = 0 + let gpgids = [] + let seen_keys = {} + let skip_key = 0 + let has_strftime = exists('*strftime') + let choices = "The name \"" . a:name . "\" is ambiguous. Please select the correct key:\n" + for line in lines + + let fields = split(line, ":") + + " search for the next pub + if (fields[0] == "pub") + " check if this key has already been processed + if has_key(seen_keys, fields[4]) + let skip_key = 1 + continue + endif + let skip_key = 0 + let seen_keys[fields[4]] = 1 + + " Ignore keys which are not usable for encryption + if fields[11] !~? 'e' + continue + endif + + let identity = fields[4] + let gpgids += [identity] + if has_strftime + let choices = choices . counter . ": ID: 0x" . identity . " created at " . strftime("%c", fields[5]) . "\n" + else + let choices = choices . counter . ": ID: 0x" . identity . "\n" + endif + let counter = counter+1 + let pubseen = 1 + " search for the next uid + elseif (!skip_key && fields[0] == "uid") + let choices = choices . " " . fields[9] . "\n" + endif + + endfor + + " counter > 1 means we have more than one results + let answer = 0 + if (counter > 1) + let choices = choices . "Enter number: " + let answer = input(choices, "0") + while (answer == "") + let answer = input("Enter number: ", "0") + endwhile + endif + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGNameToID()") + return get(gpgids, answer, "") +endfunction + +" Function: s:GPGIDToName(identity) {{{2 +" +" find name corresponding to a GPG key ID +" Returns: Name for the given ID +" +function s:GPGIDToName(identity) + call s:GPGDebug(3, ">>>>>>>> Entering s:GPGIDToName()") + + " TODO is the encryption subkey really unique? + + " ask gpg for the id for a name + let cmd = { 'level': 2 } + let cmd.args = '--quiet --with-colons --fixed-list-mode --list-keys ' . a:identity + let output = s:GPGSystem(cmd) + + " when called with "--with-colons" gpg encodes its output _ALWAYS_ as UTF-8, + " so convert it, if necessary + if (&encoding != "utf-8") + let output = iconv(output, "utf-8", &encoding) + endif + let lines = split(output, "\n") + + " parse the output of gpg + let pubseen = 0 + let uid = "" + for line in lines + let fields = split(line, ":") + + if !pubseen " search for the next pub + if (fields[0] == "pub") + " Ignore keys which are not usable for encryption + if fields[11] !~? 'e' + continue + endif + + let pubseen = 1 + endif + else " search for the next uid + if (fields[0] == "uid") + let pubseen = 0 + if exists("*strftime") + let uid = fields[9] . s:GPGMagicString . "(ID: 0x" . a:identity . " created at " . strftime("%c", fields[5]) . ")" + else + let uid = fields[9] . s:GPGMagicString . "(ID: 0x" . a:identity . ")" + endif + break + endif + endif + endfor + + call s:GPGDebug(3, "<<<<<<<< Leaving s:GPGIDToName()") + return uid +endfunction + +" Function: s:GPGPreCmd() {{{2 +" +" Setup the environment for running the gpg command +" +function s:GPGPreCmd() + let &shellredir = s:shellredir + let &shell = s:shell + let &shelltemp = s:shelltemp + " Force C locale so GPG output is consistent + let s:messages = v:lang + language messages C +endfunction + + +" Function: s:GPGPostCmd() {{{2 +" +" Restore the user's environment after running the gpg command +" +function s:GPGPostCmd() + let &shellredir = s:shellredirsave + let &shell = s:shellsave + let &shelltemp = s:shelltempsave + execute 'language messages' s:messages + " Workaround a bug in the interaction between console vim and + " pinentry-curses by forcing Vim to re-detect and setup its terminal + " settings + let &term = &term + silent doautocmd TermChanged +endfunction + +" Function: s:GPGSystem(dict) {{{2 +" +" run g:GPGCommand using system(), logging the commandline and output. This +" uses temp files (regardless of how 'shelltemp' is set) to hold the output of +" the command, so it must not be used for sensitive commands. +" Recognized keys are: +" level - Debug level at which the commandline and output will be logged +" args - Arguments to be given to g:GPGCommand +" +" Returns: command output +" +function s:GPGSystem(dict) + let commandline = s:GPGCommand + if (!empty(g:GPGHomedir)) + let commandline .= ' --homedir ' . s:shellescape(g:GPGHomedir, { 'cygpath': 1 }) + endif + let commandline .= ' ' . a:dict.args + let commandline .= ' ' . s:stderrredirnull + call s:GPGDebug(a:dict.level, "command: ". commandline) + + call s:GPGPreCmd() + let output = system(commandline) + call s:GPGPostCmd() + + call s:GPGDebug(a:dict.level, "rc: ". v:shell_error) + call s:GPGDebug(a:dict.level, "output: ". output) + return output +endfunction + +" Function: s:GPGExecute(dict) {{{2 +" +" run g:GPGCommand using :execute, logging the commandline +" Recognized keys are: +" level - Debug level at which the commandline will be logged +" args - Arguments to be given to g:GPGCommand +" ex - Ex command which will be :executed +" redirect - Shell redirect to use, if needed +" +function s:GPGExecute(dict) + let commandline = printf('%s%s', a:dict.ex, s:GPGCommand) + if (!empty(g:GPGHomedir)) + let commandline .= ' --homedir ' . s:shellescape(g:GPGHomedir, { 'special': 1, 'cygpath': 1 }) + endif + let commandline .= ' ' . a:dict.args + if (has_key(a:dict, 'redirect')) + let commandline .= ' ' . a:dict.redirect + endif + let commandline .= ' ' . s:stderrredirnull + call s:GPGDebug(a:dict.level, "command: " . commandline) + + call s:GPGPreCmd() + execute commandline + call s:GPGPostCmd() + + call s:GPGDebug(a:dict.level, "rc: ". v:shell_error) +endfunction + +" Function: s:GPGDebug(level, text) {{{2 +" +" output debug message, if this message has high enough importance +" only define function if GPGDebugLevel set at all +" +function s:GPGDebug(level, text) + if exists("g:GPGDebugLevel") && g:GPGDebugLevel >= a:level + if exists("g:GPGDebugLog") + execute "redir >> " . g:GPGDebugLog + silent echom "GnuPG: " . a:text + redir END + else + echom "GnuPG: " . a:text + endif + endif +endfunction + +" Section: Commands {{{1 + +command! GPGViewRecipients call s:GPGViewRecipients() +command! GPGEditRecipients call s:GPGEditRecipients() +command! GPGViewOptions call s:GPGViewOptions() +command! GPGEditOptions call s:GPGEditOptions() + +" Section: Menu {{{1 + +if (has("menu")) + amenu Plugin.GnuPG.View\ Recipients :GPGViewRecipients + amenu Plugin.GnuPG.Edit\ Recipients :GPGEditRecipients + amenu Plugin.GnuPG.View\ Options :GPGViewOptions + amenu Plugin.GnuPG.Edit\ Options :GPGEditOptions +endif + +" vim600: set foldmethod=marker foldlevel=0 : diff --git a/.vim/bundle/vim-gph/ftdetect/gph.vim b/.vim/bundle/vim-gph/ftdetect/gph.vim new file mode 100644 index 0000000..d331930 --- /dev/null +++ b/.vim/bundle/vim-gph/ftdetect/gph.vim @@ -0,0 +1 @@ +au BufNewFile,BufRead *.gph set ft=gph syn=gph diff --git a/.vim/bundle/vim-gph/syntax/gph.vim b/.vim/bundle/vim-gph/syntax/gph.vim new file mode 100644 index 0000000..1721e01 --- /dev/null +++ b/.vim/bundle/vim-gph/syntax/gph.vim @@ -0,0 +1,104 @@ +" Syntax colouring for gopher .gph files used by geomyidae +" Muddled about a bit by dive @ freenode / #gopherproject +" 2017-11-15 + +set shiftwidth=4 +set tabstop=4 +set expandtab + +" modif by sdk +setl enc=utf-8 +setl wrap +setl linebreak +setl nolist +setl textwidth=72 +setl formatprg=par\ -w72qie +setl nojs +setl nosmartindent +"setl spell +setl nospell +setl formatoptions=troqwbj +" end + +if version < 600 + syntax clear +elseif exists("b:current_syntax") + finish +endif + +" Use default terminal colours +hi Normal ctermbg=NONE ctermfg=NONE guifg=NONE guibg=NONE + +" Use italics for comments. If this fails and you get reverse video +" then you may want to comment it out. +hi Comment cterm=italic + +" Err colour (not sure about this one. It's a bit bright). +hi Err cterm=bold ctermbg=NONE ctermfg=130 guibg=NONE guifg=red + +hi def link gopherComment comment +hi def link gopherType preproc +hi def link gopherURL statement +hi def link gopherHtml statement +hi def link gopherLink statement +hi def link gopherServerPort statement +hi def link gopherBracket preproc +hi def link gopherPipe preproc +hi def link gopherCGI type +hi def link gopherCGI2 type +hi def link gopherQuery type +hi def link gopherErr err +hi def link SynError error + +" Format of lines: +" [||||] + +" = description of gopher item. Most printable characters should work. +" +" = full path to gopher item (base value is "/" ). Use the "Err" path for +"items not intended to be served. +" +" = hostname or IP hosting the gopher item. Must be resolvable for the +"intended clients. If this is set to "server" , the server's hostname is used. +" +" = TCP port number (usually 70) If this is set to "port" , the default +"port of the server is used. + +" Comments +syn region gopherComment start="" + +" URLs +syn match gopherURL "http:" +syn region gopherLink start="http:"lc=5 end="|"me=e-1 +syn match gopherURL "gopher:" +syn match gopherURL "URL:" +syn match gopherURL "URI:" +syn region gopherLink start="gopher:"lc=7 end="|"me=e-1 + +" Pipes +syn match gopherPipe "|" containedin=gopherServerPort + +" Queries and CGI +syn match gopherQuery "^\[7"lc=1 +syn match gopherCGI "|[^|]*\.cgi[^|]*"lc=1 +syn match gopherCGI2 "|[^|]*\.dcgi[^|]*"lc=1 + +" Server|Port +syn match gopherServerPort "|[^|]*|[^|]*]" + +" Start and end brackets +match gopherBracket "[\[\]]" + +" Entity +syn region gopherType start="^\[[0123456789ghHmswITi\+:; + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/.vim/bundle/vim-ledger/README.md b/.vim/bundle/vim-ledger/README.md new file mode 100644 index 0000000..2255836 --- /dev/null +++ b/.vim/bundle/vim-ledger/README.md @@ -0,0 +1,129 @@ +# vim-ledger + +[![Vint](https://github.com/ledger/vim-ledger/workflows/Vint/badge.svg)](https://github.com/ledger/vim-ledger/actions?workflow=Vint) +[![Vader](https://github.com/ledger/vim-ledger/workflows/Vader/badge.svg)](https://github.com/ledger/vim-ledger/actions?workflow=Vader) + +Filetype detection, syntax highlighting, auto-formatting, auto-completion, and other tools for working with ledger files. +Compatible with both [`ledger`][ledgercli] and [`hledger`][hledger]. +See [plaintextaccounting.org][pta] for background information and other useful links. + +## Usage + +Install as you would any other VIM plugin. +There are a variety of ways depending on your plugin manager. +For example with [Pathogen](https://github.com/tpope/vim-pathogen) you would clone this repository into your configuration directory. +With [vim-plug](https://github.com/junegunn/vim-plug) and many similar ones, you would declare it in your rc file like this, then run `:PlugInstall`: + + +```vimscript +Plug 'ledger/vim-ledger' +``` + +You can also manually copy the corresponding directories into your VIM plugins directory. + +One installed this plugin will identify files ending with `.ldg`, `.ledger`, or `.journal` as ledger files automatically. +Alaternatively if you use a different extension you can add a modeline to each like this: + +```ledger +; vim: filetype=ledger +``` + +## Tips and useful commands + +* Try account-completion (as explained below) + +* `:call ledger#transaction_date_set(line('.'), 'auxiliary')` + + will set today's date as the auxiliary date of the current transaction. + You can use also `primary` or `unshift` in place of `auxiliary`. + When you pass "unshift" the old primary date will be set as the auxiliary date and today's date will be set as the new primary date. + To use a different date pass a date measured in seconds since 1st Jan 1970 as the third argument. + +* `:call ledger#transaction_state_set(line('.'), '*')` + + sets the state of the current transaction to '*'. + You can use this in custom mappings. + +* `:call ledger#transaction_state_toggle(line('.'), ' *?!')` + + will toggle through the provided transaction states. + You can map this to double-clicking for example: + + noremap <2-LeftMouse>\ + :call ledger#transaction_state_toggle(line('.'), ' *?!') + +* Align commodities at the decimal point. See `help ledger-tips`. + +* `:call ledger#entry()` + + will replace the text on the current line with a new transaction based on the replaced text. + +## Configuration + +Include the following let-statements somewhere in your `.vimrc` to modify the behaviour of the ledger filetype. + +* Number of columns that will be used to display the foldtext. + Set this when you think that the amount is too far off to the right. + + let g:ledger_maxwidth = 80 + +* String that will be used to fill the space between account name and amount in the foldtext. + Set this to get some kind of lines or visual aid. + + let g:ledger_fillstring = ' -' + +* If you want the account completion to be sorted by level of detail/depth instead of alphabetical, include the following line: + + let g:ledger_detailed_first = 1 + +* By default vim will fold ledger transactions, leaving surrounding blank lines unfolded. + You can use `g:ledger_fold_blanks` to hide blank lines following a transaction. + + let g:ledger_fold_blanks = 0 + + A value of 0 will disable folding of blank lines, 1 will allow folding of a single blank line between transactions; any larger value will enable folding undconditionally. + + Note that only lines containing no trailing spaces are considered for folding. + You can take advantage of this to disable this feature on a case-by-case basis. + +## Completion + +Omni completion is implemented for transactions descriptions and posting account names. + +### Accounts + +Account names are matched by the start of every sub-level. +When you insert an account name like this: + + Asse + +You will get a list of top-level accounts that start like this. + +Go ahead and try something like: + + As:Ban:Che + +When you have an account like this, 'Assets:Bank:Checking' should show up. + +When you want to complete on a virtual transaction, it's currently best to keep the cursor in front of the closing bracket. +Of course you can insert the closing bracket after calling the completion, too. + +## License + +Copyright 2019–2021 Caleb Maclennan +Copyright 2009–2017 Johann Klähn +Copyright 2009 Stefan Karrmann +Copyright 2005 Wolfgang Oertl + +This program is free software: +you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with this program. +If not, see . + + [hledger]: https://hledger.org/ + [ledgercli]: https://www.ledger-cli.org/ + [pta]: https://plaintextaccounting.org/ diff --git a/.vim/bundle/vim-ledger/autoload/ledger.vim b/.vim/bundle/vim-ledger/autoload/ledger.vim new file mode 100644 index 0000000..b97c799 --- /dev/null +++ b/.vim/bundle/vim-ledger/autoload/ledger.vim @@ -0,0 +1,820 @@ +scriptencoding utf-8 +" vim:ts=2:sw=2:sts=2:foldmethod=marker +function! ledger#transaction_state_toggle(lnum, ...) abort + if a:0 == 1 + let chars = a:1 + else + let chars = ' *' + endif + let trans = s:transaction.from_lnum(a:lnum) + if empty(trans) || has_key(trans, 'expr') + return + endif + + let old = has_key(trans, 'state') ? trans['state'] : ' ' + let i = stridx(chars, old) + 1 + let new = chars[i >= len(chars) ? 0 : i] + + call trans.set_state(new) + + call setline(trans['head'], trans.format_head()) +endf + +function! ledger#transaction_state_set(lnum, char) abort + " modifies or sets the state of the transaction at the cursor, + " removing the state altogether if a:char is empty + let trans = s:transaction.from_lnum(a:lnum) + if empty(trans) || has_key(trans, 'expr') + return + endif + + call trans.set_state(a:char) + + call setline(trans['head'], trans.format_head()) +endf + +function! ledger#transaction_date_set(lnum, type, ...) abort + let time = a:0 == 1 ? a:1 : localtime() + let trans = s:transaction.from_lnum(a:lnum) + if empty(trans) || has_key(trans, 'expr') + return + endif + + let formatted = strftime(g:ledger_date_format, time) + if has_key(trans, 'date') && ! empty(trans['date']) + let date = split(trans['date'], '=') + else + let date = [formatted] + endif + + if a:type =~? 'effective\|actual' + echoerr 'actual/effective arguments were replaced by primary/auxiliary' + return + endif + + if a:type ==? 'primary' + let date[0] = formatted + elseif a:type ==? 'auxiliary' + if time < 0 + " remove auxiliary date + let date = [date[0]] + else + " set auxiliary date + if len(date) >= 2 + let date[1] = formatted + else + call add(date, formatted) + endif + endif + elseif a:type ==? 'unshift' + let date = [formatted, date[0]] + endif + + let trans['date'] = join(date[0:1], '=') + + call setline(trans['head'], trans.format_head()) +endf + +function! ledger#transaction_post_state_get(lnum) abort + " safe view / position + let view = winsaveview() + call cursor(a:lnum, 0) + + let line = getline('.') + if line[0] !~# '[ \t]' + " not a post + let state = '' + else + let m = matchlist(line, '^[ \t]\+\([*?!]\)') + if len(m) > 1 + let state = m[1] + else + let state = ' ' + endif + endif + + call winrestview(view) + return state +endf + +function! ledger#transaction_post_state_toggle(lnum, ...) abort + if a:0 == 1 + let chars = a:1 + else + let chars = ' *' + endif + + let old = ledger#transaction_post_state_get(a:lnum) + if old ==# '' + " not a post, probably at the first line of transaction + call ledger#transaction_state_toggle(a:lnum, chars) + return + endif + let i = stridx(chars, old) + 1 + let new = chars[i >= len(chars) ? 0 : i] + + call ledger#transaction_post_state_set(a:lnum, new) +endf + +function! ledger#transaction_post_state_set(lnum, char) abort + let state = ledger#transaction_post_state_get(a:lnum) + if state ==# '' + " not a post, probably at the first line of transaction + call ledger#transaction_state_set(a:lnum, a:char) + return + elseif state == a:char || (state ==# ' ' && a:char ==# '') + return + endif + + let line = getline('.') + if a:char =~# '^\s*$' + let newline = substitute(line, '\V' . state . '\m[ \t]', '', '') + elseif state ==# ' ' + let m = matchlist(line, '^\([ \t]\+\)\(.*\)') + let newline = m[1] . a:char . ' ' . m[2] + else + let newline = substitute(line, '\V' . state, a:char, '') + endif + call setline(a:lnum, newline) +endf + +" == get transactions == + +function! ledger#transaction_from_lnum(lnum) abort + return s:transaction.from_lnum(a:lnum) +endf + +function! ledger#transactions(...) abort + if a:0 == 2 + let lnum = a:1 + let end = a:2 + elseif a:0 == 0 + let lnum = 1 + let end = line('$') + else + throw 'wrong number of arguments for get_transactions()' + return [] + endif + + " safe view / position + let view = winsaveview() + let fe = &foldenable + set nofoldenable + + let transactions = [] + call cursor(lnum, 0) + while lnum && lnum < end + let trans = s:transaction.from_lnum(lnum) + if ! empty(trans) + call add(transactions, trans) + call cursor(trans['tail'], 0) + endif + let lnum = search('^[~=[:digit:]]', 'cW') + endw + + " restore view / position + let &foldenable = fe + call winrestview(view) + + return transactions +endf + +" == transaction object implementation == + +let s:transaction = {} "{{{1 +function! s:transaction.new() abort dict + return copy(s:transaction) +endf + +function! s:transaction.from_lnum(lnum) abort dict "{{{2 + let [head, tail] = s:get_transaction_extents(a:lnum) + if ! head + return {} + endif + + let trans = copy(s:transaction) + let trans['head'] = head + let trans['tail'] = tail + + " split off eventual comments at the end of line + let line = split(getline(head), '\ze\s*\%(\t\| \);', 1) + if len(line) > 1 + let trans['appendix'] = join(line[1:], '') + endif + + " parse rest of line + " FIXME (minor): will not preserve spacing (see 'join(parts)') + let parts = split(line[0], '\s\+') + if parts[0] ==# '~' + let trans['expr'] = join(parts[1:]) + return trans + elseif parts[0] ==# '=' + let trans['auto'] = join(parts[1:]) + return trans + elseif parts[0] !~# '^\d' + " this case is avoided in s:get_transaction_extents(), + " but we'll check anyway. + return {} + endif + + for part in parts + if ! has_key(trans, 'date') && part =~# '^\d' + let trans['date'] = part + elseif ! has_key(trans, 'code') && part =~# '^([^)]*)$' + let trans['code'] = part[1:-2] + elseif ! has_key(trans, 'state') && part =~# '^[[:punct:]]$' + " the first character by itself is assumed to be the state of the transaction. + let trans['state'] = part + else + " everything after date/code or state belongs to the description + break + endif + call remove(parts, 0) + endfor + + let trans['description'] = join(parts) + return trans +endf "}}} + +function! s:transaction.set_state(char) abort dict "{{{2 + if a:char =~# '^\s*$' + if has_key(self, 'state') + call remove(self, 'state') + endif + else + let self['state'] = a:char + endif +endf "}}} + +function! s:transaction.parse_body(...) abort dict "{{{2 + if a:0 == 2 + let head = a:1 + let tail = a:2 + elseif a:0 == 0 + let head = self['head'] + let tail = self['tail'] + else + throw 'wrong number of arguments for parse_body()' + return [] + endif + + if ! head || tail <= head + return [] + endif + + let lnum = head + let tags = {} + let postings = [] + while lnum <= tail + let line = split(getline(lnum), '\s*\%(\t\| \);', 1) + + if line[0] =~# '^\s\+[^[:blank:];]' + " posting + let [state, rest] = matchlist(line[0], '^\s\+\([*!]\?\)\s*\(.*\)$')[1:2] + if rest =~# '\t\| ' + let [account, amount] = matchlist(rest, '^\(.\{-}\)\%(\t\| \)\s*\(.\{-}\)\s*$')[1:2] + else + let amount = '' + let account = matchstr(rest, '^\s*\zs.\{-}\ze\s*$') + endif + call add(postings, {'account': account, 'amount': amount, 'state': state}) + end + + " where are tags to be stored? + if empty(postings) + " they belong to the transaction + let tag_container = tags + else + " they belong to last posting + if ! has_key(postings[-1], 'tags') + let postings[-1]['tags'] = {} + endif + let tag_container = postings[-1]['tags'] + endif + + let comment = join(line[1:], ' ;') + if comment =~# '^\s*:' + " tags without values + for t in s:findall(comment, ':\zs[^:[:blank:]]\([^:]*[^:[:blank:]]\)\?\ze:') + let tag_container[t] = '' + endfor + elseif comment =~# '^\s*[^:[:blank:]][^:]\+:' + " tag with value + let key = matchstr(comment, '^\s*\zs[^:]\+\ze:') + if ! empty(key) + let val = matchstr(comment, ':\s*\zs.*\ze\s*$') + let tag_container[key] = val + endif + endif + let lnum += 1 + endw + return [tags, postings] +endf "}}} + +function! s:transaction.format_head() abort dict "{{{2 + if has_key(self, 'expr') + return '~ '.self['expr'] + elseif has_key(self, 'auto') + return '= '.self['auto'] + endif + + let parts = [] + if has_key(self, 'date') | call add(parts, self['date']) | endif + if has_key(self, 'state') | call add(parts, self['state']) | endif + if has_key(self, 'code') | call add(parts, '('.self['code'].')') | endif + if has_key(self, 'description') | call add(parts, self['description']) | endif + + let line = join(parts) + if has_key(self, 'appendix') | let line .= self['appendix'] | endif + + return line +endf "}}} +"}}} + +" == helper functions == + +" get a list of declared accounts in the buffer +function! ledger#declared_accounts(...) abort + if a:0 == 2 + let lnum = a:1 + let lend = a:2 + elseif a:0 == 0 + let lnum = 1 + let lend = line('$') + else + throw 'wrong number of arguments for ledger#declared_accounts()' + return [] + endif + + " save view / position + let view = winsaveview() + let fe = &foldenable + set nofoldenable + + let accounts = [] + call cursor(lnum, 0) + while 1 + let lnum = search('^account\s', 'cW', lend) + if !lnum || lnum > lend + break + endif + + " remove comments at the end and "account" at the front + let line = split(getline(lnum), '\s\+;')[0] + let line = matchlist(line, 'account\s\+\(.\+\)')[1] + + if len(line) > 1 + call add(accounts, line) + endif + + call cursor(lnum+1,0) + endw + + " restore view / position + let &foldenable = fe + call winrestview(view) + + return accounts +endf + +function! s:get_transaction_extents(lnum) abort + if ! (indent(a:lnum) || getline(a:lnum) =~# '^[~=[:digit:]]') + " only do something if lnum is in a transaction + return [0, 0] + endif + + " safe view / position + let view = winsaveview() + let fe = &foldenable + set nofoldenable + + call cursor(a:lnum, 0) + let head = search('^[~=[:digit:]]', 'bcnW') + let tail = search('^[^;[:blank:]]\S\+', 'nW') + let tail = tail > head ? tail - 1 : line('$') + + " restore view / position + let &foldenable = fe + call winrestview(view) + + return head ? [head, tail] : [0, 0] +endf + +function! ledger#find_in_tree(tree, levels) abort + if empty(a:levels) + return [] + endif + let results = [] + let currentlvl = a:levels[0] + let nextlvls = a:levels[1:] + let branches = ledger#filter_items(keys(a:tree), currentlvl) + for branch in branches + let exact = empty(nextlvls) + call add(results, [branch, exact]) + if ! empty(nextlvls) + for [result, exact] in ledger#find_in_tree(a:tree[branch], nextlvls) + call add(results, [branch.':'.result, exact]) + endfor + endif + endfor + return results +endf + +function! ledger#filter_items(list, keyword) abort + " return only those items that start with a specified keyword + return filter(copy(a:list), 'v:val =~ ''^\V'.substitute(a:keyword, '\\', '\\\\', 'g').'''') +endf + +function! s:findall(text, rx) abort + " returns all the matches in a string, + " there will be overlapping matches according to :help match() + let matches = [] + + while 1 + let m = matchstr(a:text, a:rx, 0, len(matches)+1) + if empty(m) + break + endif + + call add(matches, m) + endw + + return matches +endf + +" Move the cursor to the specified column, filling the line with spaces if necessary. +" Ensure that at least min_spaces are added, and go to the end of the line if +" the line is already too long +function! s:goto_col(pos, min_spaces) abort + exec 'normal!' '$' + let diff = max([a:min_spaces, a:pos - virtcol('.')]) + if diff > 0 | exec 'normal!' diff . 'a ' | endif +endf + +" Return character position of decimal separator (multibyte safe) +function! s:decimalpos(expr) abort + let pos = match(a:expr, '\V' . g:ledger_decimal_sep) + if pos > 0 + let pos = strchars(a:expr[:pos]) - 1 + endif + return pos +endf + +" Align the amount expression after an account name at the decimal point. +" +" This function moves the amount expression of a posting so that the decimal +" separator is aligned at the column specified by g:ledger_align_at. +" +" For example, after selecting: +" +" 2015/05/09 Some Payee +" Expenses:Other $120,23 ; Tags here +" Expenses:Something $-4,99 +" Expenses:More ($12,34 + $16,32) +" +" :'<,'>call ledger#align_commodity() produces: +" +" 2015/05/09 Some Payee +" Expenses:Other $120,23 ; Tags here +" Expenses:Something $-4,99 +" Expenses:More ($12,34 + $16,32) +" +function! ledger#align_commodity() abort + " Extract the part of the line after the account name (excluding spaces): + let l:line = getline('.') + let rhs = matchstr(l:line, '\m^\s\+[^;[:space:]].\{-}\(\t\| \)\s*\zs.*$') + if rhs !=# '' + " Remove everything after the account name (including spaces): + call setline('.', substitute(l:line, '\m^\s\+[^[:space:]].\{-}\zs\(\t\| \).*$', '', '')) + let pos = -1 + if g:ledger_align_commodity == 1 + let pos = 0 + elseif g:ledger_decimal_sep !=# '' + " Find the position of the first decimal separator: + let pos = s:decimalpos(rhs) + endif + if pos < 0 + " Find the position after the first digits + let pos = matchend(rhs, '\m\d[^[:space:]]*') + endif + " Go to the column that allows us to align the decimal separator at g:ledger_align_at: + if pos >= 0 + call s:goto_col(g:ledger_align_at - pos - 1, 2) + else + call s:goto_col(g:ledger_align_at - strdisplaywidth(rhs) - 2, 2) + endif " Append the part of the line that was previously removed: + exe 'normal! a' . rhs + endif +endf + +" Align the commodity on the entire buffer +function! ledger#align_commodity_buffer() abort + " Store the viewport position + let view = winsaveview() + + " Call ledger#align_commodity for every line + %call ledger#align_commodity() + + " Restore the viewport position + call winrestview(view) + unlet view +endf + +" Align the amount under the cursor and append/prepend the default currency. +function! ledger#align_amount_at_cursor() abort + " Select and cut text: + normal! viWd + " Find the position of the decimal separator + let pos = s:decimalpos(@") " Returns zero when the separator is the empty string + if pos <= 0 + let pos = len(@") + endif + " Paste text at the correct column and append/prepend default commodity: + if g:ledger_commodity_before + call s:goto_col(g:ledger_align_at - pos - len(g:ledger_default_commodity) - len(g:ledger_commodity_sep) - 1, 2) + exe 'normal! a' . g:ledger_default_commodity . g:ledger_commodity_sep + normal! p + else + call s:goto_col(g:ledger_align_at - pos - 1, 2) + exe 'normal! pa' . g:ledger_commodity_sep . g:ledger_default_commodity + endif +endf + +" Report generation {{{1 + +" Helper functions and variables {{{2 +" Position of report windows +let s:winpos_map = { + \ 'T': 'to new', 't': 'abo new', 'B': 'bo new', 'b': 'bel new', + \ 'L': 'to vnew', 'l': 'abo vnew', 'R': 'bo vnew', 'r': 'bel vnew' + \ } + +function! s:error_message(msg) abort + redraw " See h:echo-redraw + echohl ErrorMsg + echo "\r" + echomsg a:msg + echohl NONE +endf + +function! s:warning_message(msg) abort + redraw " See h:echo-redraw + echohl WarningMsg + echo "\r" + echomsg a:msg + echohl NONE +endf + +" Open the quickfix/location window when it is not empty, +" closes it if it is empty. +" +" Optional parameters: +" a:1 Quickfix window title. +" a:2 Message to show when the window is empty. +" +" Returns 0 if the quickfix window is empty, 1 otherwise. +function! s:quickfix_toggle(...) abort + if g:ledger_use_location_list + let l:list = 'l' + let l:open = (len(getloclist(winnr())) > 0) + else + let l:list = 'c' + let l:open = (len(getqflist()) > 0) + endif + + if l:open + execute (g:ledger_qf_vertical ? 'vert' : 'botright') l:list.'open' g:ledger_qf_size + " Set local mappings to quit the quickfix window or lose focus. + nnoremap + execute 'nnoremap q :' l:list.'close' + " Note that the following settings do not persist (e.g., when you close and re-open the quickfix window). + " See: https://superuser.com/questions/356912/how-do-i-change-the-quickix-title-status-bar-in-vim + if g:ledger_qf_hide_file + setl conceallevel=2 + setl concealcursor=nc + syntax match qfFile /^[^|]*/ transparent conceal + endif + if a:0 > 0 + let w:quickfix_title = a:1 + endif + return 1 + endif + + execute l:list.'close' + call s:warning_message((a:0 > 1) ? a:2 : 'No results') + return 0 +endf + +" Populate a quickfix/location window with data. The argument must be a String +" or a List. +function! s:quickfix_populate(data) abort + " Note that cexpr/lexpr always uses the global value of errorformat + let l:efm = &errorformat " Save global errorformat + set errorformat=%EWhile\ parsing\ file\ \"%f\"\\,\ line\ %l:,%ZError:\ %m,%-C%.%# + set errorformat+=%tarning:\ \"%f\"\\,\ line\ %l:\ %m + " Format to parse command-line errors: + set errorformat+=Error:\ %m + " Format to parse reports: + set errorformat+=%f:%l\ %m + set errorformat+=%-G%.%# + execute (g:ledger_use_location_list ? 'l' : 'c').'getexpr' 'a:data' + let &errorformat = l:efm " Restore global errorformat + return +endf + +" Build a ledger command to process the given file. +function! s:ledger_cmd(file, args) abort + let l:options = g:ledger_extra_options + if len(g:ledger_date_format) > 0 && !g:ledger_is_hledger + let l:options = join([l:options, '--date-format', g:ledger_date_format, + \ '--input-date-format', g:ledger_date_format]) + endif + return join([g:ledger_bin, l:options, '-f', shellescape(expand(a:file)), a:args]) +endf +" }}} + +function! ledger#autocomplete_and_align() abort + if pumvisible() + return "\" + endif + " Align an amount only if there is a digit immediately before the cursor and + " such digit is preceded by at least one space (the latter condition is + " necessary to avoid situations where a date starting at the first column is + " confused with a commodity to be aligned). + if match(getline('.'), '\s.*\d\%'.col('.').'c') > -1 + normal! h + call ledger#align_amount_at_cursor() + return "\A" + endif + return "\\" +endf + +" Use current line as input to ledger entry and replace with output. If there +" are errors, they are echoed instead. +function! ledger#entry() abort + let l:output = systemlist(s:ledger_cmd(g:ledger_main, join(['entry', getline('.')]))) + " Filter out warnings + let l:output = filter(l:output, "v:val !~? '^Warning: '") + " Errors may occur + if v:shell_error + echomsg join(l:output) + return + endif + " Append output so we insert instead of overwrite, then delete line + call append('.', l:output) + normal! "_dd +endfunc + +" Run an arbitrary ledger command and show the output in a new buffer. If +" there are errors, no new buffer is opened: the errors are displayed in a +" quickfix window instead. +" +" Parameters: +" file The file to be processed. +" args A string of Ledger command-line arguments. +" +" Returns: +" Ledger's output as a String. +function! ledger#report(file, args) abort + let l:output = systemlist(s:ledger_cmd(a:file, a:args)) + if v:shell_error " If there are errors, show them in a quickfix/location list. + call s:quickfix_populate(l:output) + call s:quickfix_toggle('Errors', 'Unable to parse errors') + endif + return l:output +endf + +" Open the output of a Ledger's command in a new buffer. +" +" Parameters: +" report A String containing the output of a Ledger's command. +" +" Returns: +" 1 if a new buffer is created; 0 otherwise. +function! ledger#output(report) abort + if empty(a:report) + call s:warning_message('No results') + return 0 + endif + " Open a new buffer to show Ledger's output. + execute get(s:winpos_map, g:ledger_winpos, 'bo new') + setlocal buftype=nofile bufhidden=wipe modifiable nobuflisted noswapfile nowrap + call append(0, a:report) + setlocal nomodifiable + " Set local mappings to quit window or lose focus. + nnoremap + nnoremap q @=winnr('#')c + " Add some coloring to the report + syntax match LedgerNumber /-\@1 call finish_reconciling() + augroup END + " Add refresh shortcut + execute "nnoremap :call ledger#reconcile('" + \ . l:file . "','" . a:account . "'," . string(a:target_amount) . ')' + call ledger#show_balance(l:file, a:account) + endif +endf + +function! s:finish_reconciling() abort + unlet g:ledger_target_amount + augroup reconcile + autocmd! + augroup END + augroup! reconcile +endf + +" Show the pending/cleared balance of an account. +" This function has an optional parameter: +" +" a:1 An account name +" +" If no account if given, the account in the current line is used. +function! ledger#show_balance(file, ...) abort + let l:account = a:0 > 0 && !empty(a:1) ? a:1 : matchstr(getline('.'), '\m\( \|\t\)\zs\S.\{-}\ze\( \|\t\|$\)') + if empty(l:account) + call s:error_message('No account found') + return + endif + let l:cmd = s:ledger_cmd(a:file, join([ + \ 'cleared', + \ shellescape(l:account), + \ '--empty', + \ '--collapse', + \ "--format='%(scrub(get_at(display_total, 0)))|%(scrub(get_at(display_total, 1)))|%(quantity(scrub(get_at(display_total, 1))))'", + \ (empty(g:ledger_default_commodity) ? '' : '-X ' . shellescape(g:ledger_default_commodity)) + \ ])) + let l:output = systemlist(l:cmd) + " Errors may occur, for example, when the account has multiple commodities + " and g:ledger_default_commodity is empty. + if v:shell_error + call s:quickfix_populate(l:output) + call s:quickfix_toggle('Errors', 'Unable to parse errors') + return + endif + let l:amounts = split(l:output[-1], '|') + redraw " Necessary in some cases to overwrite previous messages. See :h echo-redraw + if len(l:amounts) < 3 + call s:error_message('Could not determine balance. Did you use a valid account?') + return + endif + echo g:ledger_pending_string + echohl LedgerPending + echon l:amounts[0] + echohl NONE + echon ' ' g:ledger_cleared_string + echohl LedgerCleared + echon l:amounts[1] + echohl NONE + if exists('g:ledger_target_amount') + echon ' ' g:ledger_target_string + echohl LedgerTarget + echon printf('%.2f', (g:ledger_target_amount - str2float(l:amounts[2]))) + echohl NONE + endif +endf +" }}} diff --git a/.vim/bundle/vim-ledger/compiler/ledger.vim b/.vim/bundle/vim-ledger/compiler/ledger.vim new file mode 100644 index 0000000..5ed8e21 --- /dev/null +++ b/.vim/bundle/vim-ledger/compiler/ledger.vim @@ -0,0 +1,32 @@ +" Vim Compiler File +" Compiler: ledger +" by Johann Klähn; Use according to the terms of the GPL>=2. +" vim:ts=2:sw=2:sts=2:foldmethod=marker + +scriptencoding utf-8 + +if exists('current_compiler') || !exists('g:ledger_bin') + finish +endif + +let current_compiler = g:ledger_bin + +if exists(':CompilerSet') != 2 + command -nargs=* CompilerSet setlocal +endif + +if !exists('g:ledger_main') + let g:ledger_main = '%' +endif + +if !g:ledger_is_hledger + " Capture Ledger errors (%-C ignores all lines between "While parsing..." and "Error:..."): + CompilerSet errorformat=%EWhile\ parsing\ file\ \"%f\"\\,\ line\ %l:,%ZError:\ %m,%-C%.%# + " Capture Ledger warnings: + CompilerSet errorformat+=%tarning:\ \"%f\"\\,\ line\ %l:\ %m + " Skip all other lines: + CompilerSet errorformat+=%-G%.%# + exe 'CompilerSet makeprg='.substitute(g:ledger_bin, ' ', '\\ ', 'g').'\ -f\ ' . substitute(shellescape(expand(g:ledger_main)), ' ', '\\ ', 'g') . '\ '.substitute(g:ledger_extra_options, ' ', '\\ ', 'g').'\ source\ ' . shellescape(expand(g:ledger_main)) +else + exe 'CompilerSet makeprg=('.substitute(g:ledger_bin, ' ', '\\ ', 'g').'\ -f\ ' . substitute(shellescape(expand(g:ledger_main)), ' ', '\\ ', 'g') . '\ print\ '.substitute(g:ledger_extra_options, ' ', '\\ ', 'g').'\ >\ /dev/null)' +endif diff --git a/.vim/bundle/vim-ledger/doc/ledger.txt b/.vim/bundle/vim-ledger/doc/ledger.txt new file mode 100644 index 0000000..11dd9c2 --- /dev/null +++ b/.vim/bundle/vim-ledger/doc/ledger.txt @@ -0,0 +1,464 @@ +*ledger.txt* Plugin for the ledger filetype. + + + *ledger* *ledger-plugin* + +Contents: + + Commands............|ledger-invoking| + Source................|ledger-source| + Usage..................|ledger-usage| + Tips....................|ledger-tips| + Reports..............|ledger-reports| + Settings............|ledger-settings| + Completion........|ledger-completion| + License..............|ledger-license| + + +============================================================================== +USAGE *ledger-usage* + +Copy each file to the corresponding directory in your ~/.vim directory or +install using Pathogen. + +You can also use a modeline like this in every ledger file: + + vim:filetype=ledger + +============================================================================== +TIPS *ledger-tips* + +Tips and useful commands + +* vim-ledger can do syntax-sensitive folding when you set `foldmethod=syntax` + in the |modeline| of your ledger file. This way transactions can shrink down + to just one line. + +* Try account-completion (as explained below). If you use YouCompleteMe, you + should disable it for Ledger files. Put this in your .vimrc: + + if exists('g:ycm_filetype_blacklist') + call extend(g:ycm_filetype_blacklist, { 'ledger': 1 }) + endif + +* You may use `:make` for syntax checking. It may be convenient to define a + mapping for the following command: + + :silent make | redraw! | cwindow + + It is recommended to set the value of `g:ledger_extra_options` (see below) + as follows: + + let g:ledger_extra_options = '--pedantic --explicit --check-payees' + + to catch most potential problems in your source file. + +* Remap vim paragraph motion to move by transaction. + + In vim, the "{" and "}" keystrokes move the cursor up and down by whole + paragraphs. They can be redefined in ledger files to move by transaction + instead. Add these lines to .vimrc: + + au FileType ledger noremap { ?^\d + au FileType ledger noremap } /^\d + + The default definitions already work in ledger files that separate + transactions with blank lines. + +* `:call ledger#transaction_date_set(line('.'), "auxiliary")` + + will set today's date as the auxiliary date of the current transaction. You + can use also "primary" or "unshift" in place of "auxiliary". When you pass + "unshift" the old primary date will be set as the auxiliary date and today's + date will be set as the new primary date. + To use a different date pass a date measured in seconds since 1st Jan 1970 + as the third argument. + +* `:call ledger#transaction_state_set(line('.'), '*')` + + sets the state of the current transaction to '*'. You can use this in custom + mappings. + +* `:call ledger#transaction_state_toggle(line('.'), ' *?!')` + + will toggle through the provided transaction states. You can map this to + double-clicking for example: + + noremap <2-LeftMouse>\ + :call ledger#transaction_state_toggle(line('.'), ' *?!') + +* `:call ledger#transaction_post_state_set(line('.'), '*')` + + is similar to ledger#transaction_state_set but can set state of individual + post. + +* `:call ledger#transaction_post_state_toggle(line('.'), ' *?!')` + + is similar to ledger#transaction_state_toggle but can toggle state of + individual post. + +* `:LedgerAlign` + + moves the amount expression of a posting so that the decimal separator is + aligned at the column specified by g:ledger_align_at. If an amount has no + decimal point, the imaginary decimal point to the right of the least + significant digit will align. The command acts on a range, with the default + being the current line. + + The decimal separator can be set using `g:ledger_decimal_sep`. The default + value of `g:ledger_decimal_sep` is `'.'`. + + See below for the recommended mappings. + +* `:LedgerAlignBuffer` + + This command aligns the commodity for each posting in the entire buffer, + similar to the command `:LedgerAlign`. It differs from manually specifying + the entire buffer as the range to `:LedgerAlign` or + `ledger#align_commodity()` by saving and restoring the cursor and window + position. + + Due to performance concerns, it is not recommended to call this command + on large buffers. + +* `:call ledger#align_amount_at_cursor()` + + aligns the amount under the cursor and append/prepend the default currency. + The default currency can be set using `g:ledger_default_commodity`. Whether + the commodity should be inserted before the amount or appended to it can be + configured with the boolean flag `g:ledger_commodity_before` (the default + value is 1). A separator between the commodity and the amount may be set + using `g:ledger_commodity_sep`. + + See below for the recommended mappings. + +* `:call ledger#autocomplete_and_align()` + + when the cursor is on a number or immediately after it, invokes + `ledger#align_amount_at_cursor()` to align it and add the default currency; + otherwise, performs autocompletion. If you define the following mappings in + your `.vimrc` then you may perform both autocompletion and alignment using + the key: + + au FileType ledger inoremap \ + =ledger#autocomplete_and_align() + au FileType ledger vnoremap :LedgerAlign + + Alternatively, you may create a file `.vim/after/ftplugin/ledger.vim` + containing the following definitions: + + inoremap \ + =ledger#autocomplete_and_align() + vnoremap :LedgerAlign + + Now, you may type `asset:check123.45`, and have the + account name autocompleted and `$123.45` properly aligned (assuming your + default commodity is set to `'$'`). Or you may press in Visual mode + to align a number of transactions at once. + +* `:call ledger#entry()` + + enters a new transaction based on the text in the current line. + The text in the current line is replaced by the new transaction. + This is a front end to `ledger entry`. + +============================================================================== +REPORTS *ledger-reports* + +* `:Ledger` + + Executes an arbitrary Ledger command and sends the output to a new buffer. + For example: + + :Ledger bal ^assets ^liab + + Errors are displayed in a quickfix window. The command offers account and + payee autocompletion (by pressing ): every name starting with `@` is + autocompleted as a payee; any other name is autocompleted as an account. + + In a report buffer or in the quickfix window, you may press to switch + back to your source file, and you may press `q` to dismiss the current window. + + There are three highlight groups that are used to color the report: + + * `LedgerNumber` + + This is used to color nonnegative numbers. + + * `LedgerNegativeNumber` + + This is used to color negative numbers. + + * `LedgerImproperPerc` + + This is used to color improper percentages. + +* `:Balance` + + Show the pending and cleared balance of a given account below the status + line. For example: + + :Balance checking:savings + + The command offers payee and account autocompletion (see `:Ledger`). The + account argument is optional: if no argument is given, the first account + name found in the current line is used. + + Two highlight groups can be used to customize the colors of the line: + + * `LedgerCleared` + + This is used to color the cleared balance. + + * `LedgerPending` + + This is used to color the pending balance. + +* `:Register` + + Opens an arbitrary register report in the quickfix window. For example: + + :Register groceries -p 'this month' + + The command offers account and payee autocompletion (see |:Ledger|). You + may use the standard quickfix commands to jump from an entry in the register + report to the corresponding location in the source file. If you use GUI Vim + or if your terminal has support for the mouse (e.g., iTerm2, or even + Terminal.app in OS X 10.11 or later), you may also double-click on a line + number in the quickfix window to jump to the corresponding posting. + + It is strongly recommended that you add mappings for common quickfix + commands like `:cprev` and `:cnext`, or that you use T. Pope's Unimpaired + plugin. + +* :`Reconcile` + + Reconcile an account. For example: + + :Reconcile checking + + After you press Enter, you will be asked to enter a target amount (use + Vim's syntax for numbers, not your ledger's format). For example, for a + checking account, the target amount may be the balance of your latest bank + statement. The list of uncleared postings appears in the quickfix window. + The current balance of the account, together with the difference between the + target amount and the cleared balance, is shown at the bottom of the screen. + You may use standard quickfix commands to navigate through the postings. You + may use |ledger#transaction_state_set()| to update a transaction's state. + Every time you save your file, the balance and the difference from the + target amount are updated at the bottom of the screen. The goal, of course, + is to get such difference to zero. You may press `` to refresh the + Reconcile buffer. To finish reconciling an account, simply close the + quickfix window. + + There is a highlight group to customize the color of the difference from + target: + + * `LedgerTarget` + + This is used to color the difference between the target amount and the + cleared balance. + +============================================================================== +SETTINGS *ledger-settings* + +Configuration + +Include the following let-statements somewhere in your `.vimrc` to modify the +behaviour of the ledger filetype. + +* Path to the `ledger` executable: + + let g:ledger_bin = 'ledger' + +* Additional default options for the `ledger` executable: + + let g:ledger_extra_options = '' + +* To use a custom external system command to generate a list of account names + for completion, set the following. If g:ledger_bin is set, this will default + to running that command with arguments to parse the current file using the + accounts subcommand (works with ledger or hledger), otherwise it will parse + the postings in the current file itself. + + let g:ledger_accounts_cmd = 'your_command args' + +* To use a custom external system command to generate a list of descriptions + for completion, set the following. If g:ledger_bin is set, this will default + to running that command with arguments to parse the current file using the + descriptions subcommand (works with ledger or hledger), otherwise it will + parse the transactions in the current file itself. + + let g:ledger_descriptions_cmd = 'your_command args' + +* Number of columns that will be used to display the foldtext. Set this when + you think that the amount is too far off to the right. + + let g:ledger_maxwidth = 80 + +* String that will be used to fill the space between account name and amount in + the foldtext. Set this to get some kind of lines or visual aid. + + let g:ledger_fillstring = ' -' + +* If you want the account completion to be sorted by level of detail/depth + instead of alphabetical, include the following line: + + let g:ledger_detailed_first = 1 + +* By default vim will fold ledger transactions, leaving surrounding blank lines + unfolded. You can use 'g:ledger_fold_blanks' to hide blank lines following a + transaction. + + let g:ledger_fold_blanks = 0 + + A value of 0 will disable folding of blank lines, 1 will allow folding of a + single blank line between transactions; any larger value will enable folding + unconditionally. + + Note that only lines containing no trailing spaces are considered for + folding. You can take advantage of this to disable this feature on a + case-by-case basis. + +* Decimal separator: + + let g:ledger_decimal_sep = '.' + +* Specify at which column decimal separators should be aligned: + + let g:ledger_align_at = 60 + +* Default commodity used by `ledger#align_amount_at_cursor()`: + + let g:ledger_default_commodity = '' + +* Align on the commodity location instead of the amount + + let g:ledger_align_commodity = 1 + +* Flag that tells whether the commodity should be prepended or appended to the + amount: + + let g:ledger_commodity_before = 1 + +* String to be put between the commodity and the amount: + + let g:ledger_commodity_sep = '' + +* Flag that enable the spelling of the amount: + + let g:ledger_commodity_spell = 1 + +* Format of transaction date: + + let g:ledger_date_format = '%Y/%m/%d' + +* The file to be used to generate reports: + + let g:ledger_main = '%' + + The default is to use the current file. + +* Position of a report buffer: + + let g:ledger_winpos = 'B' + + Use `b` for bottom, `t` for top, `l` for left, `r` for right. Use uppercase letters + if you want the window to always occupy the full width or height. + +* Format of quickfix register reports (see |:Register|): + + let g:ledger_qf_register_format = \ + '%(date) %-50(payee) %-30(account) %15(amount) %15(total)\n' + + The format is specified using the standard Ledger syntax for --format. + +* Format of the reconcile quickfix window (see |:Reconcile|): + + let g:ledger_qf_reconcile_format = \ + '%(date) %-4(code) %-50(payee) %-30(account) %15(amount)\n' + + The format is specified using the standard Ledger syntax for --format. + +* Flag that tells whether a location list or a quickfix list should be used: + + let g:ledger_use_location_list = 0 + + The default is to use the quickfix window. Set to 1 to use a location list. + +* Position of the quickfix/location list: + + let g:ledger_qf_vertical = 0 + + Set to 1 to open the quickfix window in a vertical split. + +* Size of the quickfix window: + + let g:ledger_qf_size = 10 + + This is the number of lines of a horizontal quickfix window, or the number + of columns of a vertical quickfix window. + +* Flag to show or hide filenames in the quickfix window: + + let g:ledger_qf_hide_file = 1 + + Filenames in the quickfix window are hidden by default. Set this to 1 is + you want filenames to be visible. + +* Text of the output of the |:Balance| command: + + let g:ledger_cleared_string = 'Cleared: ' + let g:ledger_pending_string = 'Cleared or pending: ' + let g:ledger_target_string = 'Difference from target: ' + +============================================================================== +COMPLETION *ledger-completion* + +Omni completion is currently implemented for account names only. + +### Accounts + +Account names are matched by the start of every sub-level. When you +insert an account name like this: + + Asse + +You will get a list of top-level accounts that start like this. + +Go ahead and try something like: + + As:Ban:Che + +When you have an account like this, 'Assets:Bank:Checking' should show up. + +When you want to complete on a virtual transaction, it's currently best +to keep the cursor in front of the closing bracket. Of course you can +insert the closing bracket after calling the completion, too. + +============================================================================== +LICENSE *ledger-license* + +https://github.com/ledger/vim-ledger + +Copyright 2019 Caleb Maclennan +Copyright 2009–2017 Johann Klähn +Copyright 2009 Stefan Karrmann +Copyright 2005 Wolfgang Oertl + +This program is free software: you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the +Free Software Foundation, either version 2 of the License, or (at your +option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +Public License for more details. + +You should have received a copy of the GNU General Public License along +with this program. If not, see . + + +vim:ts=8 sw=8 noexpandtab tw=78 ft=help: + diff --git a/.vim/bundle/vim-ledger/ftdetect/ledger.vim b/.vim/bundle/vim-ledger/ftdetect/ledger.vim new file mode 100644 index 0000000..848aa5a --- /dev/null +++ b/.vim/bundle/vim-ledger/ftdetect/ledger.vim @@ -0,0 +1,2 @@ +" vint: -ProhibitAutocmdWithNoGroup +autocmd BufNewFile,BufRead *.ldg,*.ledger,*.journal setfiletype ledger diff --git a/.vim/bundle/vim-ledger/ftplugin/ledger.vim b/.vim/bundle/vim-ledger/ftplugin/ledger.vim new file mode 100644 index 0000000..6de4277 --- /dev/null +++ b/.vim/bundle/vim-ledger/ftplugin/ledger.vim @@ -0,0 +1,523 @@ +" Vim filetype plugin file +" filetype: ledger +" by Johann Klähn; Use according to the terms of the GPL>=2. +" vim:ts=2:sw=2:sts=2:foldmethod=marker + +scriptencoding utf-8 + +if exists('b:did_ftplugin') + finish +endif + +let b:did_ftplugin = 1 + +let b:undo_ftplugin = 'setlocal '. + \ 'foldtext< '. + \ 'include< comments< commentstring< omnifunc< formatprg<' + +setl foldtext=LedgerFoldText() +setl include=^!\\?include +setl comments=b:; +setl commentstring=;%s +setl omnifunc=LedgerComplete + +if !exists('g:ledger_main') + let g:ledger_main = '%' +endif + +if exists('g:ledger_no_bin') && g:ledger_no_bin + unlet! g:ledger_bin +elseif !exists('g:ledger_bin') || empty(g:ledger_bin) + if executable('hledger') + let g:ledger_bin = 'hledger' + elseif executable('ledger') + let g:ledger_bin = 'ledger' + else + unlet! g:ledger_bin + echohl WarningMsg + echomsg 'No ledger command detected, set g:ledger_bin to enable more vim-ledger features.' + echohl None + endif +elseif !executable(g:ledger_bin) + unlet! g:ledger_bin + echohl WarningMsg + echomsg 'Command set in g:ledger_bin is not executable, fix to to enable more vim-ledger features.' + echohl None +endif + +if exists('g:ledger_bin') && !exists('g:ledger_is_hledger') + let g:ledger_is_hledger = g:ledger_bin =~# '.*hledger' +endif + +if exists('g:ledger_bin') + exe 'setl formatprg='.substitute(g:ledger_bin, ' ', '\\ ', 'g').'\ -f\ -\ print' +endif + +if !exists('g:ledger_extra_options') + let g:ledger_extra_options = '' +endif + +if !exists('g:ledger_date_format') + let g:ledger_date_format = '%Y/%m/%d' +endif + +" You can set a maximal number of columns the fold text (excluding amount) +" will use by overriding g:ledger_maxwidth in your .vimrc. +" When maxwidth is zero, the amount will be displayed at the far right side +" of the screen. +if !exists('g:ledger_maxwidth') + let g:ledger_maxwidth = 0 +endif + +if !exists('g:ledger_fillstring') + let g:ledger_fillstring = ' ' +endif + +if !exists('g:ledger_accounts_cmd') + if exists('g:ledger_bin') + let g:ledger_accounts_cmd = g:ledger_bin . ' -f ' . shellescape(expand(g:ledger_main)) . ' accounts' + endif +endif + +if !exists('g:ledger_descriptions_cmd') + if exists('g:ledger_bin') + if g:ledger_is_hledger + let g:ledger_descriptions_cmd = g:ledger_bin . ' -f ' . shellescape(expand(g:ledger_main)) . ' descriptions' + else + let g:ledger_descriptions_cmd = g:ledger_bin . ' -f ' . shellescape(expand(g:ledger_main)) . ' payees' + endif + endif +endif + +if !exists('g:ledger_decimal_sep') + let g:ledger_decimal_sep = '.' +endif + +if !exists('g:ledger_align_at') + let g:ledger_align_at = 60 +endif + +if !exists('g:ledger_align_commodity') + let g:ledger_align_commodity = 0 +endif + +if !exists('g:ledger_default_commodity') + let g:ledger_default_commodity = '' +endif + +if !exists('g:ledger_commodity_before') + let g:ledger_commodity_before = 1 +endif + +if !exists('g:ledger_commodity_sep') + let g:ledger_commodity_sep = '' +endif + +" If enabled this will list the most detailed matches at the top {{{ +" of the completion list. +" For example when you have some accounts like this: +" A:Ba:Bu +" A:Bu:Bu +" and you complete on A:B:B normal behaviour may be the following +" A:B:B +" A:Bu:Bu +" A:Bu +" A:Ba:Bu +" A:Ba +" A +" with this option turned on it will be +" A:B:B +" A:Bu:Bu +" A:Ba:Bu +" A:Bu +" A:Ba +" A +" }}} +if !exists('g:ledger_detailed_first') + let g:ledger_detailed_first = 1 +endif + +" only display exact matches (no parent accounts etc.) +if !exists('g:ledger_exact_only') + let g:ledger_exact_only = 0 +endif + +" display original text / account name as completion +if !exists('g:ledger_include_original') + let g:ledger_include_original = 0 +endif + +" Settings for Ledger reports {{{ +if !exists('g:ledger_winpos') + let g:ledger_winpos = 'B' " Window position (see s:winpos_map) +endif + +if !exists('g:ledger_use_location_list') + let g:ledger_use_location_list = 0 " Use quickfix list by default +endif + +if !exists('g:ledger_cleared_string') + let g:ledger_cleared_string = 'Cleared: ' +endif + +if !exists('g:ledger_pending_string') + let g:ledger_pending_string = 'Cleared or pending: ' +endif + +if !exists('g:ledger_target_string') + let g:ledger_target_string = 'Difference from target: ' +endif +" }}} + +" Settings for the quickfix window {{{ +if !exists('g:ledger_qf_register_format') + let g:ledger_qf_register_format = + \ '%(date) %(justify(payee, 50)) '. + \ '%(justify(account, 30)) %(justify(amount, 15, -1, true)) '. + \ '%(justify(total, 15, -1, true))\n' +endif + +if !exists('g:ledger_qf_reconcile_format') + let g:ledger_qf_reconcile_format = + \ '%(date) %(justify(code, 4)) '. + \ '%(justify(payee, 50)) %(justify(account, 30)) '. + \ '%(justify(amount, 15, -1, true))\n' +endif + +if !exists('g:ledger_qf_size') + let g:ledger_qf_size = 10 " Size of the quickfix window +endif + +if !exists('g:ledger_qf_vertical') + let g:ledger_qf_vertical = 0 +endif + +if !exists('g:ledger_qf_hide_file') + let g:ledger_qf_hide_file = 1 +endif +" }}} + +if !exists('current_compiler') + compiler ledger +endif + +" Highlight groups for Ledger reports {{{ +hi link LedgerNumber Number +hi link LedgerNegativeNumber Special +hi link LedgerCleared Constant +hi link LedgerPending Todo +hi link LedgerTarget Statement +hi link LedgerImproperPerc Special +" }}} + +let s:rx_amount = '\('. + \ '\%([0-9]\+\)'. + \ '\%([,.][0-9]\+\)*'. + \ '\|'. + \ '[,.][0-9]\+'. + \ '\)'. + \ '\s*\%([[:alpha:]¢$€£]\+\s*\)\?'. + \ '\%(\s*;.*\)\?$' + +function! LedgerFoldText() "{{{1 + " find amount + let amount = '' + let lnum = v:foldstart + 1 + while lnum <= v:foldend + let line = getline(lnum) + + " Skip metadata/leading comment + if line !~# '^\%(\s\+;\|\d\)' + " No comment, look for amount... + let groups = matchlist(line, s:rx_amount) + if ! empty(groups) + let amount = groups[1] + break + endif + endif + let lnum += 1 + endwhile + + " strip whitespace at beginning and end of line + let foldtext = substitute(getline(v:foldstart), + \ '\(^\s\+\|\s\+$\)', '', 'g') + + " number of columns foldtext can use + let columns = s:get_columns() + if g:ledger_maxwidth + let columns = min([columns, g:ledger_maxwidth]) + endif + + let amount = printf(' %s ', amount) + " left cut-off if window is too narrow to display the amount + while columns < strdisplaywidth(amount) + let amount = substitute(amount, '^.', '', '') + endwhile + let columns -= strdisplaywidth(amount) + + if columns <= 0 + return amount + endif + + " right cut-off if there is not sufficient space to display the description + while columns < strdisplaywidth(foldtext) + let foldtext = substitute(foldtext, '.$', '', '') + endwhile + let columns -= strdisplaywidth(foldtext) + + if columns <= 0 + return foldtext . amount + endif + + " fill in the fillstring + if strlen(g:ledger_fillstring) + let fillstring = g:ledger_fillstring + else + let fillstring = ' ' + endif + let fillstrlen = strdisplaywidth(fillstring) + + let foldtext .= ' ' + let columns -= 1 + while columns >= fillstrlen + let foldtext .= fillstring + let columns -= fillstrlen + endwhile + + while columns < strdisplaywidth(fillstring) + let fillstring = substitute(fillstring, '.$', '', '') + endwhile + let foldtext .= fillstring + + return foldtext . amount +endfunction "}}} + +function! LedgerComplete(findstart, base) "{{{1 + if a:findstart + let lnum = line('.') + let line = getline('.') + let b:compl_context = '' + if line =~# '^\s\+[^[:blank:];]' "{{{2 (account) + " only allow completion when in or at end of account name + if matchend(line, '^\s\+\%(\S \S\|\S\)\+') >= col('.') - 1 + " the start of the first non-blank character + " (excluding virtual-transaction and 'cleared' marks) + " is the beginning of the account name + let b:compl_context = 'account' + return matchend(line, '^\s\+[*!]\?\s*[\[(]\?') + endif + elseif line =~# '^\d' "{{{2 (description) + let pre = matchend(line, '^\d\S\+\%(([^)]*)\|[*?!]\|\s\)\+') + if pre < col('.') - 1 + let b:compl_context = 'description' + return pre + endif + elseif line =~# '^$' "{{{2 (new line) + let b:compl_context = 'new' + endif "}}} + return -1 + else + if ! exists('b:compl_cache') + let b:compl_cache = s:collect_completion_data() + let b:compl_cache['#'] = changenr() + endif + let update_cache = 0 + + let results = [] + if b:compl_context ==# 'account' "{{{2 (account) + let hierarchy = split(a:base, ':') + if a:base =~# ':$' + call add(hierarchy, '') + endif + + let results = ledger#find_in_tree(b:compl_cache.accounts, hierarchy) + let exacts = filter(copy(results), 'v:val[1]') + + if len(exacts) < 1 + " update cache if we have no exact matches + let update_cache = 1 + endif + + if g:ledger_exact_only + let results = exacts + endif + + call map(results, 'v:val[0]') + + if g:ledger_detailed_first + let results = reverse(sort(results, 's:sort_accounts_by_depth')) + else + let results = sort(results) + endif + elseif b:compl_context ==# 'description' "{{{2 (description) + let results = ledger#filter_items(b:compl_cache.descriptions, a:base) + + if len(results) < 1 + let update_cache = 1 + endif + elseif b:compl_context ==# 'new' "{{{2 (new line) + return [strftime(g:ledger_date_format)] + endif "}}} + + + if g:ledger_include_original + call insert(results, a:base) + endif + + " no completion (apart from a:base) found. update cache if file has changed + if update_cache && b:compl_cache['#'] != changenr() + unlet b:compl_cache + return LedgerComplete(a:findstart, a:base) + else + unlet! b:compl_context + return results + endif + endif +endf "}}} + +" Deprecated functions {{{1 +let s:deprecated = { + \ 'LedgerToggleTransactionState': 'ledger#transaction_state_toggle', + \ 'LedgerSetTransactionState': 'ledger#transaction_state_set', + \ 'LedgerSetDate': 'ledger#transaction_date_set' + \ } + +for [s:old, s:new] in items(s:deprecated) + let s:fun = "function! {s:old}(...)\nechohl WarningMsg\necho '" . s:old . + \ ' is deprecated. Use '.s:new." instead!'\nechohl None\n" . + \ "call call('" . s:new . "', a:000)\nendf" + exe s:fun +endfor +unlet s:old s:new s:fun +" }}}1 + +function! s:collect_completion_data() "{{{1 + let transactions = ledger#transactions() + let cache = {'descriptions': [], 'tags': {}, 'accounts': {}} + if exists('g:ledger_accounts_cmd') + let accounts = systemlist(g:ledger_accounts_cmd) + else + let accounts = ledger#declared_accounts() + endif + if exists('g:ledger_descriptions_cmd') + let cache.descriptions = systemlist(g:ledger_descriptions_cmd) + endif + for xact in transactions + if !exists('g:ledger_descriptions_cmd') + " collect descriptions + if has_key(xact, 'description') && index(cache.descriptions, xact['description']) < 0 + call add(cache.descriptions, xact['description']) + endif + endif + let [t, postings] = xact.parse_body() + let tagdicts = [t] + + " collect account names + if !exists('g:ledger_accounts_cmd') + for posting in postings + if has_key(posting, 'tags') + call add(tagdicts, posting.tags) + endif + " remove virtual-transaction-marks + let name = substitute(posting.account, '\%(^\s*[\[(]\?\|[\])]\?\s*$\)', '', 'g') + if index(accounts, name) < 0 + call add(accounts, name) + endif + endfor + endif + + " collect tags + for tags in tagdicts | for [tag, val] in items(tags) + let values = get(cache.tags, tag, []) + if index(values, val) < 0 + call add(values, val) + endif + let cache.tags[tag] = values + endfor | endfor + endfor + + for account in accounts + let last = cache.accounts + for part in split(account, ':') + let last[part] = get(last, part, {}) + let last = last[part] + endfor + endfor + + return cache +endf "}}} + +" Helper functions {{{1 + +" return length of string with fix for multibyte characters +function! s:multibyte_strlen(text) "{{{2 + return strlen(substitute(a:text, '.', 'x', 'g')) +endfunction "}}} + +" get # of visible/usable columns in current window +function! s:get_columns() " {{{2 + " As long as vim doesn't provide a command natively, + " we have to compute the available columns. + " see :help todo.txt -> /Add argument to winwidth()/ + + let columns = (winwidth(0) == 0 ? 80 : winwidth(0)) - &foldcolumn + if &number + " line('w$') is the line number of the last line + let columns -= max([len(line('w$'))+1, &numberwidth]) + endif + + " are there any signs/is the sign column displayed? + redir => signs + silent execute 'sign place buffer='.string(bufnr('%')) + redir END + if signs =~# 'id=' + let columns -= 2 + endif + + return columns +endf "}}} + +function! s:sort_accounts_by_depth(name1, name2) "{{{2 + let depth1 = s:count_expression(a:name1, ':') + let depth2 = s:count_expression(a:name2, ':') + return depth1 == depth2 ? 0 : depth1 > depth2 ? 1 : -1 +endf "}}} + +function! s:count_expression(text, expression) "{{{2 + return len(split(a:text, a:expression, 1))-1 +endf "}}} + +function! s:autocomplete_account_or_payee(argLead, cmdLine, cursorPos) "{{{2 + return (a:argLead =~# '^@') ? + \ map(filter(systemlist(g:ledger_bin . ' -f ' . shellescape(expand(g:ledger_main)) . ' payees'), + \ "v:val =~? '" . strpart(a:argLead, 1) . "' && v:val !~? '^Warning: '"), '"@" . escape(v:val, " ")') + \ : + \ map(filter(systemlist(g:ledger_bin . ' -f ' . shellescape(expand(g:ledger_main)) . ' accounts'), + \ "v:val =~? '" . a:argLead . "' && v:val !~? '^Warning: '"), 'escape(v:val, " ")') +endf "}}} + +function! s:reconcile(file, account) "{{{2 + " call inputsave() + let l:amount = input('Target amount' . (empty(g:ledger_default_commodity) ? ': ' : ' (' . g:ledger_default_commodity . '): ')) + " call inputrestore() + call ledger#reconcile(a:file, a:account, str2float(l:amount)) +endf "}}} + +" Commands {{{1 +command! -buffer -nargs=? -complete=customlist,s:autocomplete_account_or_payee + \ Balance call ledger#show_balance(g:ledger_main, ) + +command! -buffer -nargs=+ -complete=customlist,s:autocomplete_account_or_payee + \ Ledger call ledger#output(ledger#report(g:ledger_main, )) + +command! -buffer -range LedgerAlign ,call ledger#align_commodity() + +command! -buffer LedgerAlignBuffer call ledger#align_commodity_buffer() + +command! -buffer -nargs=1 -complete=customlist,s:autocomplete_account_or_payee + \ Reconcile call reconcile(g:ledger_main, ) + +command! -buffer -complete=customlist,s:autocomplete_account_or_payee -nargs=* + \ Register call ledger#register(g:ledger_main, ) +" }}} + diff --git a/.vim/bundle/vim-ledger/indent/ledger.vim b/.vim/bundle/vim-ledger/indent/ledger.vim new file mode 100644 index 0000000..301515b --- /dev/null +++ b/.vim/bundle/vim-ledger/indent/ledger.vim @@ -0,0 +1,48 @@ +" Vim filetype indent file +" filetype: ledger +" by Johann Klähn; Use according to the terms of the GPL>=2. +" vim:ts=2:sw=2:sts=2:foldmethod=marker + +scriptencoding utf-8 + +if exists('b:did_indent') + finish +endif +let b:did_indent = 1 + +setl autoindent +setl indentexpr=GetLedgerIndent() + +if exists('*GetLedgerIndent') + finish +endif + +function GetLedgerIndent(...) + " You can pass in a line number when calling this function manually. + let lnum = a:0 > 0 ? a:1 : v:lnum + " If this line is empty look at (the indentation of) the last line. + " Note that inside of a transaction no blank lines are allowed. + let line = getline(lnum) + let prev = getline(lnum - 1) + + if line =~# '^\s\+\S' + " Lines that already are indented (→postings, sub-directives) keep their indentation. + return &shiftwidth + elseif line =~# '^\s*$' + " Current line is empty, try to guess its type based on the previous line. + if prev =~# '^\([[:digit:]~=]\|\s\+\S\)' + " This is very likely a posting or a sub-directive. + " While lines following the start of a transaction are automatically + " indented you will have to indent the first line following a + " pre-declaration manually. This makes it easier to type long lists of + " 'account' pre-declarations without sub-directives, for example. + return &shiftwidth + else + return 0 + endif + else + " Everything else is not indented: + " start of transactions, pre-declarations, apply/end-lines + return 0 + endif +endf diff --git a/.vim/bundle/vim-ledger/spec/align.vader b/.vim/bundle/vim-ledger/spec/align.vader new file mode 100644 index 0000000..cddb3f4 --- /dev/null +++ b/.vim/bundle/vim-ledger/spec/align.vader @@ -0,0 +1,31 @@ +Given ledger: + 1970-01-01 Person + Expenses $6.00 + Cash + +Execute (align buffer): + LedgerAlignBuffer + +Expect ledger: + 1970-01-01 Person + Expenses $6.00 + Cash + +Execute (align buffer on commodity): + let g:ledger_align_commodity = 1 + LedgerAlignBuffer + +Expect ledger: + 1970-01-01 Person + Expenses $6.00 + Cash + +Execute (change default alignment): + let g:ledger_align_commodity = 0 + let g:ledger_align_at = 40 + LedgerAlignBuffer + +Expect ledger: + 1970-01-01 Person + Expenses $6.00 + Cash diff --git a/.vim/bundle/vim-ledger/spec/commands.vader b/.vim/bundle/vim-ledger/spec/commands.vader new file mode 100644 index 0000000..6f45c33 --- /dev/null +++ b/.vim/bundle/vim-ledger/spec/commands.vader @@ -0,0 +1,8 @@ +Execute: + file spec/ledger.ledger + Ledger accounts + +Expect ledger: + Cash + Expenses + diff --git a/.vim/bundle/vim-ledger/spec/ledger.ledger b/.vim/bundle/vim-ledger/spec/ledger.ledger new file mode 100644 index 0000000..6f686e0 --- /dev/null +++ b/.vim/bundle/vim-ledger/spec/ledger.ledger @@ -0,0 +1,3 @@ +1970-01-01 Person + Expenses $6.00 + Cash diff --git a/.vim/bundle/vim-ledger/spec/reconcile.vader b/.vim/bundle/vim-ledger/spec/reconcile.vader new file mode 100644 index 0000000..eaa6749 --- /dev/null +++ b/.vim/bundle/vim-ledger/spec/reconcile.vader @@ -0,0 +1,78 @@ +Given ledger: + 2020-03-06 Buy + Expenses $10.00 + Cash + +Execute (set state): + 2 + call ledger#transaction_post_state_set(line('.'), '*') + +Expect ledger: + 2020-03-06 Buy + * Expenses $10.00 + Cash + +Execute (clear state): + 2 + call ledger#transaction_post_state_set(line('.'), '*') + call ledger#transaction_post_state_set(line('.'), '') + +Expect ledger: + 2020-03-06 Buy + Expenses $10.00 + Cash + +Execute (clear already clear state): + 2 + call ledger#transaction_post_state_set(line('.'), '') + +Expect ledger: + 2020-03-06 Buy + Expenses $10.00 + Cash + +Execute (toggle state): + 2 + call ledger#transaction_post_state_toggle(line('.'), '!* ') + +Expect ledger: + 2020-03-06 Buy + ! Expenses $10.00 + Cash + +Execute (toggle state x2): + 2 + call ledger#transaction_post_state_toggle(line('.'), '!* ') + call ledger#transaction_post_state_toggle(line('.'), '!* ') + +Expect ledger: + 2020-03-06 Buy + * Expenses $10.00 + Cash + +Execute (toggle state x3): + 2 + call ledger#transaction_post_state_toggle(line('.'), '!* ') + call ledger#transaction_post_state_toggle(line('.'), '!* ') + call ledger#transaction_post_state_toggle(line('.'), '!* ') + +Expect ledger: + 2020-03-06 Buy + Expenses $10.00 + Cash + +Execute (fallback to posting): + call ledger#transaction_post_state_toggle(line('.'), '!* ') + +Expect ledger: + 2020-03-06 ! Buy + Expenses $10.00 + Cash + +Execute (fallback to posting, clear): + call ledger#transaction_post_state_set(line('.'), ' ') + +Expect ledger: + 2020-03-06 Buy + Expenses $10.00 + Cash diff --git a/.vim/bundle/vim-ledger/syntax/ledger.vim b/.vim/bundle/vim-ledger/syntax/ledger.vim new file mode 100644 index 0000000..ad1b793 --- /dev/null +++ b/.vim/bundle/vim-ledger/syntax/ledger.vim @@ -0,0 +1,132 @@ +" Vim syntax file +" filetype: ledger +" by Johann Klähn; Use according to the terms of the GPL>=2. +" by Stefan Karrmann; Use according to the terms of the GPL>=2. +" by Wolfgang Oertl; Use according to the terms of the GPL>=2. +" vim:ts=2:sw=2:sts=2:foldmethod=marker + +scriptencoding utf-8 + +if exists('b:current_syntax') + finish +endif + +if !exists ('b:is_hledger') + if exists('g:ledger_is_hledger') + let b:is_hledger = 1 + else + let b:is_hledger = 0 + endif +endif + +" Force old regex engine (:help two-engines) +let s:oe = '\%#=1' +let s:lb1 = '\@1<=' + +let s:line_comment_chars = b:is_hledger ? ';*#' : ';|*#%' + +let s:fb = get(g:, 'ledger_fold_blanks', 0) +let s:skip = s:fb > 0 ? '\|^\n' : '' +if s:fb == 1 + let s:skip .= '\n\@!' +endif + +let s:ledgerAmount_contains = '' +if get(g:, 'ledger_commodity_spell', 0) == 0 + let s:ledgerAmount_contains .= '@NoSpell' +endif + +" for debugging +syntax clear + +" DATE[=EDATE] [*|!] [(CODE)] DESC <-- first line of transaction +" ACCOUNT AMOUNT [; NOTE] <-- posting + +exe 'syn region ledgerTransaction start=/^[[:digit:]~=]/ '. + \ 'skip=/^\s'. s:skip . '/ end=/^/ fold keepend transparent '. + \ 'contains=ledgerTransactionDate,ledgerTransactionMetadata,ledgerPosting,ledgerTransactionExpression' +syn match ledgerTransactionDate /^\d\S\+/ contained +syn match ledgerTransactionExpression /^[=~]\s\+\zs.*/ contained +syn match ledgerPosting /^\s\+[^[:blank:];].*/ + \ contained transparent contains=ledgerAccount,ledgerAmount,ledgerValueExpression,ledgerPostingMetadata +" every space in an account name shall be surrounded by two non-spaces +" every account name ends with a tab, two spaces or the end of the line +exe 'syn match ledgerAccount '. + \ '/'.s:oe.'^\s\+\zs\%(\S'.s:lb1.' \S\|\S\)\+\ze\%( \|\t\|\s*$\)/ contained' +exe 'syn match ledgerAmount '. + \ '/'.s:oe.'\S'.s:lb1.'\%( \|\t\)\s*\zs\%([^();[:space:]]\|\s\+[^();[:space:]]\)\+/ contains='.s:ledgerAmount_contains.' contained' +exe 'syn match ledgerValueExpression '. + \ '/'.s:oe.'\S'.s:lb1.'\%( \|\t\)\s*\zs(\%([^;[:space:]]\|\s\+[^;[:space:]]\)\+)/ contains='.s:ledgerAmount_contains.' contained' + +syn region ledgerPreDeclaration start=/^\(account\|payee\|commodity\|tag\)/ skip=/^\s/ end=/^/ + \ keepend transparent + \ contains=ledgerPreDeclarationType,ledgerPreDeclarationName,ledgerPreDeclarationDirective +syn match ledgerPreDeclarationType /^\(account\|payee\|commodity\|tag\)/ contained +syn match ledgerPreDeclarationName /^\S\+\s\+\zs.*/ contained +syn match ledgerPreDeclarationDirective /^\s\+\zs\S\+/ contained + +syn match ledgerDirective + \ /^\%(alias\|assert\|bucket\|capture\|check\|define\|expr\|fixed\|include\|year\)\s/ +syn match ledgerOneCharDirective /^\%(P\|A\|Y\|N\|D\|C\)\s/ + +syn region ledgerBlockComment start=/^comment/ end=/^end comment/ +syn region ledgerBlockTest start=/^test/ end=/^end test/ +exe 'syn match ledgerComment /^['.s:line_comment_chars.'].*$/' +" comments at eol must be preceded by at least 2 spaces / 1 tab +if b:is_hledger + syn region ledgerTransactionMetadata start=/;/ end=/^/ + \ keepend contained contains=ledgerTags,ledgerValueTag,ledgerTypedTag +else + syn region ledgerTransactionMetadata start=/\%(\s\s\|\t\|^\s\+\);/ end=/^/ + \ keepend contained contains=ledgerTags,ledgerValueTag,ledgerTypedTag +endif +syn region ledgerPostingMetadata start=/;/ end=/^/ + \ keepend contained contains=ledgerTags,ledgerValueTag,ledgerTypedTag +exe 'syn match ledgerTags '. + \ '/'.s:oe.'\%(\%(;\s*\|^tag\s\+\)\)\@<='. + \ ':[^:[:space:]][^:]*\%(::\?[^:[:space:]][^:]*\)*:\s*$/ '. + \ 'contained contains=ledgerTag' +syn match ledgerTag /:\zs[^:]\+\ze:/ contained +exe 'syn match ledgerValueTag '. + \ '/'.s:oe.'\%(\%(;\|^tag\)[^:]\+\)\@<=[^:]\+:\ze.\+$/ contained' +exe 'syn match ledgerTypedTag '. + \ '/'.s:oe.'\%(\%(;\|^tag\)[^:]\+\)\@<=[^:]\+::\ze.\+$/ contained' + +syn region ledgerApply + \ matchgroup=ledgerStartApply start=/^apply\>/ + \ matchgroup=ledgerEndApply end=/^end\s\+apply\>/ + \ contains=ledgerApplyHead,ledgerApply,ledgerTransaction,ledgerComment +exe 'syn match ledgerApplyHead '. + \ '/'.s:oe.'\%(^apply\s\+\)\@<=\S.*$/ contained' + +syntax keyword ledgerTodo FIXME TODO + \ contained containedin=ledgerComment,ledgerTransaction,ledgerTransactionMetadata,ledgerPostingMetadata + +highlight default link ledgerComment Comment +highlight default link ledgerBlockComment Comment +highlight default link ledgerBlockTest Comment +highlight default link ledgerTransactionDate Constant +highlight default link ledgerTransactionExpression Statement +highlight default link ledgerTransactionMetadata Tag +highlight default link ledgerPostingMetadata Tag +highlight default link ledgerTypedTag Keyword +highlight default link ledgerValueTag Type +highlight default link ledgerTag Type +highlight default link ledgerStartApply Tag +highlight default link ledgerEndApply Tag +highlight default link ledgerApplyHead Type +highlight default link ledgerAccount Identifier +highlight default link ledgerAmount Number +highlight default link ledgerValueExpression Function +highlight default link ledgerPreDeclarationType Type +highlight default link ledgerPreDeclarationName Identifier +highlight default link ledgerPreDeclarationDirective Type +highlight default link ledgerDirective Type +highlight default link ledgerOneCharDirective Type +highlight default link ledgerTodo Todo + +" syncinc is easy: search for the first transaction. +syn sync clear +syn sync match ledgerSync grouphere ledgerTransaction "^[[:digit:]~=]" + +let b:current_syntax = b:is_hledger ? 'hledger' : 'ledger' diff --git a/.vim/bundle/vim-luakit/ftdetect/luakit.vim b/.vim/bundle/vim-luakit/ftdetect/luakit.vim new file mode 100644 index 0000000..425a955 --- /dev/null +++ b/.vim/bundle/vim-luakit/ftdetect/luakit.vim @@ -0,0 +1,2 @@ +au BufNewFile,BufRead */luakit/**/*.lua set ft=luakit +au BufNewFile,BufRead */luakit/*.lua set ft=luakit diff --git a/.vim/bundle/vim-luakit/ftplugin/luakit.vim b/.vim/bundle/vim-luakit/ftplugin/luakit.vim new file mode 100644 index 0000000..50096d6 --- /dev/null +++ b/.vim/bundle/vim-luakit/ftplugin/luakit.vim @@ -0,0 +1,69 @@ +" Vim filetype plugin +" Language: luakit configuration +" Maintainer: Gregor Uhlenheuer +" Last Change: Tue 14 Sep 2010 01:18:20 PM CEST + +" Custom configuration: +" +" Set 'g:luakit_prefix' to the path prefix where the system-wide +" luakit configuration files are installed to. The default is set to +" something like '/etc/xdg' or '/usr/share/xdg'. +" If this variable is not defined the path is tried to determine via the +" environment variable $XDG_CONFIG_DIRS +" +" let g:luakit_prefix = '/etc/xdg' +" +" Defined mappings (buffer-local): +" +" ld Diff current config file with its system-wide counterpart + +if exists('b:did_luakit') + finish +endif +let b:did_luakit = 1 + +if !exists('g:luakit_prefix') + let g:luakit_prefix = '/etc/xdg' +endif + +function! s:GetFile() + let fcomponents = [] + if $XDG_CONFIG_DIRS != '' + call add(fcomponents, $XDG_CONFIG_DIRS) + else + call add(fcomponents, g:luakit_prefix) + endif + call add(fcomponents, "luakit") + call add(fcomponents, expand('%:t')) + let config_file = join(fcomponents, '/') + if filereadable(config_file) + return config_file + endif + return '' +endfunction + +if !exists('*CompareLuakitFile') + function! CompareLuakitFile() + let file = GetFile() + if file != '' + if file != expand('%:p') + exe 'vert diffsplit' file + wincmd p + else + echohl WarningMsg + echom 'You cannot compare the file with itself' + echohl None + endif + else + echohl WarningMsg + echom 'Could not find system-wide luakit '''.expand('%:t').''' file' + echom 'Please define ''g:luakit_prefix''' + echohl None + endif + endfunction +endif + +com! -buffer LuakitDiff call CompareLuakitFile() +nmap ld :LuakitDiff + +runtime! ftplugin/lua.vim diff --git a/.vim/bundle/vim-luakit/indent/luakit.vim b/.vim/bundle/vim-luakit/indent/luakit.vim new file mode 100644 index 0000000..8efaaf3 --- /dev/null +++ b/.vim/bundle/vim-luakit/indent/luakit.vim @@ -0,0 +1,10 @@ +" Vim indent file +" Language: luakit configuration +" Maintainer: Gregor Uhlenheuer +" Last Change: Fri 27 Aug 2010 09:49:20 PM CEST + +if exists('b:did_indent') + finish +endif + +runtime! indent/lua.vim diff --git a/.vim/bundle/vim-luakit/syntax/luakit.vim b/.vim/bundle/vim-luakit/syntax/luakit.vim new file mode 100644 index 0000000..babc3e1 --- /dev/null +++ b/.vim/bundle/vim-luakit/syntax/luakit.vim @@ -0,0 +1,33 @@ +" Vim syntax file +" Language: luakit configuration +" Maintainer: Gregor Uhlenheuer +" Last Change: Fri 27 Aug 2010 09:46:46 PM CEST + +if exists('b:current_syntax') + finish +endif + +runtime! syntax/lua.vim + +" Javascript between [=[ & ]=] regions +unlet b:current_syntax +syntax include @JAVASCRIPT syntax/javascript.vim +try | syntax include @JAVASCRIPT after/syntax/javascript.vim | catch | endtry +syntax region jsBLOCK matchgroup=jsBLOCKMATCH start=/\[=\[/ end=/\]=\]/ contains=@JAVASCRIPT +hi link jsBLOCKMATCH SpecialComment + +" HTML between [==[ & ]==] regions +unlet b:current_syntax +syntax include @HTML syntax/html.vim +try | syntax include @HTML after/syntax/html.vim | catch | endtry +syntax region htmlBLOCK matchgroup=htmlBLOCKMATCH start=/\[==\[/ end=/\]==\]/ contains=@HTML +hi link htmlBLOCKMATCH SpecialComment + +" CSS between [===[ & ]===] regions +unlet b:current_syntax +syntax include @CSS syntax/css.vim +try | syntax include @CSS after/syntax/css.vim | catch | endtry +syntax region cssBLOCK matchgroup=cssBLOCKMATCH start=/\[===\[/ end=/\]===\]/ contains=@CSS +hi link cssBLOCKMATCH SpecialComment + +let b:current_syntax = 'luakit' diff --git a/.vim/bundle/vim-mail/ftplugin/mail.vim b/.vim/bundle/vim-mail/ftplugin/mail.vim new file mode 100644 index 0000000..0f92422 --- /dev/null +++ b/.vim/bundle/vim-mail/ftplugin/mail.vim @@ -0,0 +1,38 @@ +setl wrap " softwrap the text +setl linebreak " don't break in the middle of the word +setl nolist " list disables linebreak +setl textwidth=72 +setl formatprg=par\ -B+.,\\-\\!\\?\\\"\\\'\\*\\<\ -w72qie +setl enc=utf-8 +setl nojs +setl nosmartindent +setl spell + +setl comments=n:> +setl formatoptions= +setl fo+=r " Insert the current comment leader after hitting in Insert mode. +setl fo+=o " Insert the current comment leader after hitting 'o' or 'O' in Normal mode. +setl fo+=q " Allow formatting of comments with "gq". +setl fo+=w " Trailing white space indicates a paragraph continues in the next line. +setl fo+=b " Like 'v', but only auto-wrap if you enter a blank at or before the wrap margin. +setl fo+=j " Where it makes sense, remove a comment leader when joining lines. + +match ErrorMsg '\s\s\+$' + +syn match quote0 "^>" +syn match quote1 "^> *>" +syn match quote2 "^> *> *>" +syn match quote3 "^> *> *> *>" +syn match quote4 "^> *> *> *> *>" +syn match quote5 "^> *> *> *> *> *>" +syn match quote6 "^> *> *> *> *> *> *>" +syn match quote7 "^> *> *> *> *> *> *> *>" + +hi quote0 ctermfg=magenta +hi quote1 ctermfg=cyan +hi quote2 ctermfg=blue +hi quote3 ctermfg=yellow +hi quote4 ctermfg=magenta +hi quote5 ctermfg=cyan +hi quote6 ctermfg=blue +hi quote7 ctermfg=yellow diff --git a/.vim/bundle/vim-snipmate/after/plugin/snipMate.vim b/.vim/bundle/vim-snipmate/after/plugin/snipMate.vim new file mode 100644 index 0000000..03e79ae --- /dev/null +++ b/.vim/bundle/vim-snipmate/after/plugin/snipMate.vim @@ -0,0 +1,35 @@ +" These are the mappings for snipMate.vim. Putting it here ensures that it +" will be mapped after other plugins such as supertab.vim. +if !exists('loaded_snips') || exists('s:did_snips_mappings') + finish +endif +let s:did_snips_mappings = 1 + +ino =TriggerSnippet() +snor i=TriggerSnippet() +ino =BackwardsSnippet() +snor i=BackwardsSnippet() +ino =ShowAvailableSnips() + +" The default mappings for these are annoying & sometimes break snipMate. +" You can change them back if you want, I've put them here for convenience. +snor b +snor a +snor bi +snor ' b' +snor ` b` +snor % b% +snor U bU +snor ^ b^ +snor \ b\ +snor b + +" By default load snippets in snippets_dir +if empty(snippets_dir) + finish +endif + +call GetSnippets(snippets_dir, '_') " Get global snippets + +au FileType * if &ft != 'help' | call GetSnippets(snippets_dir, &ft) | endif +" vim:noet:sw=4:ts=4:ft=vim diff --git a/.vim/bundle/vim-snipmate/autoload/snipMate.vim b/.vim/bundle/vim-snipmate/autoload/snipMate.vim new file mode 100644 index 0000000..dcd28f6 --- /dev/null +++ b/.vim/bundle/vim-snipmate/autoload/snipMate.vim @@ -0,0 +1,433 @@ +fun! Filename(...) + let filename = expand('%:t:r') + if filename == '' | return a:0 == 2 ? a:2 : '' | endif + return !a:0 || a:1 == '' ? filename : substitute(a:1, '$1', filename, 'g') +endf + +fun s:RemoveSnippet() + unl! g:snipPos s:curPos s:snipLen s:endCol s:endLine s:prevLen + \ s:lastBuf s:oldWord + if exists('s:update') + unl s:startCol s:origWordLen s:update + if exists('s:oldVars') | unl s:oldVars s:oldEndCol | endif + endif + aug! snipMateAutocmds +endf + +fun snipMate#expandSnip(snip, col) + let lnum = line('.') | let col = a:col + + let snippet = s:ProcessSnippet(a:snip) + " Avoid error if eval evaluates to nothing + if snippet == '' | return '' | endif + + " Expand snippet onto current position with the tab stops removed + let snipLines = split(substitute(snippet, '$\d\+\|${\d\+.\{-}}', '', 'g'), "\n", 1) + + let line = getline(lnum) + let afterCursor = strpart(line, col - 1) + " Keep text after the cursor + if afterCursor != "\t" && afterCursor != ' ' + let line = strpart(line, 0, col - 1) + let snipLines[-1] .= afterCursor + else + let afterCursor = '' + " For some reason the cursor needs to move one right after this + if line != '' && col == 1 && &ve != 'all' && &ve != 'onemore' + let col += 1 + endif + endif + + call setline(lnum, line.snipLines[0]) + + " Autoindent snippet according to previous indentation + let indent = matchend(line, '^.\{-}\ze\(\S\|$\)') + 1 + call append(lnum, map(snipLines[1:], "'".strpart(line, 0, indent - 1)."'.v:val")) + + " Open any folds snippet expands into + if &fen | sil! exe lnum.','.(lnum + len(snipLines) - 1).'foldopen' | endif + + let [g:snipPos, s:snipLen] = s:BuildTabStops(snippet, lnum, col - indent, indent) + + if s:snipLen + aug snipMateAutocmds + au CursorMovedI * call s:UpdateChangedSnip(0) + au InsertEnter * call s:UpdateChangedSnip(1) + aug END + let s:lastBuf = bufnr(0) " Only expand snippet while in current buffer + let s:curPos = 0 + let s:endCol = g:snipPos[s:curPos][1] + let s:endLine = g:snipPos[s:curPos][0] + + call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1]) + let s:prevLen = [line('$'), col('$')] + if g:snipPos[s:curPos][2] != -1 | return s:SelectWord() | endif + else + unl g:snipPos s:snipLen + " Place cursor at end of snippet if no tab stop is given + let newlines = len(snipLines) - 1 + call cursor(lnum + newlines, indent + len(snipLines[-1]) - len(afterCursor) + \ + (newlines ? 0: col - 1)) + endif + return '' +endf + +" Prepare snippet to be processed by s:BuildTabStops +fun s:ProcessSnippet(snip) + let snippet = a:snip + " Evaluate eval (`...`) expressions. + " Using a loop here instead of a regex fixes a bug with nested "\=". + if stridx(snippet, '`') != -1 + while match(snippet, '`.\{-}`') != -1 + let snippet = substitute(snippet, '`.\{-}`', + \ substitute(eval(matchstr(snippet, '`\zs.\{-}\ze`')), + \ "\n\\%$", '', ''), '') + endw + let snippet = substitute(snippet, "\r", "\n", 'g') + endif + + " Place all text after a colon in a tab stop after the tab stop + " (e.g. "${#:foo}" becomes "${:foo}foo"). + " This helps tell the position of the tab stops later. + let snippet = substitute(snippet, '${\d\+:\(.\{-}\)}', '&\1', 'g') + + " Update the a:snip so that all the $# become the text after + " the colon in their associated ${#}. + " (e.g. "${1:foo}" turns all "$1"'s into "foo") + let i = 1 + while stridx(snippet, '${'.i) != -1 + let s = matchstr(snippet, '${'.i.':\zs.\{-}\ze}') + if s != '' + let snippet = substitute(snippet, '$'.i, s.'&', 'g') + endif + let i += 1 + endw + + if &et " Expand tabs to spaces if 'expandtab' is set. + return substitute(snippet, '\t', repeat(' ', &sts ? &sts : &sw), 'g') + endif + return snippet +endf + +" Counts occurences of haystack in needle +fun s:Count(haystack, needle) + let counter = 0 + let index = stridx(a:haystack, a:needle) + while index != -1 + let index = stridx(a:haystack, a:needle, index+1) + let counter += 1 + endw + return counter +endf + +" Builds a list of a list of each tab stop in the snippet containing: +" 1.) The tab stop's line number. +" 2.) The tab stop's column number +" (by getting the length of the string between the last "\n" and the +" tab stop). +" 3.) The length of the text after the colon for the current tab stop +" (e.g. "${1:foo}" would return 3). If there is no text, -1 is returned. +" 4.) If the "${#:}" construct is given, another list containing all +" the matches of "$#", to be replaced with the placeholder. This list is +" composed the same way as the parent; the first item is the line number, +" and the second is the column. +fun s:BuildTabStops(snip, lnum, col, indent) + let snipPos = [] + let i = 1 + let withoutVars = substitute(a:snip, '$\d\+', '', 'g') + while stridx(a:snip, '${'.i) != -1 + let beforeTabStop = matchstr(withoutVars, '^.*\ze${'.i.'\D') + let withoutOthers = substitute(withoutVars, '${\('.i.'\D\)\@!\d\+.\{-}}', '', 'g') + + let j = i - 1 + call add(snipPos, [0, 0, -1]) + let snipPos[j][0] = a:lnum + s:Count(beforeTabStop, "\n") + let snipPos[j][1] = a:indent + len(matchstr(withoutOthers, '.*\(\n\|^\)\zs.*\ze${'.i.'\D')) + if snipPos[j][0] == a:lnum | let snipPos[j][1] += a:col | endif + + " Get all $# matches in another list, if ${#:name} is given + if stridx(withoutVars, '${'.i.':') != -1 + let snipPos[j][2] = len(matchstr(withoutVars, '${'.i.':\zs.\{-}\ze}')) + let dots = repeat('.', snipPos[j][2]) + call add(snipPos[j], []) + let withoutOthers = substitute(a:snip, '${\d\+.\{-}}\|$'.i.'\@!\d\+', '', 'g') + while match(withoutOthers, '$'.i.'\(\D\|$\)') != -1 + let beforeMark = matchstr(withoutOthers, '^.\{-}\ze'.dots.'$'.i.'\(\D\|$\)') + call add(snipPos[j][3], [0, 0]) + let snipPos[j][3][-1][0] = a:lnum + s:Count(beforeMark, "\n") + let snipPos[j][3][-1][1] = a:indent + (snipPos[j][3][-1][0] > a:lnum + \ ? len(matchstr(beforeMark, '.*\n\zs.*')) + \ : a:col + len(beforeMark)) + let withoutOthers = substitute(withoutOthers, '$'.i.'\ze\(\D\|$\)', '', '') + endw + endif + let i += 1 + endw + return [snipPos, i - 1] +endf + +fun snipMate#jumpTabStop(backwards) + let leftPlaceholder = exists('s:origWordLen') + \ && s:origWordLen != g:snipPos[s:curPos][2] + if leftPlaceholder && exists('s:oldEndCol') + let startPlaceholder = s:oldEndCol + 1 + endif + + if exists('s:update') + call s:UpdatePlaceholderTabStops() + else + call s:UpdateTabStops() + endif + + " Don't reselect placeholder if it has been modified + if leftPlaceholder && g:snipPos[s:curPos][2] != -1 + if exists('startPlaceholder') + let g:snipPos[s:curPos][1] = startPlaceholder + else + let g:snipPos[s:curPos][1] = col('.') + let g:snipPos[s:curPos][2] = 0 + endif + endif + + let s:curPos += a:backwards ? -1 : 1 + " Loop over the snippet when going backwards from the beginning + if s:curPos < 0 | let s:curPos = s:snipLen - 1 | endif + + if s:curPos == s:snipLen + let sMode = s:endCol == g:snipPos[s:curPos-1][1]+g:snipPos[s:curPos-1][2] + call s:RemoveSnippet() + return sMode ? "\" : TriggerSnippet() + endif + + call cursor(g:snipPos[s:curPos][0], g:snipPos[s:curPos][1]) + + let s:endLine = g:snipPos[s:curPos][0] + let s:endCol = g:snipPos[s:curPos][1] + let s:prevLen = [line('$'), col('$')] + + return g:snipPos[s:curPos][2] == -1 ? '' : s:SelectWord() +endf + +fun s:UpdatePlaceholderTabStops() + let changeLen = s:origWordLen - g:snipPos[s:curPos][2] + unl s:startCol s:origWordLen s:update + if !exists('s:oldVars') | return | endif + " Update tab stops in snippet if text has been added via "$#" + " (e.g., in "${1:foo}bar$1${2}"). + if changeLen != 0 + let curLine = line('.') + + for pos in g:snipPos + if pos == g:snipPos[s:curPos] | continue | endif + let changed = pos[0] == curLine && pos[1] > s:oldEndCol + let changedVars = 0 + let endPlaceholder = pos[2] - 1 + pos[1] + " Subtract changeLen from each tab stop that was after any of + " the current tab stop's placeholders. + for [lnum, col] in s:oldVars + if lnum > pos[0] | break | endif + if pos[0] == lnum + if pos[1] > col || (pos[2] == -1 && pos[1] == col) + let changed += 1 + elseif col < endPlaceholder + let changedVars += 1 + endif + endif + endfor + let pos[1] -= changeLen * changed + let pos[2] -= changeLen * changedVars " Parse variables within placeholders + " e.g., "${1:foo} ${2:$1bar}" + + if pos[2] == -1 | continue | endif + " Do the same to any placeholders in the other tab stops. + for nPos in pos[3] + let changed = nPos[0] == curLine && nPos[1] > s:oldEndCol + for [lnum, col] in s:oldVars + if lnum > nPos[0] | break | endif + if nPos[0] == lnum && nPos[1] > col + let changed += 1 + endif + endfor + let nPos[1] -= changeLen * changed + endfor + endfor + endif + unl s:endCol s:oldVars s:oldEndCol +endf + +fun s:UpdateTabStops() + let changeLine = s:endLine - g:snipPos[s:curPos][0] + let changeCol = s:endCol - g:snipPos[s:curPos][1] + if exists('s:origWordLen') + let changeCol -= s:origWordLen + unl s:origWordLen + endif + let lnum = g:snipPos[s:curPos][0] + let col = g:snipPos[s:curPos][1] + " Update the line number of all proceeding tab stops if has + " been inserted. + if changeLine != 0 + let changeLine -= 1 + for pos in g:snipPos + if pos[0] >= lnum + if pos[0] == lnum | let pos[1] += changeCol | endif + let pos[0] += changeLine + endif + if pos[2] == -1 | continue | endif + for nPos in pos[3] + if nPos[0] >= lnum + if nPos[0] == lnum | let nPos[1] += changeCol | endif + let nPos[0] += changeLine + endif + endfor + endfor + elseif changeCol != 0 + " Update the column of all proceeding tab stops if text has + " been inserted/deleted in the current line. + for pos in g:snipPos + if pos[1] >= col && pos[0] == lnum + let pos[1] += changeCol + endif + if pos[2] == -1 | continue | endif + for nPos in pos[3] + if nPos[0] > lnum | break | endif + if nPos[0] == lnum && nPos[1] >= col + let nPos[1] += changeCol + endif + endfor + endfor + endif +endf + +fun s:SelectWord() + let s:origWordLen = g:snipPos[s:curPos][2] + let s:oldWord = strpart(getline('.'), g:snipPos[s:curPos][1] - 1, + \ s:origWordLen) + let s:prevLen[1] -= s:origWordLen + if !empty(g:snipPos[s:curPos][3]) + let s:update = 1 + let s:endCol = -1 + let s:startCol = g:snipPos[s:curPos][1] - 1 + endif + if !s:origWordLen | return '' | endif + let l = col('.') != 1 ? 'l' : '' + if &sel == 'exclusive' + return "\".l.'v'.s:origWordLen."l\" + endif + return s:origWordLen == 1 ? "\".l.'gh' + \ : "\".l.'v'.(s:origWordLen - 1)."l\" +endf + +" This updates the snippet as you type when text needs to be inserted +" into multiple places (e.g. in "${1:default text}foo$1bar$1", +" "default text" would be highlighted, and if the user types something, +" UpdateChangedSnip() would be called so that the text after "foo" & "bar" +" are updated accordingly) +" +" It also automatically quits the snippet if the cursor is moved out of it +" while in insert mode. +fun s:UpdateChangedSnip(entering) + if exists('g:snipPos') && bufnr(0) != s:lastBuf + call s:RemoveSnippet() + elseif exists('s:update') " If modifying a placeholder + if !exists('s:oldVars') && s:curPos + 1 < s:snipLen + " Save the old snippet & word length before it's updated + " s:startCol must be saved too, in case text is added + " before the snippet (e.g. in "foo$1${2}bar${1:foo}"). + let s:oldEndCol = s:startCol + let s:oldVars = deepcopy(g:snipPos[s:curPos][3]) + endif + let col = col('.') - 1 + + if s:endCol != -1 + let changeLen = col('$') - s:prevLen[1] + let s:endCol += changeLen + else " When being updated the first time, after leaving select mode + if a:entering | return | endif + let s:endCol = col - 1 + endif + + " If the cursor moves outside the snippet, quit it + if line('.') != g:snipPos[s:curPos][0] || col < s:startCol || + \ col - 1 > s:endCol + unl! s:startCol s:origWordLen s:oldVars s:update + return s:RemoveSnippet() + endif + + call s:UpdateVars() + let s:prevLen[1] = col('$') + elseif exists('g:snipPos') + if !a:entering && g:snipPos[s:curPos][2] != -1 + let g:snipPos[s:curPos][2] = -2 + endif + + let col = col('.') + let lnum = line('.') + let changeLine = line('$') - s:prevLen[0] + + if lnum == s:endLine + let s:endCol += col('$') - s:prevLen[1] + let s:prevLen = [line('$'), col('$')] + endif + if changeLine != 0 + let s:endLine += changeLine + let s:endCol = col + endif + + " Delete snippet if cursor moves out of it in insert mode + if (lnum == s:endLine && (col > s:endCol || col < g:snipPos[s:curPos][1])) + \ || lnum > s:endLine || lnum < g:snipPos[s:curPos][0] + call s:RemoveSnippet() + endif + endif +endf + +" This updates the variables in a snippet when a placeholder has been edited. +" (e.g., each "$1" in "${1:foo} $1bar $1bar") +fun s:UpdateVars() + let newWordLen = s:endCol - s:startCol + 1 + let newWord = strpart(getline('.'), s:startCol, newWordLen) + if newWord == s:oldWord || empty(g:snipPos[s:curPos][3]) + return + endif + + let changeLen = g:snipPos[s:curPos][2] - newWordLen + let curLine = line('.') + let startCol = col('.') + let oldStartSnip = s:startCol + let updateTabStops = changeLen != 0 + let i = 0 + + for [lnum, col] in g:snipPos[s:curPos][3] + if updateTabStops + let start = s:startCol + if lnum == curLine && col <= start + let s:startCol -= changeLen + let s:endCol -= changeLen + endif + for nPos in g:snipPos[s:curPos][3][(i):] + " This list is in ascending order, so quit if we've gone too far. + if nPos[0] > lnum | break | endif + if nPos[0] == lnum && nPos[1] > col + let nPos[1] -= changeLen + endif + endfor + if lnum == curLine && col > start + let col -= changeLen + let g:snipPos[s:curPos][3][i][1] = col + endif + let i += 1 + endif + + " "Very nomagic" is used here to allow special characters. + call setline(lnum, substitute(getline(lnum), '\%'.col.'c\V'. + \ escape(s:oldWord, '\'), escape(newWord, '\&'), '')) + endfor + if oldStartSnip != s:startCol + call cursor(0, startCol + s:startCol - oldStartSnip) + endif + + let s:oldWord = newWord + let g:snipPos[s:curPos][2] = newWordLen +endf +" vim:noet:sw=4:ts=4:ft=vim diff --git a/.vim/bundle/vim-snipmate/doc/snipMate.txt b/.vim/bundle/vim-snipmate/doc/snipMate.txt new file mode 100644 index 0000000..704d44a --- /dev/null +++ b/.vim/bundle/vim-snipmate/doc/snipMate.txt @@ -0,0 +1,286 @@ +*snipMate.txt* Plugin for using TextMate-style snippets in Vim. + +snipMate *snippet* *snippets* *snipMate* +Last Change: July 13, 2009 + +|snipMate-description| Description +|snipMate-syntax| Snippet syntax +|snipMate-usage| Usage +|snipMate-settings| Settings +|snipMate-features| Features +|snipMate-disadvantages| Disadvantages to TextMate +|snipMate-contact| Contact + +For Vim version 7.0 or later. +This plugin only works if 'compatible' is not set. +{Vi does not have any of these features.} + +============================================================================== +DESCRIPTION *snipMate-description* + +snipMate.vim implements some of TextMate's snippets features in Vim. A +snippet is a piece of often-typed text that you can insert into your +document using a trigger word followed by a . + +For instance, in a C file using the default installation of snipMate.vim, if +you type "for" in insert mode, it will expand a typical for loop in C: > + + for (i = 0; i < count; i++) { + + } + + +To go to the next item in the loop, simply over to it; if there is +repeated code, such as the "i" variable in this example, you can simply +start typing once it's highlighted and all the matches specified in the +snippet will be updated. To go in reverse, use . + +============================================================================== +SYNTAX *snippet-syntax* + +Snippets can be defined in two ways. They can be in their own file, named +after their trigger in 'snippets//.snippet', or they can be +defined together in a 'snippets/.snippets' file. Note that dotted +'filetype' syntax is supported -- e.g., you can use > + + :set ft=html.eruby + +to activate snippets for both HTML and eRuby for the current file. + +The syntax for snippets in *.snippets files is the following: > + + snippet trigger + expanded text + more expanded text + +Note that the first hard tab after the snippet trigger is required, and not +expanded in the actual snippet. The syntax for *.snippet files is the same, +only without the trigger declaration and starting indentation. + +Also note that snippets must be defined using hard tabs. They can be expanded +to spaces later if desired (see |snipMate-indenting|). + +"#" is used as a line-comment character in *.snippets files; however, they can +only be used outside of a snippet declaration. E.g.: > + + # this is a correct comment + snippet trigger + expanded text + snippet another_trigger + # this isn't a comment! + expanded text +< +This should hopefully be obvious with the included syntax highlighting. + + *snipMate-${#}* +Tab stops ~ + +By default, the cursor is placed at the end of a snippet. To specify where the +cursor is to be placed next, use "${#}", where the # is the number of the tab +stop. E.g., to place the cursor first on the id of a
tag, and then allow +the user to press to go to the middle of it: + > + snippet div +
+ ${2} +
+< + *snipMate-placeholders* *snipMate-${#:}* *snipMate-$#* +Placeholders ~ + +Placeholder text can be supplied using "${#:text}", where # is the number of +the tab stop. This text then can be copied throughout the snippet using "$#", +given # is the same number as used before. So, to make a C for loop: > + + snippet for + for (${2:i}; $2 < ${1:count}; $1++) { + ${4} + } + +This will cause "count" to first be selected and change if the user starts +typing. When is pressed, the "i" in ${2}'s position will be selected; +all $2 variables will default to "i" and automatically be updated if the user +starts typing. +NOTE: "$#" syntax is used only for variables, not for tab stops as in TextMate. + +Variables within variables are also possible. For instance: > + + snippet opt + + +Will, as usual, cause "option" to first be selected and update all the $1 +variables if the user starts typing. Since one of these variables is inside of +${2}, this text will then be used as a placeholder for the next tab stop, +allowing the user to change it if he wishes. + +To copy a value throughout a snippet without supplying default text, simply +use the "${#:}" construct without the text; e.g.: > + + snippet foo + ${1:}bar$1 +< *snipMate-commands* +Interpolated Vim Script ~ + +Snippets can also contain Vim script commands that are executed (via |eval()|) +when the snippet is inserted. Commands are given inside backticks (`...`); for +TextMates's functionality, use the |system()| function. E.g.: > + + snippet date + `system("date +%Y-%m-%d")` + +will insert the current date, assuming you are on a Unix system. Note that you +can also (and should) use |strftime()| for this example. + +Filename([{expr}] [, {defaultText}]) *snipMate-filename* *Filename()* + +Since the current filename is used often in snippets, a default function +has been defined for it in snipMate.vim, appropriately called Filename(). + +With no arguments, the default filename without an extension is returned; +the first argument specifies what to place before or after the filename, +and the second argument supplies the default text to be used if the file +has not been named. "$1" in the first argument is replaced with the filename; +if you only want the filename to be returned, the first argument can be left +blank. Examples: > + + snippet filename + `Filename()` + snippet filename_with_default + `Filename('', 'name')` + snippet filename_foo + `filename('$1_foo')` + +The first example returns the filename if it the file has been named, and an +empty string if it hasn't. The second returns the filename if it's been named, +and "name" if it hasn't. The third returns the filename followed by "_foo" if +it has been named, and an empty string if it hasn't. + + *multi_snip* +To specify that a snippet can have multiple matches in a *.snippets file, use +this syntax: > + + snippet trigger A description of snippet #1 + expand this text + snippet trigger A description of snippet #2 + expand THIS text! + +In this example, when "trigger" is typed, a numbered menu containing all +of the descriptions of the "trigger" will be shown; when the user presses the +corresponding number, that snippet will then be expanded. + +To create a snippet with multiple matches using *.snippet files, +simply place all the snippets in a subdirectory with the trigger name: +'snippets///.snippet'. + +============================================================================== +USAGE *snipMate-usage* + + *'snippets'* *g:snippets_dir* +Snippets are by default looked for any 'snippets' directory in your +'runtimepath'. Typically, it is located at '~/.vim/snippets/' on *nix or +'$HOME\vimfiles\snippets\' on Windows. To change that location or add another +one, change the g:snippets_dir variable in your |.vimrc| to your preferred +directory, or use the |ExtractSnips()|function. This will be used by the +|globpath()| function, and so accepts the same syntax as it (e.g., +comma-separated paths). + +ExtractSnipsFile({directory}, {filetype}) *ExtractSnipsFile()* *.snippets* + +ExtractSnipsFile() extracts the specified *.snippets file for the given +filetype. A .snippets file contains multiple snippet declarations for the +filetype. It is further explained above, in |snippet-syntax|. + +ExtractSnips({directory}, {filetype}) *ExtractSnips()* *.snippet* + +ExtractSnips() extracts *.snippet files from the specified directory and +defines them as snippets for the given filetype. The directory tree should +look like this: 'snippets//.snippet'. If the snippet has +multiple matches, it should look like this: +'snippets///.snippet' (see |multi_snip|). + + *ResetSnippets()* +The ResetSnippets() function removes all snippets from memory. This is useful +to put at the top of a snippet setup file for if you would like to |:source| +it multiple times. + + *list-snippets* *i_CTRL-R_* +If you would like to see what snippets are available, simply type +in the current buffer to show a list via |popupmenu-completion|. + +============================================================================== +SETTINGS *snipMate-settings* *g:snips_author* + +The g:snips_author string (similar to $TM_FULLNAME in TextMate) should be set +to your name; it can then be used in snippets to automatically add it. E.g.: > + + let g:snips_author = 'Hubert Farnsworth' + snippet name + `g:snips_author` +< + *snipMate-expandtab* *snipMate-indenting* +If you would like your snippets to be expanded using spaces instead of tabs, +just enable 'expandtab' and set 'softtabstop' to your preferred amount of +spaces. If 'softtabstop' is not set, 'shiftwidth' is used instead. + + *snipMate-remap* +snipMate does not come with a setting to customize the trigger key, but you +can remap it easily in the two lines it's defined in the 'after' directory +under 'plugin/snipMate.vim'. For instance, to change the trigger key +to CTRL-J, just change this: > + + ino =TriggerSnippet() + snor i=TriggerSnippet() + +to this: > + ino =TriggerSnippet() + snor i=TriggerSnippet() + +============================================================================== +FEATURES *snipMate-features* + +snipMate.vim has the following features among others: + - The syntax of snippets is very similar to TextMate's, allowing + easy conversion. + - The position of the snippet is kept transparently (i.e. it does not use + markers/placeholders written to the buffer), which allows you to escape + out of an incomplete snippet, something particularly useful in Vim. + - Variables in snippets are updated as-you-type. + - Snippets can have multiple matches. + - Snippets can be out of order. For instance, in a do...while loop, the + condition can be added before the code. + - [New] File-based snippets are supported. + - [New] Triggers after non-word delimiters are expanded, e.g. "foo" + in "bar.foo". + - [New] can now be used to jump tab stops in reverse order. + +============================================================================== +DISADVANTAGES *snipMate-disadvantages* + +snipMate.vim currently has the following disadvantages to TextMate's snippets: + - There is no $0; the order of tab stops must be explicitly stated. + - Placeholders within placeholders are not possible. E.g.: > + + '${3}
' +< + In TextMate this would first highlight ' id="some_id"', and if + you hit delete it would automatically skip ${2} and go to ${3} + on the next , but if you didn't delete it it would highlight + "some_id" first. You cannot do this in snipMate.vim. + - Regex cannot be performed on variables, such as "${1/.*/\U&}" + - Placeholders cannot span multiple lines. + - Activating snippets in different scopes of the same file is + not possible. + +Perhaps some of these features will be added in a later release. + +============================================================================== +CONTACT *snipMate-contact* *snipMate-author* + +To contact the author (Michael Sanders), please email: + msanders42+snipmate gmail com + +I greatly appreciate any suggestions or improvements offered for the script. + +============================================================================== + +vim:tw=78:ts=8:ft=help:norl: diff --git a/.vim/bundle/vim-snipmate/doc/tags b/.vim/bundle/vim-snipmate/doc/tags new file mode 100644 index 0000000..b21f751 --- /dev/null +++ b/.vim/bundle/vim-snipmate/doc/tags @@ -0,0 +1,33 @@ +'snippets' snipMate.txt /*'snippets'* +.snippet snipMate.txt /*.snippet* +.snippets snipMate.txt /*.snippets* +ExtractSnips() snipMate.txt /*ExtractSnips()* +ExtractSnipsFile() snipMate.txt /*ExtractSnipsFile()* +Filename() snipMate.txt /*Filename()* +ResetSnippets() snipMate.txt /*ResetSnippets()* +g:snippets_dir snipMate.txt /*g:snippets_dir* +g:snips_author snipMate.txt /*g:snips_author* +i_CTRL-R_ snipMate.txt /*i_CTRL-R_* +list-snippets snipMate.txt /*list-snippets* +multi_snip snipMate.txt /*multi_snip* +snipMate snipMate.txt /*snipMate* +snipMate-$# snipMate.txt /*snipMate-$#* +snipMate-${#:} snipMate.txt /*snipMate-${#:}* +snipMate-${#} snipMate.txt /*snipMate-${#}* +snipMate-author snipMate.txt /*snipMate-author* +snipMate-commands snipMate.txt /*snipMate-commands* +snipMate-contact snipMate.txt /*snipMate-contact* +snipMate-description snipMate.txt /*snipMate-description* +snipMate-disadvantages snipMate.txt /*snipMate-disadvantages* +snipMate-expandtab snipMate.txt /*snipMate-expandtab* +snipMate-features snipMate.txt /*snipMate-features* +snipMate-filename snipMate.txt /*snipMate-filename* +snipMate-indenting snipMate.txt /*snipMate-indenting* +snipMate-placeholders snipMate.txt /*snipMate-placeholders* +snipMate-remap snipMate.txt /*snipMate-remap* +snipMate-settings snipMate.txt /*snipMate-settings* +snipMate-usage snipMate.txt /*snipMate-usage* +snipMate.txt snipMate.txt /*snipMate.txt* +snippet snipMate.txt /*snippet* +snippet-syntax snipMate.txt /*snippet-syntax* +snippets snipMate.txt /*snippets* diff --git a/.vim/bundle/vim-snipmate/ftplugin/html_snip_helper.vim b/.vim/bundle/vim-snipmate/ftplugin/html_snip_helper.vim new file mode 100644 index 0000000..2e54570 --- /dev/null +++ b/.vim/bundle/vim-snipmate/ftplugin/html_snip_helper.vim @@ -0,0 +1,10 @@ +" Helper function for (x)html snippets +if exists('s:did_snip_helper') || &cp || !exists('loaded_snips') + finish +endif +let s:did_snip_helper = 1 + +" Automatically closes tag if in xhtml +fun! Close() + return stridx(&ft, 'xhtml') == -1 ? '' : ' /' +endf diff --git a/.vim/bundle/vim-snipmate/plugin/snipMate.vim b/.vim/bundle/vim-snipmate/plugin/snipMate.vim new file mode 100644 index 0000000..3efee2a --- /dev/null +++ b/.vim/bundle/vim-snipmate/plugin/snipMate.vim @@ -0,0 +1,247 @@ +" File: snipMate.vim +" Author: Michael Sanders +" Last Updated: July 13, 2009 +" Version: 0.83 +" Description: snipMate.vim implements some of TextMate's snippets features in +" Vim. A snippet is a piece of often-typed text that you can +" insert into your document using a trigger word followed by a "". +" +" For more help see snipMate.txt; you can do this by using: +" :helptags ~/.vim/doc +" :h snipMate.txt + +if exists('loaded_snips') || &cp || version < 700 + finish +endif +let loaded_snips = 1 +if !exists('snips_author') | let snips_author = 'Me' | endif + +au BufRead,BufNewFile *.snippets\= set ft=snippet +au FileType snippet setl noet fdm=indent + +let s:snippets = {} | let s:multi_snips = {} + +if !exists('snippets_dir') + let snippets_dir = substitute(globpath(&rtp, 'snippets/'), "\n", ',', 'g') +endif + +fun! MakeSnip(scope, trigger, content, ...) + let multisnip = a:0 && a:1 != '' + let var = multisnip ? 's:multi_snips' : 's:snippets' + if !has_key({var}, a:scope) | let {var}[a:scope] = {} | endif + if !has_key({var}[a:scope], a:trigger) + let {var}[a:scope][a:trigger] = multisnip ? [[a:1, a:content]] : a:content + elseif multisnip | let {var}[a:scope][a:trigger] += [[a:1, a:content]] + else + echom 'Warning in snipMate.vim: Snippet '.a:trigger.' is already defined.' + \ .' See :h multi_snip for help on snippets with multiple matches.' + endif +endf + +fun! ExtractSnips(dir, ft) + for path in split(globpath(a:dir, '*'), "\n") + if isdirectory(path) + let pathname = fnamemodify(path, ':t') + for snipFile in split(globpath(path, '*.snippet'), "\n") + call s:ProcessFile(snipFile, a:ft, pathname) + endfor + elseif fnamemodify(path, ':e') == 'snippet' + call s:ProcessFile(path, a:ft) + endif + endfor +endf + +" Processes a single-snippet file; optionally add the name of the parent +" directory for a snippet with multiple matches. +fun s:ProcessFile(file, ft, ...) + let keyword = fnamemodify(a:file, ':t:r') + if keyword == '' | return | endif + try + let text = join(readfile(a:file), "\n") + catch /E484/ + echom "Error in snipMate.vim: couldn't read file: ".a:file + endtry + return a:0 ? MakeSnip(a:ft, a:1, text, keyword) + \ : MakeSnip(a:ft, keyword, text) +endf + +fun! ExtractSnipsFile(file, ft) + if !filereadable(a:file) | return | endif + let text = readfile(a:file) + let inSnip = 0 + for line in text + ["\n"] + if inSnip && (line[0] == "\t" || line == '') + let content .= strpart(line, 1)."\n" + continue + elseif inSnip + call MakeSnip(a:ft, trigger, content[:-2], name) + let inSnip = 0 + endif + + if line[:6] == 'snippet' + let inSnip = 1 + let trigger = strpart(line, 8) + let name = '' + let space = stridx(trigger, ' ') + 1 + if space " Process multi snip + let name = strpart(trigger, space) + let trigger = strpart(trigger, 0, space - 1) + endif + let content = '' + endif + endfor +endf + +fun! ResetSnippets() + let s:snippets = {} | let s:multi_snips = {} | let g:did_ft = {} +endf + +let g:did_ft = {} +fun! GetSnippets(dir, filetypes) + for ft in split(a:filetypes, '\.') + if has_key(g:did_ft, ft) | continue | endif + call s:DefineSnips(a:dir, ft, ft) + if ft == 'objc' || ft == 'cpp' || ft == 'cs' + call s:DefineSnips(a:dir, 'c', ft) + elseif ft == 'xhtml' + call s:DefineSnips(a:dir, 'html', 'xhtml') + endif + let g:did_ft[ft] = 1 + endfor +endf + +" Define "aliasft" snippets for the filetype "realft". +fun s:DefineSnips(dir, aliasft, realft) + for path in split(globpath(a:dir, a:aliasft.'/')."\n". + \ globpath(a:dir, a:aliasft.'-*/'), "\n") + call ExtractSnips(path, a:realft) + endfor + for path in split(globpath(a:dir, a:aliasft.'.snippets')."\n". + \ globpath(a:dir, a:aliasft.'-*.snippets'), "\n") + call ExtractSnipsFile(path, a:realft) + endfor +endf + +fun! TriggerSnippet() + if exists('g:SuperTabMappingForward') + if g:SuperTabMappingForward == "" + let SuperTabKey = "\" + elseif g:SuperTabMappingBackward == "" + let SuperTabKey = "\" + endif + endif + + if pumvisible() " Update snippet if completion is used, or deal with supertab + if exists('SuperTabKey') + call feedkeys(SuperTabKey) | return '' + endif + call feedkeys("\a", 'n') " Close completion menu + call feedkeys("\") | return '' + endif + + if exists('g:snipPos') | return snipMate#jumpTabStop(0) | endif + + let word = matchstr(getline('.'), '\S\+\%'.col('.').'c') + for scope in [bufnr('%')] + split(&ft, '\.') + ['_'] + let [trigger, snippet] = s:GetSnippet(word, scope) + " If word is a trigger for a snippet, delete the trigger & expand + " the snippet. + if snippet != '' + let col = col('.') - len(trigger) + sil exe 's/\V'.escape(trigger, '/.').'\%#//' + return snipMate#expandSnip(snippet, col) + endif + endfor + + if exists('SuperTabKey') + call feedkeys(SuperTabKey) + return '' + endif + return "\" +endf + +fun! BackwardsSnippet() + if exists('g:snipPos') | return snipMate#jumpTabStop(1) | endif + + if exists('g:SuperTabMappingForward') + if g:SuperTabMappingBackward == "" + let SuperTabKey = "\" + elseif g:SuperTabMappingForward == "" + let SuperTabKey = "\" + endif + endif + if exists('SuperTabKey') + call feedkeys(SuperTabKey) + return '' + endif + return "\" +endf + +" Check if word under cursor is snippet trigger; if it isn't, try checking if +" the text after non-word characters is (e.g. check for "foo" in "bar.foo") +fun s:GetSnippet(word, scope) + let word = a:word | let snippet = '' + while snippet == '' + if exists('s:snippets["'.a:scope.'"]["'.escape(word, '\"').'"]') + let snippet = s:snippets[a:scope][word] + elseif exists('s:multi_snips["'.a:scope.'"]["'.escape(word, '\"').'"]') + let snippet = s:ChooseSnippet(a:scope, word) + if snippet == '' | break | endif + else + if match(word, '\W') == -1 | break | endif + let word = substitute(word, '.\{-}\W', '', '') + endif + endw + if word == '' && a:word != '.' && stridx(a:word, '.') != -1 + let [word, snippet] = s:GetSnippet('.', a:scope) + endif + return [word, snippet] +endf + +fun s:ChooseSnippet(scope, trigger) + let snippet = [] + let i = 1 + for snip in s:multi_snips[a:scope][a:trigger] + let snippet += [i.'. '.snip[0]] + let i += 1 + endfor + if i == 2 | return s:multi_snips[a:scope][a:trigger][0][1] | endif + let num = inputlist(snippet) - 1 + return num == -1 ? '' : s:multi_snips[a:scope][a:trigger][num][1] +endf + +fun! ShowAvailableSnips() + let line = getline('.') + let col = col('.') + let word = matchstr(getline('.'), '\S\+\%'.col.'c') + let words = [word] + if stridx(word, '.') + let words += split(word, '\.', 1) + endif + let matchlen = 0 + let matches = [] + for scope in [bufnr('%')] + split(&ft, '\.') + ['_'] + let triggers = has_key(s:snippets, scope) ? keys(s:snippets[scope]) : [] + if has_key(s:multi_snips, scope) + let triggers += keys(s:multi_snips[scope]) + endif + for trigger in triggers + for word in words + if word == '' + let matches += [trigger] " Show all matches if word is empty + elseif trigger =~ '^'.word + let matches += [trigger] + let len = len(word) + if len > matchlen | let matchlen = len | endif + endif + endfor + endfor + endfor + + " This is to avoid a bug with Vim when using complete(col - matchlen, matches) + " (Issue#46 on the Google Code snipMate issue tracker). + call setline(line('.'), substitute(line, repeat('.', matchlen).'\%'.col.'c', '', '')) + call complete(col, matches) + return '' +endf +" vim:noet:sw=4:ts=4:ft=vim diff --git a/.vim/bundle/vim-snipmate/snippets/_.snippets b/.vim/bundle/vim-snipmate/snippets/_.snippets new file mode 100644 index 0000000..4abda48 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/_.snippets @@ -0,0 +1,35 @@ +# Global snippets + +# (c) holds no legal value ;) +snippet c) + `&enc[:2] == "utf" ? "©" : "(c)"` Copyright `strftime("%Y")` ${1:`g:snips_author`}. All Rights Reserved.${2} +snippet date + `strftime("%Y-%m-%d")` +snippet mfg + Mit freundlichen Grüßen, + Stefan Hagen +snippet mailf + Sehr geehrte Frau ${1:Name}, + + ${2:Text} + + Mit freundlichen Grüßen, + Stefan Hagen +snippet mailh + Sehr geehrter Herr ${1:Name}, + + ${2:Text} + + Mit freundlichen Grüßen, + Stefan Hagen +snippet sig + -- + Stefan Hagen + T: +49 (0)176 64292517 + M: sh@codevoid.de +snippet je + `strftime("%A %d.%m.%d %H:%M")`: +snippet tel + 017664292517 +snippet worktel + 015162345601 diff --git a/.vim/bundle/vim-snipmate/snippets/c.snippets b/.vim/bundle/vim-snipmate/snippets/c.snippets new file mode 100644 index 0000000..89b81ba --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/c.snippets @@ -0,0 +1,110 @@ +# main() +snippet main + int main(int argc, const char *argv[]) + { + ${1} + return 0; + } +# #include <...> +snippet inc + #include <${1:stdio}.h>${2} +# #include "..." +snippet Inc + #include "${1:`Filename("$1.h")`}"${2} +# #ifndef ... #define ... #endif +snippet Def + #ifndef $1 + #define ${1:SYMBOL} ${2:value} + #endif${3} +snippet def + #define +snippet ifdef + #ifdef ${1:FOO} + ${2:#define } + #endif +snippet #if + #if ${1:FOO} + ${2} + #endif +# Header Include-Guard +# (the randomizer code is taken directly from TextMate; it could probably be +# cleaner, I don't know how to do it in vim script) +snippet once + #ifndef ${1:`toupper(Filename('', 'UNTITLED').'_'.system("/usr/bin/ruby -e 'print (rand * 2821109907455).round.to_s(36)'"))`} + + #define $1 + + ${2} + + #endif /* end of include guard: $1 */ +# If Condition +snippet if + if (${1:/* condition */}) { + ${2:/* code */} + } +snippet el + else { + ${1} + } +# Tertiary conditional +snippet t + ${1:/* condition */} ? ${2:a} : ${3:b} +# Do While Loop +snippet do + do { + ${2:/* code */} + } while (${1:/* condition */}); +# While Loop +snippet wh + while (${1:/* condition */}) { + ${2:/* code */} + } +# For Loop +snippet for + for (${2:i} = 0; $2 < ${1:count}; $2${3:++}) { + ${4:/* code */} + } +# Custom For Loop +snippet forr + for (${1:i} = ${2:0}; ${3:$1 < 10}; $1${4:++}) { + ${5:/* code */} + } +# Function +snippet fun + ${1:void} ${2:function_name}(${3}) + { + ${4:/* code */} + } +# Function Declaration +snippet fund + ${1:void} ${2:function_name}(${3});${4} +# Typedef +snippet td + typedef ${1:int} ${2:MyCustomType};${3} +# Struct +snippet st + struct ${1:`Filename('$1_t', 'name')`} { + ${2:/* data */} + }${3: /* optional variable list */};${4} +# Typedef struct +snippet tds + typedef struct ${2:_$1 }{ + ${3:/* data */} + } ${1:`Filename('$1_t', 'name')`}; +# Typdef enum +snippet tde + typedef enum { + ${1:/* data */} + } ${2:foo}; +# printf +# unfortunately version this isn't as nice as TextMates's, given the lack of a +# dynamic `...` +snippet pr + printf("${1:%s}\n"${2});${3} +# fprintf (again, this isn't as nice as TextMate's version, but it works) +snippet fpr + fprintf(${1:stderr}, "${2:%s}\n"${3});${4} +snippet . + [${1}]${2} +snippet un + unsigned diff --git a/.vim/bundle/vim-snipmate/snippets/cpp.snippets b/.vim/bundle/vim-snipmate/snippets/cpp.snippets new file mode 100644 index 0000000..e4850cd --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/cpp.snippets @@ -0,0 +1,30 @@ +# Read File Into Vector +snippet readfile + std::vector v; + if (FILE *${2:fp} = fopen(${1:"filename"}, "r")) { + char buf[1024]; + while (size_t len = fread(buf, 1, sizeof(buf), $2)) + v.insert(v.end(), buf, buf + len); + fclose($2); + }${3} +# std::map +snippet map + std::map<${1:key}, ${2:value}> map${3}; +# std::vector +snippet vector + std::vector<${1:char}> v${2}; +# Namespace +snippet ns + namespace ${1:`Filename('', 'my')`} { + ${2} + } /* $1 */ +# Class +snippet cl + class ${1:`Filename('$1_t', 'name')`} { + public: + $1 (${2:arguments}); + virtual ~$1 (); + + private: + ${3:/* data */} + }; diff --git a/.vim/bundle/vim-snipmate/snippets/html.snippets b/.vim/bundle/vim-snipmate/snippets/html.snippets new file mode 100644 index 0000000..aefb9db --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/html.snippets @@ -0,0 +1,190 @@ +# Some useful Unicode entities +# Non-Breaking Space +snippet nbs +   +# ← +snippet left + ← +# → +snippet right + → +# ↑ +snippet up + ↑ +# ↓ +snippet down + ↓ +# ↩ +snippet return + ↩ +# ⇤ +snippet backtab + ⇤ +# ⇥ +snippet tab + ⇥ +# ⇧ +snippet shift + ⇧ +# ⌃ +snippet control + ⌃ +# ⌅ +snippet enter + ⌅ +# ⌘ +snippet command + ⌘ +# ⌥ +snippet option + ⌥ +# ⌦ +snippet delete + ⌦ +# ⌫ +snippet backspace + ⌫ +# ⎋ +snippet escape + ⎋ +# Generic Doctype +snippet doctype HTML 4.01 Strict + +snippet doctype HTML 4.01 Transitional + +snippet doctype HTML 5 + +snippet doctype XHTML 1.0 Frameset + +snippet doctype XHTML 1.0 Strict + +snippet doctype XHTML 1.0 Transitional + +snippet doctype XHTML 1.1 + +# HTML Doctype 4.01 Strict +snippet docts + +# HTML Doctype 4.01 Transitional +snippet doct + +# HTML Doctype 5 +snippet doct5 + +# XHTML Doctype 1.0 Frameset +snippet docxf + +# XHTML Doctype 1.0 Strict +snippet docxs + +# XHTML Doctype 1.0 Transitional +snippet docxt + +# XHTML Doctype 1.1 +snippet docx + +snippet html + + ${1} + +snippet xhtml + + ${1} + +snippet body + + ${1} + +snippet head + + + + ${1:`substitute(Filename('', 'Page Title'), '^.', '\u&', '')`} + ${2} + +snippet title + ${1:`substitute(Filename('', 'Page Title'), '^.', '\u&', '')`}${2} +snippet script + ${2} +snippet scriptsrc + ${2} +snippet style + ${3} +snippet base + +snippet r + +snippet div +
+ ${2} +
+# Embed QT Movie +snippet movie + + + + + + ${6} +snippet fieldset +
+ ${1:name} + + ${3} +
+snippet form +
+ ${3} + + +

+
+snippet h1 +

${2:$1}

+snippet input + ${4} +snippet label + ${7} +snippet link + ${4} +snippet mailto + ${3:email me} +snippet meta + ${3} +snippet opt + ${3} +snippet optt + ${2} +snippet select + ${5} +snippet table + + + +
${2:Header}
${3:Data}
${4} +snippet textarea + ${5} diff --git a/.vim/bundle/vim-snipmate/snippets/java.snippets b/.vim/bundle/vim-snipmate/snippets/java.snippets new file mode 100644 index 0000000..fd705cb --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/java.snippets @@ -0,0 +1,78 @@ +snippet main + public static void main (String [] args) + { + ${1:/* code */} + } +snippet pu + public +snippet po + protected +snippet pr + private +snippet st + static +snippet fi + final +snippet ab + abstract +snippet re + return +snippet br + break; +snippet de + default: + ${1} +snippet ca + catch(${1:Exception} ${2:e}) ${3} +snippet th + throw +snippet sy + synchronized +snippet im + import +snippet j.u + java.util +snippet j.i + java.io. +snippet j.b + java.beans. +snippet j.n + java.net. +snippet j.m + java.math. +snippet if + if (${1}) ${2} +snippet el + else +snippet elif + else if (${1}) ${2} +snippet wh + while (${1}) ${2} +snippet for + for (${1}; ${2}; ${3}) ${4} +snippet fore + for (${1} : ${2}) ${3} +snippet sw + switch (${1}) ${2} +snippet cs + case ${1}: + ${2} + ${3} +snippet tc + public class ${1:`Filename()`} extends ${2:TestCase} +snippet t + public void test${1:Name}() throws Exception ${2} +snippet cl + class ${1:`Filename("", "untitled")`} ${2} +snippet in + interface ${1:`Filename("", "untitled")`} ${2:extends Parent}${3} +snippet m + ${1:void} ${2:method}(${3}) ${4:throws }${5} +snippet v + ${1:String} ${2:var}${3: = null}${4};${5} +snippet co + static public final ${1:String} ${2:var} = ${3};${4} +snippet cos + static public final String ${1:var} = "${2}";${3} +snippet as + assert ${1:test} : "${2:Failure message}";${3} diff --git a/.vim/bundle/vim-snipmate/snippets/javascript.snippets b/.vim/bundle/vim-snipmate/snippets/javascript.snippets new file mode 100644 index 0000000..51f5e05 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/javascript.snippets @@ -0,0 +1,74 @@ +# Prototype +snippet proto + ${1:class_name}.prototype.${2:method_name} = + function(${3:first_argument}) { + ${4:// body...} + }; +# Function +snippet fun + function ${1:function_name} (${2:argument}) { + ${3:// body...} + } +# Anonymous Function +snippet f + function(${1}) {${2}}; +# if +snippet if + if (${1:true}) {${2}}; +# if ... else +snippet ife + if (${1:true}) {${2}} + else{${3}}; +# tertiary conditional +snippet t + ${1:/* condition */} ? ${2:a} : ${3:b} +# switch +snippet switch + switch(${1:expression}) { + case '${3:case}': + ${4:// code} + break; + ${5} + default: + ${2:// code} + } +# case +snippet case + case '${1:case}': + ${2:// code} + break; + ${3} +# for (...) {...} +snippet for + for (var ${2:i} = 0; $2 < ${1:Things}.length; $2${3:++}) { + ${4:$1[$2]} + }; +# for (...) {...} (Improved Native For-Loop) +snippet forr + for (var ${2:i} = ${1:Things}.length - 1; $2 >= 0; $2${3:--}) { + ${4:$1[$2]} + }; +# while (...) {...} +snippet wh + while (${1:/* condition */}) { + ${2:/* code */} + } +# do...while +snippet do + do { + ${2:/* code */} + } while (${1:/* condition */}); +# Object Method +snippet :f + ${1:method_name}: function(${2:attribute}) { + ${4} + }${3:,} +# setTimeout function +snippet timeout + setTimeout(function() {${3}}${2}, ${1:10}; +# Get Elements +snippet get + getElementsBy${1:TagName}('${2}')${3} +# Get Element +snippet gett + getElementBy${1:Id}('${2}')${3} diff --git a/.vim/bundle/vim-snipmate/snippets/perl.snippets b/.vim/bundle/vim-snipmate/snippets/perl.snippets new file mode 100644 index 0000000..cf8f9fc --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/perl.snippets @@ -0,0 +1,91 @@ +# #!/usr/bin/perl +snippet #! + #!/usr/bin/perl + +# Hash Pointer +snippet . + => +# Function +snippet sub + sub ${1:function_name} { + ${2:#body ...} + } +# Conditional +snippet if + if (${1}) { + ${2:# body...} + } +# Conditional if..else +snippet ife + if (${1}) { + ${2:# body...} + } else { + ${3:# else...} + } +# Conditional if..elsif..else +snippet ifee + if (${1}) { + ${2:# body...} + } elsif (${3}) { + ${4:# elsif...} + } else { + ${5:# else...} + } +# Conditional One-line +snippet xif + ${1:expression} if ${2:condition};${3} +# Unless conditional +snippet unless + unless (${1}) { + ${2:# body...} + } +# Unless conditional One-line +snippet xunless + ${1:expression} unless ${2:condition};${3} +# Try/Except +snippet eval + eval { + ${1:# do something risky...} + }; + if ($@) { + ${2:# handle failure...} + } +# While Loop +snippet wh + while (${1}) { + ${2:# body...} + } +# While Loop One-line +snippet xwh + ${1:expression} while ${2:condition};${3} +# For Loop +snippet for + for (my $${2:var} = 0; $$2 < ${1:count}; $$2${3:++}) { + ${4:# body...} + } +# Foreach Loop +snippet fore + foreach my $${1:x} (@${2:array}) { + ${3:# body...} + } +# Foreach Loop One-line +snippet xfore + ${1:expression} foreach @${2:array};${3} +# Package +snippet cl + package ${1:ClassName}; + + use base qw(${2:ParentClass}); + + sub new { + my $class = shift; + $class = ref $class if ref $class; + my $self = bless {}, $class; + $self; + } + + 1;${3} +# Read File +snippet slurp + my $${1:var}; + { local $/ = undef; local *FILE; open FILE, "<${2:file}"; $$1 = ; close FILE }${3} diff --git a/.vim/bundle/vim-snipmate/snippets/php.snippets b/.vim/bundle/vim-snipmate/snippets/php.snippets new file mode 100644 index 0000000..3ce9e26 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/php.snippets @@ -0,0 +1,216 @@ +snippet php + +snippet ec + echo "${1:string}"${2}; +snippet inc + include '${1:file}';${2} +snippet inc1 + include_once '${1:file}';${2} +snippet req + require '${1:file}';${2} +snippet req1 + require_once '${1:file}';${2} +# $GLOBALS['...'] +snippet globals + $GLOBALS['${1:variable}']${2: = }${3:something}${4:;}${5} +snippet $_ COOKIE['...'] + $_COOKIE['${1:variable}']${2} +snippet $_ ENV['...'] + $_ENV['${1:variable}']${2} +snippet $_ FILES['...'] + $_FILES['${1:variable}']${2} +snippet $_ Get['...'] + $_GET['${1:variable}']${2} +snippet $_ POST['...'] + $_POST['${1:variable}']${2} +snippet $_ REQUEST['...'] + $_REQUEST['${1:variable}']${2} +snippet $_ SERVER['...'] + $_SERVER['${1:variable}']${2} +snippet $_ SESSION['...'] + $_SESSION['${1:variable}']${2} +# Start Docblock +snippet /* + /** + * ${1} + **/ +# Class - post doc +snippet doc_cp + /** + * ${1:undocumented class} + * + * @package ${2:default} + * @author ${3:`g:snips_author`} + **/${4} +# Class Variable - post doc +snippet doc_vp + /** + * ${1:undocumented class variable} + * + * @var ${2:string} + **/${3} +# Class Variable +snippet doc_v + /** + * ${3:undocumented class variable} + * + * @var ${4:string} + **/ + ${1:var} $${2};${5} +# Class +snippet doc_c + /** + * ${3:undocumented class} + * + * @packaged ${4:default} + * @author ${5:`g:snips_author`} + **/ + ${1:}class ${2:} + {${6} + } // END $1class $2 +# Constant Definition - post doc +snippet doc_dp + /** + * ${1:undocumented constant} + **/${2} +# Constant Definition +snippet doc_d + /** + * ${3:undocumented constant} + **/ + define(${1}, ${2});${4} +# Function - post doc +snippet doc_fp + /** + * ${1:undocumented function} + * + * @return ${2:void} + * @author ${3:`g:snips_author`} + **/${4} +# Function signature +snippet doc_s + /** + * ${4:undocumented function} + * + * @return ${5:void} + * @author ${6:`g:snips_author`} + **/ + ${1}function ${2}(${3});${7} +# Function +snippet doc_f + /** + * ${4:undocumented function} + * + * @return ${5:void} + * @author ${6:`g:snips_author`} + **/ + ${1}function ${2}(${3}) + {${7} + } +# Header +snippet doc_h + /** + * ${1} + * + * @author ${2:`g:snips_author`} + * @version ${3:$Id$} + * @copyright ${4:$2}, `strftime('%d %B, %Y')` + * @package ${5:default} + **/ + + /** + * Define DocBlock + *// +# Interface +snippet doc_i + /** + * ${2:undocumented class} + * + * @package ${3:default} + * @author ${4:`g:snips_author`} + **/ + interface ${1:} + {${5} + } // END interface $1 +# class ... +snippet class + /** + * ${1} + **/ + class ${2:ClassName} + { + ${3} + function ${4:__construct}(${5:argument}) + { + ${6:// code...} + } + } +# define(...) +snippet def + define('${1}'${2});${3} +# defined(...) +snippet def? + ${1}defined('${2}')${3} +snippet wh + while (${1:/* condition */}) { + ${2:// code...} + } +# do ... while +snippet do + do { + ${2:// code... } + } while (${1:/* condition */}); +snippet if + if (${1:/* condition */}) { + ${2:// code...} + } +snippet ife + if (${1:/* condition */}) { + ${2:// code...} + } else { + ${3:// code...} + } + ${4} +snippet else + else { + ${1:// code...} + } +snippet elseif + elseif (${1:/* condition */}) { + ${2:// code...} + } +# Tertiary conditional +snippet t + $${1:retVal} = (${2:condition}) ? ${3:a} : ${4:b};${5} +snippet switch + switch ($${1:variable}) { + case '${2:value}': + ${3:// code...} + break; + ${5} + default: + ${4:// code...} + break; + } +snippet case + case '${1:value}': + ${2:// code...} + break;${3} +snippet for + for ($${2:i} = 0; $$2 < ${1:count}; $$2${3:++}) { + ${4: // code...} + } +snippet foreach + foreach ($${1:variable} as $${2:key}) { + ${3:// code...} + } +snippet fun + ${1:public }function ${2:FunctionName}(${3}) + { + ${4:// code...} + } +# $... = array (...) +snippet array + $${1:arrayName} = array('${2}' => ${3});${4} diff --git a/.vim/bundle/vim-snipmate/snippets/python.snippets b/.vim/bundle/vim-snipmate/snippets/python.snippets new file mode 100644 index 0000000..d511184 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/python.snippets @@ -0,0 +1,86 @@ +snippet #! + #!/usr/bin/python + +snippet imp + import ${1:module} +# Module Docstring +snippet docs + ''' + File: ${1:`Filename('$1.py', 'foo.py')`} + Author: ${2:`g:snips_author`} + Description: ${3} + ''' +snippet wh + while ${1:condition}: + ${2:# code...} +snippet for + for ${1:needle} in ${2:haystack}: + ${3:# code...} +# New Class +snippet cl + class ${1:ClassName}(${2:object}): + """${3:docstring for $1}""" + def __init__(self, ${4:arg}): + ${5:super($1, self).__init__()} + self.$4 = $4 + ${6} +# New Function +snippet def + def ${1:fname}(${2:`indent('.') ? 'self' : ''`}): + """${3:docstring for $1}""" + ${4:pass} +snippet deff + def ${1:fname}(${2:`indent('.') ? 'self' : ''`}): + ${3} +# New Method +snippet defs + def ${1:mname}(self, ${2:arg}): + ${3:pass} +# New Property +snippet property + def ${1:foo}(): + doc = "${2:The $1 property.}" + def fget(self): + ${3:return self._$1} + def fset(self, value): + ${4:self._$1 = value} +# Lambda +snippet ld + ${1:var} = lambda ${2:vars} : ${3:action} +snippet . + self. +snippet try Try/Except + try: + ${1:pass} + except ${2:Exception}, ${3:e}: + ${4:raise $3} +snippet try Try/Except/Else + try: + ${1:pass} + except ${2:Exception}, ${3:e}: + ${4:raise $3} + else: + ${5:pass} +snippet try Try/Except/Finally + try: + ${1:pass} + except ${2:Exception}, ${3:e}: + ${4:raise $3} + finally: + ${5:pass} +snippet try Try/Except/Else/Finally + try: + ${1:pass} + except ${2:Exception}, ${3:e}: + ${4:raise $3} + else: + ${5:pass} + finally: + ${6:pass} +# if __name__ == '__main__': +snippet ifmain + if __name__ == '__main__': + ${1:main()} +# __magic__ +snippet _ + __${1:init}__${2} diff --git a/.vim/bundle/vim-snipmate/snippets/ruby.snippets b/.vim/bundle/vim-snipmate/snippets/ruby.snippets new file mode 100644 index 0000000..bf1d7f1 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/ruby.snippets @@ -0,0 +1,420 @@ +# #!/usr/bin/ruby +snippet #! + #!/usr/bin/ruby + +# New Block +snippet =b + =begin rdoc + ${1} + =end +snippet y + :yields: ${1:arguments} +snippet rb + #!/usr/bin/env ruby -wKU + +snippet req + require "${1}"${2} +snippet # + # => +snippet end + __END__ +snippet case + case ${1:object} + when ${2:condition} + ${3} + end +snippet when + when ${1:condition} + ${2} +snippet def + def ${1:method_name} + ${2} + end +snippet deft + def test_${1:case_name} + ${2} + end +snippet if + if ${1:condition} + ${2} + end +snippet ife + if ${1:condition} + ${2} + else + ${3} + end +snippet elsif + elsif ${1:condition} + ${2} +snippet unless + unless ${1:condition} + ${2} + end +snippet while + while ${1:condition} + ${2} + end +snippet until + until ${1:condition} + ${2} + end +snippet cla class .. end + class ${1:`substitute(Filename(), '^.', '\u&', '')`} + ${2} + end +snippet cla class .. initialize .. end + class ${1:`substitute(Filename(), '^.', '\u&', '')`} + def initialize(${2:args}) + ${3} + end + + + end +snippet cla class .. < ParentClass .. initialize .. end + class ${1:`substitute(Filename(), '^.', '\u&', '')`} < ${2:ParentClass} + def initialize(${3:args}) + ${4} + end + + + end +snippet cla ClassName = Struct .. do .. end + ${1:`substitute(Filename(), '^.', '\u&', '')`} = Struct.new(:${2:attr_names}) do + def ${3:method_name} + ${4} + end + + + end +snippet cla class BlankSlate .. initialize .. end + class ${1:BlankSlate} + instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A__/ } +snippet cla class << self .. end + class << ${1:self} + ${2} + end +# class .. < DelegateClass .. initialize .. end +snippet cla- + class ${1:`substitute(Filename(), '^.', '\u&', '')`} < DelegateClass(${2:ParentClass}) + def initialize(${3:args}) + super(${4:del_obj}) + + ${5} + end + + + end +snippet mod module .. end + module ${1:`substitute(Filename(), '^.', '\u&', '')`} + ${2} + end +snippet mod module .. module_function .. end + module ${1:`substitute(Filename(), '^.', '\u&', '')`} + module_function + + ${2} + end +snippet mod module .. ClassMethods .. end + module ${1:`substitute(Filename(), '^.', '\u&', '')`} + module ClassMethods + ${2} + end + + module InstanceMethods + + end + + def self.included(receiver) + receiver.extend ClassMethods + receiver.send :include, InstanceMethods + end + end +# attr_reader +snippet r + attr_reader :${1:attr_names} +# attr_writer +snippet w + attr_writer :${1:attr_names} +# attr_accessor +snippet rw + attr_accessor :${1:attr_names} +# include Enumerable +snippet Enum + include Enumerable + + def each(&block) + ${1} + end +# include Comparable +snippet Comp + include Comparable + + def <=>(other) + ${1} + end +# extend Forwardable +snippet Forw- + extend Forwardable +# def self +snippet defs + def self.${1:class_method_name} + ${2} + end +# def method_missing +snippet defmm + def method_missing(meth, *args, &blk) + ${1} + end +snippet defd + def_delegator :${1:@del_obj}, :${2:del_meth}, :${3:new_name} +snippet defds + def_delegators :${1:@del_obj}, :${2:del_methods} +snippet am + alias_method :${1:new_name}, :${2:old_name} +snippet app + if __FILE__ == $PROGRAM_NAME + ${1} + end +# usage_if() +snippet usai + if ARGV.${1} + abort "Usage: #{$PROGRAM_NAME} ${2:ARGS_GO_HERE}"${3} + end +# usage_unless() +snippet usau + unless ARGV.${1} + abort "Usage: #{$PROGRAM_NAME} ${2:ARGS_GO_HERE}"${3} + end +snippet array + Array.new(${1:10}) { |${2:i}| ${3} } +snippet hash + Hash.new { |${1:hash}, ${2:key}| $1[$2] = ${3} } +snippet file File.foreach() { |line| .. } + File.foreach(${1:"path/to/file"}) { |${2:line}| ${3} } +snippet file File.read() + File.read(${1:"path/to/file"})${2} +snippet Dir Dir.global() { |file| .. } + Dir.glob(${1:"dir/glob/*"}) { |${2:file}| ${3} } +snippet Dir Dir[".."] + Dir[${1:"glob/**/*.rb"}]${2} +snippet dir + Filename.dirname(__FILE__) +snippet deli + delete_if { |${1:e}| ${2} } +snippet fil + fill(${1:range}) { |${2:i}| ${3} } +# flatten_once() +snippet flao + inject(Array.new) { |${1:arr}, ${2:a}| $1.push(*$2)}${3} +snippet zip + zip(${1:enums}) { |${2:row}| ${3} } +# downto(0) { |n| .. } +snippet dow + downto(${1:0}) { |${2:n}| ${3} } +snippet ste + step(${1:2}) { |${2:n}| ${3} } +snippet tim + times { |${1:n}| ${2} } +snippet upt + upto(${1:1.0/0.0}) { |${2:n}| ${3} } +snippet loo + loop { ${1} } +snippet ea + each { |${1:e}| ${2} } +snippet eab + each_byte { |${1:byte}| ${2} } +snippet eac- each_char { |chr| .. } + each_char { |${1:chr}| ${2} } +snippet eac- each_cons(..) { |group| .. } + each_cons(${1:2}) { |${2:group}| ${3} } +snippet eai + each_index { |${1:i}| ${2} } +snippet eak + each_key { |${1:key}| ${2} } +snippet eal + each_line { |${1:line}| ${2} } +snippet eap + each_pair { |${1:name}, ${2:val}| ${3} } +snippet eas- + each_slice(${1:2}) { |${2:group}| ${3} } +snippet eav + each_value { |${1:val}| ${2} } +snippet eawi + each_with_index { |${1:e}, ${2:i}| ${3} } +snippet reve + reverse_each { |${1:e}| ${2} } +snippet inj + inject(${1:init}) { |${2:mem}, ${3:var}| ${4} } +snippet map + map { |${1:e}| ${2} } +snippet mapwi- + enum_with_index.map { |${1:e}, ${2:i}| ${3} } +snippet sor + sort { |a, b| ${1} } +snippet sorb + sort_by { |${1:e}| ${2} } +snippet ran + sort_by { rand } +snippet all + all? { |${1:e}| ${2} } +snippet any + any? { |${1:e}| ${2} } +snippet cl + classify { |${1:e}| ${2} } +snippet col + collect { |${1:e}| ${2} } +snippet det + detect { |${1:e}| ${2} } +snippet fet + fetch(${1:name}) { |${2:key}| ${3} } +snippet fin + find { |${1:e}| ${2} } +snippet fina + find_all { |${1:e}| ${2} } +snippet gre + grep(${1:/pattern/}) { |${2:match}| ${3} } +snippet sub + ${1:g}sub(${2:/pattern/}) { |${3:match}| ${4} } +snippet sca + scan(${1:/pattern/}) { |${2:match}| ${3} } +snippet max + max { |a, b|, ${1} } +snippet min + min { |a, b|, ${1} } +snippet par + partition { |${1:e}|, ${2} } +snippet rej + reject { |${1:e}|, ${2} } +snippet sel + select { |${1:e}|, ${2} } +snippet lam + lambda { |${1:args}| ${2} } +snippet do + do |${1:variable}| + ${2} + end +snippet : + :${1:key} => ${2:"value"}${3} +snippet ope + open(${1:"path/or/url/or/pipe"}, "${2:w}") { |${3:io}| ${4} } +# path_from_here() +snippet patfh + File.join(File.dirname(__FILE__), *%2[${1:rel path here}])${2} +# unix_filter {} +snippet unif + ARGF.each_line${1} do |${2:line}| + ${3} + end +# option_parse {} +snippet optp + require "optparse" + + options = {${1:default => "args"}} + + ARGV.options do |opts| + opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} +snippet opt + opts.on( "-${1:o}", "--${2:long-option-name}", ${3:String}, + "${4:Option description.}") do |${5:opt}| + ${6} + end +snippet tc + require "test/unit" + + require "${1:library_file_name}" + + class Test${2:$1} < Test::Unit::TestCase + def test_${3:case_name} + ${4} + end + end +snippet ts + require "test/unit" + + require "tc_${1:test_case_file}" + require "tc_${2:test_case_file}"${3} +snippet as + assert(${1:test}, "${2:Failure message.}")${3} +snippet ase + assert_equal(${1:expected}, ${2:actual})${3} +snippet asne + assert_not_equal(${1:unexpected}, ${2:actual})${3} +snippet asid + assert_in_delta(${1:expected_float}, ${2:actual_float}, ${3:2 ** -20})${4} +snippet asio + assert_instance_of(${1:ExpectedClass}, ${2:actual_instance})${3} +snippet asko + assert_kind_of(${1:ExpectedKind}, ${2:actual_instance})${3} +snippet asn + assert_nil(${1:instance})${2} +snippet asnn + assert_not_nil(${1:instance})${2} +snippet asm + assert_match(/${1:expected_pattern}/, ${2:actual_string})${3} +snippet asnm + assert_no_match(/${1:unexpected_pattern}/, ${2:actual_string})${3} +snippet aso + assert_operator(${1:left}, :${2:operator}, ${3:right})${4} +snippet asr + assert_raise(${1:Exception}) { ${2} } +snippet asnr + assert_nothing_raised(${1:Exception}) { ${2} } +snippet asrt + assert_respond_to(${1:object}, :${2:method})${3} +snippet ass assert_same(..) + assert_same(${1:expected}, ${2:actual})${3} +snippet ass assert_send(..) + assert_send([${1:object}, :${2:message}, ${3:args}])${4} +snippet asns + assert_not_same(${1:unexpected}, ${2:actual})${3} +snippet ast + assert_throws(:${1:expected}) { ${2} } +snippet asnt + assert_nothing_thrown { ${1} } +snippet fl + flunk("${1:Failure message.}")${2} +# Benchmark.bmbm do .. end +snippet bm- + TESTS = ${1:10_000} + Benchmark.bmbm do |results| + ${2} + end +snippet rep + results.report("${1:name}:") { TESTS.times { ${2} }} +# Marshal.dump(.., file) +snippet Md + File.open(${1:"path/to/file.dump"}, "wb") { |${2:file}| Marshal.dump(${3:obj}, $2) }${4} +# Mashal.load(obj) +snippet Ml + File.open(${1:"path/to/file.dump"}, "rb") { |${2:file}| Marshal.load($2) }${3} +# deep_copy(..) +snippet deec + Marshal.load(Marshal.dump(${1:obj_to_copy}))${2} +snippet Pn- + PStore.new(${1:"file_name.pstore"})${2} +snippet tra + transaction(${1:true}) { ${2} } +# xmlread(..) +snippet xml- + REXML::Document.new(File.read(${1:"path/to/file"}))${2} +# xpath(..) { .. } +snippet xpa + elements.each(${1:"//Xpath"}) do |${2:node}| + ${3} + end +# class_from_name() +snippet clafn + split("::").inject(Object) { |par, const| par.const_get(const) } +# singleton_class() +snippet sinc + class << self; self end +snippet nam + namespace :${1:`Filename()`} do + ${2} + end +snippet tas + desc "${1:Task description\}" + task :${2:task_name => [:dependent, :tasks]} do + ${3} + end diff --git a/.vim/bundle/vim-snipmate/snippets/sh.snippets b/.vim/bundle/vim-snipmate/snippets/sh.snippets new file mode 100644 index 0000000..0ef01a2 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/sh.snippets @@ -0,0 +1,39 @@ +# #!/bin/sh +snippet #! + #!/bin/sh + +snippet if + if [[ ${1:condition} ]]; then + ${2:#statements} + fi +snippet elif + elif [[ ${1:condition} ]]; then + ${2:#statements} +snippet for + for (( ${2:i} = 0; $2 < ${1:count}; $2++ )); do + ${3:#statements} + done +snippet wh + while [[ ${1:condition} ]]; do + ${2:#statements} + done +snippet until + until [[ ${1:condition} ]]; do + ${2:#statements} + done +snippet case + case ${1:word} in + ${2:pattern}) + ${3};; + esac +snippet getopt + while getopts ao: name + do + case $name in + a) flag=1 ;; + o) oarg=$OPTARG ;; + ?) echo "Usage: ..."; exit 2 ;; + esac + done + shift $(($OPTIND - 1)) + echo "Non-option arguments: " "$@" diff --git a/.vim/bundle/vim-snipmate/snippets/snippet.snippets b/.vim/bundle/vim-snipmate/snippets/snippet.snippets new file mode 100644 index 0000000..854c058 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/snippet.snippets @@ -0,0 +1,7 @@ +# snippets for making snippets :) +snippet snip + snippet ${1:trigger} + ${2} +snippet msnip + snippet ${1:trigger} ${2:description} + ${3} diff --git a/.vim/bundle/vim-snipmate/snippets/tcl.snippets b/.vim/bundle/vim-snipmate/snippets/tcl.snippets new file mode 100644 index 0000000..bee2ef8 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/tcl.snippets @@ -0,0 +1,92 @@ +# #!/usr/bin/tclsh +snippet #! + #!/usr/bin/tclsh + +# Process +snippet pro + proc ${1:function_name} {${2:args}} { + ${3:#body ...} + } +#xif +snippet xif + ${1:expr}? ${2:true} : ${3:false} +# Conditional +snippet if + if {${1}} { + ${2:# body...} + } +# Conditional if..else +snippet ife + if {${1}} { + ${2:# body...} + } else { + ${3:# else...} + } +# Conditional if..elsif..else +snippet ifee + if {${1}} { + ${2:# body...} + } elseif {${3}} { + ${4:# elsif...} + } else { + ${5:# else...} + } +# If catch then +snippet ifc + if { [catch {${1:#do something...}} ${2:err}] } { + ${3:# handle failure...} + } +# Catch +snippet catch + catch {${1}} ${2:err} ${3:options} +# While Loop +snippet wh + while {${1}} { + ${2:# body...} + } +# For Loop +snippet for + for {set ${2:var} 0} {$$2 < ${1:count}} {${3:incr} $2} { + ${4:# body...} + } +# Foreach Loop +snippet fore + foreach ${1:x} {${2:#list}} { + ${3:# body...} + } +# after ms script... +snippet af + after ${1:ms} ${2:#do something} +# after cancel id +snippet afc + after cancel ${1:id or script} +# after idle +snippet afi + after idle ${1:script} +# after info id +snippet afin + after info ${1:id} +# Expr +snippet exp + expr {${1:#expression here}} +# Switch +snippet sw + switch ${1:var} { + ${3:pattern 1} { + ${4:#do something} + } + default { + ${2:#do something} + } + } +# Case +snippet ca + ${1:pattern} { + ${2:#do something} + }${3} +# Namespace eval +snippet ns + namespace eval ${1:path} {${2:#script...}} +# Namespace current +snippet nsc + namespace current diff --git a/.vim/bundle/vim-snipmate/snippets/tex.snippets b/.vim/bundle/vim-snipmate/snippets/tex.snippets new file mode 100644 index 0000000..22f7316 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/tex.snippets @@ -0,0 +1,115 @@ +# \begin{}...\end{} +snippet begin + \begin{${1:env}} + ${2} + \end{$1} +# Tabular +snippet tab + \begin{${1:tabular}}{${2:c}} + ${3} + \end{$1} +# Align(ed) +snippet ali + \begin{align${1:ed}} + ${2} + \end{align$1} +# Gather(ed) +snippet gat + \begin{gather${1:ed}} + ${2} + \end{gather$1} +# Equation +snippet eq + \begin{equation} + ${1} + \end{equation} +# Unnumbered Equation +snippet \ + \\[ + ${1} + \\] +# Enumerate +snippet enum + \begin{enumerate} + \item ${1} + \end{enumerate} +# Itemize +snippet item + \begin{itemize} + \item ${1} + \end{itemize} +# Description +snippet desc + \begin{description} + \item[${1}] ${2} + \end{description} +# Matrix +snippet mat + \begin{${1:p/b/v/V/B/small}matrix} + ${2} + \end{$1matrix} +# Cases +snippet cas + \begin{cases} + ${1:equation}, &\text{ if }${2:case}\\ + ${3} + \end{cases} +# Split +snippet spl + \begin{split} + ${1} + \end{split} +# Part +snippet part + \part{${1:part name}} % (fold) + \label{prt:${2:$1}} + ${3} + % part $2 (end) +# Chapter +snippet cha + \chapter{${1:chapter name}} % (fold) + \label{cha:${2:$1}} + ${3} + % chapter $2 (end) +# Section +snippet sec + \section{${1:section name}} % (fold) + \label{sec:${2:$1}} + ${3} + % section $2 (end) +# Sub Section +snippet sub + \subsection{${1:subsection name}} % (fold) + \label{sub:${2:$1}} + ${3} + % subsection $2 (end) +# Sub Sub Section +snippet subs + \subsubsection{${1:subsubsection name}} % (fold) + \label{ssub:${2:$1}} + ${3} + % subsubsection $2 (end) +# Paragraph +snippet par + \paragraph{${1:paragraph name}} % (fold) + \label{par:${2:$1}} + ${3} + % paragraph $2 (end) +# Sub Paragraph +snippet subp + \subparagraph{${1:subparagraph name}} % (fold) + \label{subp:${2:$1}} + ${3} + % subparagraph $2 (end) +snippet itd + \item[${1:description}] ${2:item} +snippet figure + ${1:Figure}~\ref{${2:fig:}}${3} +snippet table + ${1:Table}~\ref{${2:tab:}}${3} +snippet listing + ${1:Listing}~\ref{${2:list}}${3} +snippet section + ${1:Section}~\ref{${2:sec:}}${3} +snippet page + ${1:page}~\pageref{${2}}${3} diff --git a/.vim/bundle/vim-snipmate/snippets/vim.snippets b/.vim/bundle/vim-snipmate/snippets/vim.snippets new file mode 100644 index 0000000..64e7807 --- /dev/null +++ b/.vim/bundle/vim-snipmate/snippets/vim.snippets @@ -0,0 +1,32 @@ +snippet header + " File: ${1:`expand('%:t')`} + " Author: ${2:`g:snips_author`} + " Description: ${3} + ${4:" Last Modified: `strftime("%B %d, %Y")`} +snippet guard + if exists('${1:did_`Filename()`}') || &cp${2: || version < 700} + finish + endif + let $1 = 1${3} +snippet f + fun ${1:function_name}(${2}) + ${3:" code} + endf +snippet for + for ${1:needle} in ${2:haystack} + ${3:" code} + endfor +snippet wh + while ${1:condition} + ${2:" code} + endw +snippet if + if ${1:condition} + ${2:" code} + endif +snippet ife + if ${1:condition} + ${2} + else + ${3} + endif diff --git a/.vim/bundle/vim-snipmate/syntax/snippet.vim b/.vim/bundle/vim-snipmate/syntax/snippet.vim new file mode 100644 index 0000000..5e919e7 --- /dev/null +++ b/.vim/bundle/vim-snipmate/syntax/snippet.vim @@ -0,0 +1,19 @@ +" Syntax highlighting for snippet files (used for snipMate.vim) +" Hopefully this should make snippets a bit nicer to write! +syn match snipComment '^#.*' +syn match placeHolder '\${\d\+\(:.\{-}\)\=}' contains=snipCommand +syn match tabStop '\$\d\+' +syn match snipCommand '`.\{-}`' +syn match snippet '^snippet.*' transparent contains=multiSnipText,snipKeyword +syn match multiSnipText '\S\+ \zs.*' contained +syn match snipKeyword '^snippet'me=s+8 contained +syn match snipError "^[^#s\t].*$" + +hi link snipComment Comment +hi link multiSnipText String +hi link snipKeyword Keyword +hi link snipComment Comment +hi link placeHolder Special +hi link tabStop Special +hi link snipCommand String +hi link snipError Error diff --git a/.vim/bundle/vim-tagbar/autoload/tagbar.vim b/.vim/bundle/vim-tagbar/autoload/tagbar.vim new file mode 100644 index 0000000..7377016 --- /dev/null +++ b/.vim/bundle/vim-tagbar/autoload/tagbar.vim @@ -0,0 +1,3508 @@ +" ============================================================================ +" File: tagbar.vim +" Description: List the current file's tags in a sidebar, ordered by class etc +" Author: Jan Larres +" Licence: Vim licence +" Website: http://majutsushi.github.com/tagbar/ +" Version: 2.7 +" Note: This plugin was heavily inspired by the 'Taglist' plugin by +" Yegappan Lakshmanan and uses a small amount of code from it. +" +" Original taglist copyright notice: +" Permission is hereby granted to use and distribute this code, +" with or without modifications, provided that this copyright +" notice is copied with it. Like anything else that's free, +" taglist.vim is provided *as is* and comes with no warranty of +" any kind, either expressed or implied. In no event will the +" copyright holder be liable for any damamges resulting from the +" use of this software. +" ============================================================================ + +scriptencoding utf-8 + +" Initialization {{{1 + +" If another plugin calls an autoloaded Tagbar function on startup before the +" plugin/tagbar.vim file got loaded, load it explicitly +if exists(':Tagbar') == 0 + runtime plugin/tagbar.vim +endif + +if exists(':Tagbar') == 0 + echomsg 'Tagbar: Could not load plugin code, check your runtimepath!' + finish +endif + +" Basic init {{{2 + +redir => s:ftype_out +silent filetype +redir END +if s:ftype_out !~# 'detection:ON' + echomsg 'Tagbar: Filetype detection is turned off, skipping plugin' + unlet s:ftype_out + finish +endif +unlet s:ftype_out + +let g:tagbar#icon_closed = g:tagbar_iconchars[0] +let g:tagbar#icon_open = g:tagbar_iconchars[1] + +let s:type_init_done = 0 +let s:autocommands_done = 0 +let s:statusline_in_use = 0 +let s:init_done = 0 + +" 0: not checked yet; 1: checked and found; 2: checked and not found +let s:checked_ctags = 0 +let s:checked_ctags_types = 0 +let s:ctags_is_uctags = 0 + +let s:new_window = 1 +let s:is_maximized = 0 +let s:winrestcmd = '' +let s:short_help = 1 +let s:nearby_disabled = 0 +let s:paused = 0 +let s:pwin_by_tagbar = 0 +let s:buffer_seqno = 0 +let s:vim_quitting = 0 +let s:last_alt_bufnr = -1 + +let s:window_expanded = 0 +let s:expand_bufnr = -1 +let s:window_pos = { + \ 'pre' : { 'x' : 0, 'y' : 0 }, + \ 'post' : { 'x' : 0, 'y' : 0 } +\} + +let s:delayed_update_files = [] + +let g:loaded_tagbar = 1 + +let s:last_highlight_tline = 0 + +let s:warnings = { + \ 'type': [], + \ 'encoding': 0 +\ } + +" s:Init() {{{2 +function! s:Init(silent) abort + if s:checked_ctags == 2 && a:silent + return 0 + elseif s:checked_ctags != 1 + if !s:CheckForExCtags(a:silent) + return 0 + endif + endif + + if !s:type_init_done + call s:InitTypes() + endif + + if !s:autocommands_done + call s:CreateAutocommands() + call s:AutoUpdate(fnamemodify(expand('%'), ':p'), 0) + endif + + let s:init_done = 1 + return 1 +endfunction + +" s:InitTypes() {{{2 +function! s:InitTypes() abort + call tagbar#debug#log('Initializing types') + + let supported_types = s:GetSupportedFiletypes() + + if s:ctags_is_uctags + let s:known_types = tagbar#types#uctags#init(supported_types) + else + let s:known_types = tagbar#types#ctags#init(supported_types) + endif + + " Use dart_ctags if available + let dart_ctags = s:CheckFTCtags('dart_ctags', 'dart') + if dart_ctags !=# '' + let supported_types['dart'] = 1 + call tagbar#debug#log('Detected dart_ctags, overriding typedef') + let type_dart = tagbar#prototypes#typeinfo#new() + let type_dart.ctagstype = 'dart' + let type_dart.kinds = [ + \ {'short' : 'i', 'long' : 'imports', 'fold' : 1, 'stl' : 0}, + \ {'short' : 'C', 'long' : 'consts', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'F', 'long' : 'functions', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'c', 'long' : 'classes', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'f', 'long' : 'fields', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'm', 'long' : 'methods', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'M', 'long' : 'static methods', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'r', 'long' : 'constructors', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'o', 'long' : 'operators', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'g', 'long' : 'getters', 'fold' : 0, 'stl' : 0}, + \ {'short' : 's', 'long' : 'setters', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'a', 'long' : 'abstract functions', 'fold' : 0, 'stl' : 0}, + \ ] + let type_dart.sro = ':' + let type_dart.kind2scope = { + \ 'c' : 'class' + \ } + let type_dart.scope2kind = { + \ 'class' : 'c' + \ } + let type_dart.ctagsbin = dart_ctags + let type_dart.ctagsargs = '-l' + let type_dart.ftype = 'dart' + call type_dart.createKinddict() + let s:known_types.dart = type_dart + endif + + " Use jsctags/doctorjs if available + let jsctags = s:CheckFTCtags('jsctags', 'javascript') + if jsctags !=# '' + call tagbar#debug#log('Detected jsctags, overriding typedef') + let type_javascript = tagbar#prototypes#typeinfo#new() + let type_javascript.ctagstype = 'javascript' + let type_javascript.kinds = [ + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0, 'stl' : 1} + \ ] + let type_javascript.sro = '.' + let type_javascript.kind2scope = { + \ 'v' : 'namespace', + \ 'f' : 'namespace' + \ } + let type_javascript.scope2kind = { + \ 'namespace' : 'f' + \ } + let type_javascript.ctagsbin = jsctags + let type_javascript.ctagsargs = '-f -' + let type_javascript.ftype = 'javascript' + call type_javascript.createKinddict() + let s:known_types.javascript = type_javascript + endif + + " Use gotags if available + let gotags = s:CheckFTCtags('gotags', 'go') + if gotags !=# '' + call tagbar#debug#log('Detected gotags, overriding typedef') + let type_go = tagbar#prototypes#typeinfo#new() + let type_go.ctagstype = 'go' + let type_go.kinds = [ + \ {'short' : 'p', 'long' : 'package', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'i', 'long' : 'imports', 'fold' : 1, 'stl' : 0}, + \ {'short' : 'c', 'long' : 'constants', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'v', 'long' : 'variables', 'fold' : 0, 'stl' : 0}, + \ {'short' : 't', 'long' : 'types', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'n', 'long' : 'intefaces', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'w', 'long' : 'fields', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'e', 'long' : 'embedded', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'm', 'long' : 'methods', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'r', 'long' : 'constructors', 'fold' : 0, 'stl' : 0}, + \ {'short' : 'f', 'long' : 'functions', 'fold' : 0, 'stl' : 0}, + \ ] + let type_go.sro = '.' + let type_go.kind2scope = { + \ 't' : 'ctype', + \ 'n' : 'ntype' + \ } + let type_go.scope2kind = { + \ 'ctype' : 't', + \ 'ntype' : 'n' + \ } + let type_go.ctagsbin = gotags + let type_go.ctagsargs = '-sort -silent' + let type_go.ftype = 'go' + call type_go.createKinddict() + let s:known_types.go = type_go + endif + + call s:LoadUserTypeDefs() + + " Add an 'unknown' kind to the types for pseudotags that we can't + " determine the correct kind for since they don't have any children that + " are not pseudotags and that therefore don't provide scope information + for typeinfo in values(s:known_types) + if has_key(typeinfo, 'kind2scope') + let unknown_kind = + \ {'short' : '?', 'long' : 'unknown', 'fold' : 0, 'stl' : 1} + " Check for existence first since some types exist under more than + " one name + if index(typeinfo.kinds, unknown_kind) == -1 + call add(typeinfo.kinds, unknown_kind) + endif + let typeinfo.kind2scope['?'] = 'unknown' + endif + endfor + + let s:type_init_done = 1 +endfunction + +" s:LoadUserTypeDefs() {{{2 +function! s:LoadUserTypeDefs(...) abort + if a:0 > 0 + let type = a:1 + + let defdict = {} + let defdict[type] = g:tagbar_type_{type} + else + let defdict = tagbar#getusertypes() + endif + + let transformed = {} + for [type, def] in items(defdict) + let transformed[type] = s:TransformUserTypeDef(def) + let transformed[type].ftype = type + endfor + + for [key, value] in items(transformed) + call tagbar#debug#log("Initializing user type '" . key . "'") + if !has_key(s:known_types, key) || get(value, 'replace', 0) + let s:known_types[key] = tagbar#prototypes#typeinfo#new(value) + else + call extend(s:known_types[key], value) + endif + call s:known_types[key].createKinddict() + endfor +endfunction + +" s:TransformUserTypeDef() {{{2 +" Transform the user definitions into the internal format +function! s:TransformUserTypeDef(def) abort + let newdef = copy(a:def) + + if has_key(a:def, 'kinds') + let newdef.kinds = [] + let kinds = a:def.kinds + for kind in kinds + let kindlist = split(kind, ':') + let kinddict = {'short' : kindlist[0], 'long' : kindlist[1]} + let kinddict.fold = get(kindlist, 2, 0) + let kinddict.stl = get(kindlist, 3, 1) + call add(newdef.kinds, kinddict) + endfor + endif + + " If the user only specified one of kind2scope and scope2kind then use it + " to generate the respective other + if has_key(a:def, 'kind2scope') && !has_key(a:def, 'scope2kind') + let newdef.scope2kind = {} + for [key, value] in items(a:def.kind2scope) + let newdef.scope2kind[value] = key + endfor + elseif has_key(a:def, 'scope2kind') && !has_key(a:def, 'kind2scope') + let newdef.kind2scope = {} + for [key, value] in items(a:def.scope2kind) + let newdef.kind2scope[value] = key + endfor + endif + + return newdef +endfunction + +" s:RestoreSession() {{{2 +" Properly restore Tagbar after a session got loaded +function! s:RestoreSession() abort + if s:init_done + call tagbar#debug#log('Tagbar already initialized; not restoring session') + return + endif + + call tagbar#debug#log('Restoring session') + + let curfile = fnamemodify(bufname('%'), ':p') + + let tagbarwinnr = bufwinnr(s:TagbarBufName()) + if tagbarwinnr == -1 + " Tagbar wasn't open in the saved session, nothing to do + return + endif + + let in_tagbar = 1 + if winnr() != tagbarwinnr + call s:goto_win(tagbarwinnr, 1) + let in_tagbar = 0 + endif + + let s:last_autofocus = 0 + + call s:Init(0) + + call s:InitWindow(g:tagbar_autoclose) + + call s:AutoUpdate(curfile, 0) + + if !in_tagbar + call s:goto_win('p') + endif +endfunction + +" s:MapKeys() {{{2 +function! s:MapKeys() abort + call tagbar#debug#log('Mapping keys') + + nnoremap