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
|
" Align currency on decimal point.
function! beancount#align_commodity(line1, line2)
" Saving cursor position to adjust it if necessary.
let cursor_col = col('.')
let cursor_line = line('.')
" This matches the line up to the first dot (or other separator),
" excluding comments.
" Note very nomagic so that the separator is not interpreted as regex.
let separator_regex = '^\V\[^;]\{-}' . g:beancount_decimal_separator
" This lets me increment at start of loop, because of continue statements.
let i = a:line1 - 1
while i < a:line2
let i += 1
let s = getline(i)
" This matches an account name followed by a space. There may be
" some conflicts with non-transaction syntax that I don't know about.
" It won't match a comment or any non-indented line.
let end_acc = matchend(s, '^\v([-\d]+\s+(balance|price))? +\S+[^:] ')
if end_acc < 0 | continue | endif
" Where does commodity amount begin?
let end_space = matchend(s, '^ *', end_acc)
" Find the first decimal point, not counting comments.
let separator = matchend(s, separator_regex, end_space)
if separator < 0
" If there is no separator, pretend there's one after the last digit.
let separator = matchend(s, '^\v[^;]*\d+') + 1
endif
if separator < 0 | continue | endif
let has_spaces = end_space - end_acc
let need_spaces = g:beancount_separator_col - separator + has_spaces
if need_spaces < 0 | continue | endif
call setline(i, s[0 : end_acc - 1] . repeat(" ", need_spaces) . s[ end_space : -1])
if i == cursor_line && cursor_col >= end_acc
" Adjust cursor position for continuity.
call cursor(0, cursor_col + need_spaces - has_spaces)
endif
endwhile
endfunction
function! s:count_expression(text, expression)
return len(split(a:text, a:expression, 1)) - 1
endfunction
function! s:sort_accounts_by_depth(name1, name2)
let l:depth1 = s:count_expression(a:name1, ':')
let l:depth2 = s:count_expression(a:name2, ':')
return depth1 == depth2 ? 0 : depth1 > depth2 ? 1 : -1
endfunction
" Complete account name.
function! beancount#complete_account(findstart, base)
if a:findstart
let l:col = searchpos('\s', "bn", line("."))[1]
if col == 0
return -1
else
return col
endif
endif
if !exists('b:beancount_accounts')
if exists('b:beancount_root')
let l:root = b:beancount_root
else
let l:root = expand('%')
endif
let b:beancount_accounts = beancount#find_accounts(l:root)
endif
if g:beancount_account_completion ==? 'chunks'
let l:pattern = '^\V' . substitute(a:base, ":", '\\[^:]\\*:', "g") . '\[^:]\*'
else
let l:pattern = '^\V\.\*' . substitute(a:base, ":", '\\.\\*:\\.\\*', "g") . '\.\*'
endif
let l:matches = []
let l:index = -1
while 1
let l:index = match(b:beancount_accounts, l:pattern, l:index + 1)
if l:index == -1 | break | endif
call add(l:matches, matchstr(b:beancount_accounts[l:index], l:pattern))
endwhile
if g:beancount_detailed_first
let l:matches = reverse(sort(l:matches, 's:sort_accounts_by_depth'))
endif
return l:matches
endfunction
" Get list of acounts.
function! beancount#find_accounts(root_file)
python << EOM
import collections
import os
import re
import sys
import vim
RE_INCLUDE = re.compile(r'^include\s+"([^\n"]+)"')
RE_ACCOUNT = re.compile(r'^\d{4,}-\d{2}-\d{2}\s+open\s+(\S+)')
def combine_paths(old, new):
return os.path.normpath(
new if os.path.isabs(new) else os.path.join(old, new))
def parse_file(fh, files, accounts):
for line in fh:
m = RE_INCLUDE.match(line)
if m: files.append(combine_paths(os.path.dirname(fh.name), m.group(1)))
m = RE_ACCOUNT.match(line)
if m: accounts.add(m.group(1))
files = collections.deque([vim.eval("a:root_file")])
accounts = set()
seen = set()
while files:
current = files.popleft()
if current in seen:
continue
seen.add(current)
try:
with open(current, 'r') as fh:
parse_file(fh, files, accounts)
except IOError as err:
pass
vim.command('return [{}]'.format(','.join(repr(x) for x in sorted(accounts))))
EOM
endfunction
|