1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
|
" Vim filetype plugin file
" filetype: ledger
" Version: 0.1.0
" by Johann Klähn; Use according to the terms of the GPL>=2.
" vim:ts=2:sw=2:sts=2:foldmethod=marker
if exists("b:did_ftplugin")
finish
endif
let b:did_ftplugin = 1
let b:undo_ftplugin = "setlocal ".
\ "foldmethod< foldtext< ".
\ "include< comments< iskeyword< omnifunc< "
" don't fill fold lines --> cleaner look
setl fillchars="fold: "
setl foldtext=LedgerFoldText()
setl foldmethod=syntax
setl include=^!include
setl comments=b:;
" so you can use C-X C-N completion on accounts
" FIXME: Does not work with something like:
" Assets:Accountname with Spaces
setl iskeyword+=:
setl omnifunc=LedgerComplete
" 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
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
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
let fmt = '%s %s '
" 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(0)
if g:ledger_maxwidth
let columns = min([columns, g:ledger_maxwidth])
endif
let columns -= s:multibyte_strlen(printf(fmt, '', amount))
" add spaces so the text is always long enough when we strip it
" to a certain width (fake table)
if strlen(g:ledger_fillstring)
" add extra spaces so fillstring aligns
let filen = s:multibyte_strlen(g:ledger_fillstring)
let folen = s:multibyte_strlen(foldtext)
let foldtext .= repeat(' ', filen - (folen%filen))
let foldtext .= repeat(g:ledger_fillstring,
\ s:get_columns(0)/filen)
else
let foldtext .= repeat(' ', s:get_columns(0))
endif
" we don't use slices[:5], because that messes up multibyte characters
let foldtext = substitute(foldtext, '.\{'.columns.'}\zs.*$', '', '')
return printf(fmt, foldtext, amount)
endfunction "}}}
function! LedgerComplete(findstart, base)
if a:findstart
let lnum = line('.')
let line = getline('.')
let lastcol = col('.') - 2
if line =~ '^\d'
let b:compl_context = 'payee'
return -1
elseif line =~ '^\s\+;'
let b:compl_context = 'meta'
return -1
elseif line =~ '^\s\+'
let b:compl_context = 'account'
if matchend(line, '^\s\+\%(\S \S\|\S\)\+') <= lastcol
" only allow completion when in or at end of account name
return -1
endif
return matchend(line, '^\s\+')
else
return -1
endif
else
if b:compl_context == 'account'
unlet! b:compl_context
let hierarchy = split(a:base, ':')
if a:base =~ ':$'
call add(hierarchy, '')
endif
let results = LedgerFindInTree(LedgerGetAccountHierarchy(), hierarchy)
call add(results, a:base)
return reverse(results)
else
unlet! b:compl_context
return []
endif
endif
endf
function! LedgerFindInTree(tree, levels)
if empty(a:levels)
return []
endif
let results = []
let currentlvl = a:levels[0]
let nextlvls = a:levels[1:]
let branches = filter(keys(a:tree), 'v:val =~ ''^\V'.substitute(currentlvl, '\\', '\\\\', 'g').'''')
for branch in branches
call add(results, branch)
if !empty(nextlvls)
for result in LedgerFindInTree(a:tree[branch], nextlvls)
call add(results, branch.':'.result)
endfor
endif
endfor
return results
endf
function! LedgerGetAccountHierarchy()
let hierarchy = {}
let accounts = map(getline(1, '$'), 'matchstr(v:val, ''^\s\+\zs[^[:blank:];]\%(\S \S\|\S\)\+\ze'')')
let accounts = filter(accounts, 'v:val != ""')
for name in accounts
let last = hierarchy
for part in split(name, ':')
let last[part] = get(last, part, {})
let last = last[part]
endfor
endfor
return hierarchy
endf
" Helper functions {{{1
function! s:multibyte_strlen(text) "{{{2
return strlen(substitute(a:text, ".", "x", "g"))
endfunction "}}}
function! s:get_columns(win) "{{{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()/
" FIXME: Although this will propably never be used with debug mode enabled
" this should take the signs column into account (:help sign.txt)
let columns = (winwidth(a:win) == 0 ? 80 : winwidth(a:win)) - &foldcolumn
if &number
" line('w$') is the line number of the last line
let columns -= max([len(line('w$'))+1, &numberwidth])
endif
return columns
endfunction "}}}
|