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
|
" 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
" 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
let l:pattern = '\V' . substitute(a:base, ":", '\\S\\*:\\S\\*', "g")
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, b:beancount_accounts[l:index])
endwhile
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):
regexes = ((RE_INCLUDE, files), (RE_ACCOUNT, 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
|