Add ledger Vim/Pathogen bundle
This commit is contained in:
parent
1a24501ca0
commit
ff8c074079
4 changed files with 769 additions and 0 deletions
89
vim/bundle/ledger/README
Normal file
89
vim/bundle/ledger/README
Normal file
|
@ -0,0 +1,89 @@
|
|||
|
||||
This is the ledger filetype for vim.
|
||||
Copy each file to the corresponding directory in your ~/.vim directory.
|
||||
Then include the following line in your .vimrc or in ~/.vim/filetype.vim
|
||||
au BufNewFile,BufRead *.ldg,*.ledger setf ledger | comp ledger
|
||||
You can also use a modeline like this in every ledger file
|
||||
vim:filetype=ledger
|
||||
|
||||
Tips and useful commands
|
||||
======================================================================
|
||||
• Try account-completion (as explained below)
|
||||
• :call LedgerSetDate(line('.'), 'effective')
|
||||
will set today's date as the effective date of the current
|
||||
transaction. You can use also 'actual' in place of 'effective'
|
||||
or pass in a different date measured as seconds since 1st Jan 1970.
|
||||
• :call LedgerSetTransactionState(line('.'), '*')
|
||||
sets the state of the current transaction to '*'. You can
|
||||
use this in custom mappings.
|
||||
• :call LedgerToggleTransactionState(line('.'), ' *?!')
|
||||
will toggle through the provided transaction states.
|
||||
You can map this to double-clicking for example:
|
||||
noremap <silent><buffer> <2-LeftMouse>
|
||||
\ :call LedgerToggleTransactionState(line('.'), ' *?!')<CR>
|
||||
|
||||
Configuration
|
||||
======================================================================
|
||||
Include the following let-statements somewhere in your .vimrc
|
||||
to modify the behaviour of the ledger filetype.
|
||||
|
||||
* Number of colums 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 = ' -'
|
||||
My special tip is to use so-called digraphs:
|
||||
Press <C-K> followed by the two-characters key sequence below.
|
||||
(in insert-mode)
|
||||
'. = ˙ or ': = ¨ --> ˙˙˙˙˙˙ or ¨¨¨¨¨¨
|
||||
', = ¸ --> ¸¸¸¸¸¸
|
||||
.M = · --> ······
|
||||
>> = » --> »»»»»»
|
||||
All those look rather unobstrusive
|
||||
and provide a good visual aid to find the correct amount.
|
||||
|
||||
* 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
|
||||
|
||||
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<C-X><C-O>
|
||||
You will get a list of top-level accounts that start like this.
|
||||
|
||||
Go ahead and try something like:
|
||||
As:Ban:Che<C-X><C-O>
|
||||
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 2011-2009 by Johann Klähn
|
||||
Copyright 2009 by Stefan Karrmann
|
||||
Copyright 2005 by 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 <http://www.gnu.org/licenses/>.
|
||||
|
28
vim/bundle/ledger/compiler/ledger.vim
Normal file
28
vim/bundle/ledger/compiler/ledger.vim
Normal file
|
@ -0,0 +1,28 @@
|
|||
" 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
|
||||
|
||||
if exists("current_compiler")
|
||||
finish
|
||||
endif
|
||||
let current_compiler = "ledger"
|
||||
|
||||
if exists(":CompilerSet") != 2
|
||||
command -nargs=* CompilerSet setlocal <args>
|
||||
endif
|
||||
|
||||
" default value will be set in ftplugin
|
||||
if ! exists("g:ledger_bin") || empty(g:ledger_bin) || ! executable(split(g:ledger_bin, '\s')[0])
|
||||
finish
|
||||
endif
|
||||
|
||||
" %-G throws away blank lines, everything else is assumed to be part of a
|
||||
" multi-line error message.
|
||||
CompilerSet errorformat=%-G,%EWhile\ parsing\ file\ \"%f\"\\,\ line\ %l:%.%#,%ZError:\ %m,%C%.%#
|
||||
CompilerSet errorformat+=%tarning:\ \"%f\"\\,\ line\ %l:\ %m
|
||||
|
||||
" unfortunately there is no 'check file' command,
|
||||
" so we will just use a query that returns no results. ever.
|
||||
exe 'CompilerSet makeprg='.substitute(g:ledger_bin, ' ', '\\ ', 'g').'\ -f\ %\ reg\ not\ ''.*''\ \>\ /dev/null'
|
||||
|
597
vim/bundle/ledger/ftplugin/ledger.vim
Normal file
597
vim/bundle/ledger/ftplugin/ledger.vim
Normal file
|
@ -0,0 +1,597 @@
|
|||
" 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
|
||||
|
||||
if exists("b:did_ftplugin")
|
||||
finish
|
||||
endif
|
||||
|
||||
let b:did_ftplugin = 1
|
||||
|
||||
let b:undo_ftplugin = "setlocal ".
|
||||
\ "foldmethod< foldtext< ".
|
||||
\ "include< comments< omnifunc< formatprg<"
|
||||
|
||||
" don't fill fold lines --> cleaner look
|
||||
setl fillchars="fold: "
|
||||
setl foldtext=LedgerFoldText()
|
||||
setl foldmethod=syntax
|
||||
setl include=^!include
|
||||
setl comments=b:;
|
||||
setl omnifunc=LedgerComplete
|
||||
|
||||
" set location of ledger binary for checking and auto-formatting
|
||||
if ! exists("g:ledger_bin") || empty(g:ledger_bin) || ! executable(split(g:ledger_bin, '\s')[0])
|
||||
if executable('ledger')
|
||||
let g:ledger_bin = 'ledger'
|
||||
else
|
||||
unlet g:ledger_bin
|
||||
echoerr "ledger command not found. Set g:ledger_bin or extend $PATH ".
|
||||
\ "to enable error checking and auto-formatting."
|
||||
endif
|
||||
endif
|
||||
|
||||
if exists("g:ledger_bin")
|
||||
exe 'setl formatprg='.substitute(g:ledger_bin, ' ', '\\ ', 'g').'\ -f\ -\ print'
|
||||
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 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 = 0
|
||||
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()
|
||||
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()/filen)
|
||||
else
|
||||
let foldtext .= repeat(' ', s:get_columns())
|
||||
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) "{{{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-marks)
|
||||
" is the beginning of the account name
|
||||
let b:compl_context = 'account'
|
||||
return matchend(line, '^\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 results = []
|
||||
if b:compl_context == 'account' "{{{2 (account)
|
||||
unlet! b:compl_context
|
||||
let hierarchy = split(a:base, ':')
|
||||
if a:base =~ ':$'
|
||||
call add(hierarchy, '')
|
||||
endif
|
||||
|
||||
let results = LedgerFindInTree(b:compl_cache.accounts, hierarchy)
|
||||
" sort by alphabet and reverse because it will get reversed one more time
|
||||
if g:ledger_detailed_first
|
||||
let results = reverse(sort(results, 's:sort_accounts_by_depth'))
|
||||
else
|
||||
let results = sort(results)
|
||||
endif
|
||||
call insert(results, a:base)
|
||||
elseif b:compl_context == 'description' "{{{2 (description)
|
||||
let results = [a:base] + s:filter_items(b:compl_cache.descriptions, a:base)
|
||||
elseif b:compl_context == 'new' "{{{2 (new line)
|
||||
return [strftime('%Y/%m/%d')]
|
||||
endif "}}}
|
||||
|
||||
" no completion (apart from a:base) found. update cache if file has changed
|
||||
if len(results) <= 1 && b:compl_cache['#'] != changenr()
|
||||
unlet b:compl_cache
|
||||
return LedgerComplete(a:findstart, a:base)
|
||||
else
|
||||
unlet! b:compl_context
|
||||
return results
|
||||
endif
|
||||
endif
|
||||
endf "}}}
|
||||
|
||||
function! LedgerFindInTree(tree, levels) "{{{1
|
||||
if empty(a:levels)
|
||||
return []
|
||||
endif
|
||||
let results = []
|
||||
let currentlvl = a:levels[0]
|
||||
let nextlvls = a:levels[1:]
|
||||
let branches = s:filter_items(keys(a:tree), currentlvl)
|
||||
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! LedgerToggleTransactionState(lnum, ...)
|
||||
if a:0 == 1
|
||||
let chars = a:1
|
||||
else
|
||||
let chars = ' *'
|
||||
endif
|
||||
let trans = s:transaction.from_lnum(a:lnum)
|
||||
if empty(trans)
|
||||
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! LedgerSetTransactionState(lnum, char) "{{{1
|
||||
" modifies or sets the state of the transaction at the cursor,
|
||||
" removing the state alltogether if a:char is empty
|
||||
let trans = s:transaction.from_lnum(a:lnum)
|
||||
if empty(trans)
|
||||
return
|
||||
endif
|
||||
|
||||
call trans.set_state(a:char)
|
||||
|
||||
call setline(trans['head'], trans.format_head())
|
||||
endf "}}}
|
||||
|
||||
function! LedgerSetDate(lnum, type, ...) "{{{1
|
||||
let time = a:0 == 1 ? a:1 : localtime()
|
||||
let trans = s:transaction.from_lnum(a:lnum)
|
||||
if empty(trans)
|
||||
return
|
||||
endif
|
||||
|
||||
let formatted = strftime('%Y/%m/%d', time)
|
||||
if has_key(trans, 'date') && ! empty(trans['date'])
|
||||
let date = split(trans['date'], '=')
|
||||
else
|
||||
let date = [formatted]
|
||||
endif
|
||||
|
||||
if a:type ==? 'actual'
|
||||
let date[0] = formatted
|
||||
elseif a:type ==? 'effective'
|
||||
if time < 0
|
||||
" remove effective date
|
||||
let date = [date[0]]
|
||||
else
|
||||
" set effective date
|
||||
if len(date) >= 2
|
||||
let date[1] = formatted
|
||||
else
|
||||
call add(date, formatted)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
let trans['date'] = join(date, '=')
|
||||
|
||||
call setline(trans['head'], trans.format_head())
|
||||
endf "}}}
|
||||
|
||||
function! s:collect_completion_data() "{{{1
|
||||
let transactions = s:get_transactions()
|
||||
let cache = {'descriptions': [], 'tags': {}, 'accounts': {}}
|
||||
let accounts = []
|
||||
for xact in transactions
|
||||
" collect descriptions
|
||||
if index(cache.descriptions, xact['description']) < 0
|
||||
call add(cache.descriptions, xact['description'])
|
||||
endif
|
||||
let [t, postings] = xact.parse_body()
|
||||
let tagdicts = [t]
|
||||
|
||||
" collect account names
|
||||
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
|
||||
|
||||
" 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 "}}}
|
||||
|
||||
let s:transaction = {} "{{{1
|
||||
function! s:transaction.new() dict
|
||||
return copy(s:transaction)
|
||||
endf
|
||||
|
||||
function! s:transaction.from_lnum(lnum) 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] !~ '^\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) dict "{{{2
|
||||
if has_key(self, 'state') && a:char =~ '^\s*$'
|
||||
call remove(self, 'state')
|
||||
else
|
||||
let self['state'] = a:char
|
||||
endif
|
||||
endf "}}}
|
||||
|
||||
function! s:transaction.parse_body(...) 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
|
||||
" FIXME: replaces original spacing in amount with single spaces
|
||||
let parts = split(line[0], '\%(\t\| \)\s*')
|
||||
call add(postings, {'account': parts[0], 'amount': join(parts[1:], ' ')})
|
||||
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() dict "{{{2
|
||||
if has_key(self, 'expr')
|
||||
return '~ '.self['expr']
|
||||
endif
|
||||
|
||||
let parts = []
|
||||
if has_key(self, 'date') | call add(parts, self['date']) | endif
|
||||
if has_key(self, 'code') | call add(parts, '('.self['code'].')') | endif
|
||||
if has_key(self, 'state') | call add(parts, self['state']) | 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 {{{1
|
||||
|
||||
function! s:get_transactions(...) "{{{2
|
||||
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:]]\S\+', 'cW')
|
||||
endw
|
||||
|
||||
" restore view / position
|
||||
let &foldenable = fe
|
||||
call winrestview(view)
|
||||
|
||||
return transactions
|
||||
endf "}}}
|
||||
|
||||
function! s:get_transaction_extents(lnum) "{{{2
|
||||
if ! (indent(a:lnum) || getline(a:lnum) =~ '^[~[:digit:]]\S\+')
|
||||
" 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:]]\S\+', '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! s:findall(text, rx) " {{{2
|
||||
" 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 "}}}
|
||||
|
||||
" 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 "}}}
|
||||
|
||||
" remove spaces at start and end of string
|
||||
function! s:strip_spaces(text) "{{{2
|
||||
return matchstr(a:text, '^\s*\zs\S\%(.*\S\)\?\ze\s*$')
|
||||
endf "}}}
|
||||
|
||||
" return only those items that start with a specified keyword
|
||||
function! s:filter_items(list, keyword) "{{{2
|
||||
return filter(copy(a:list), 'v:val =~ ''^\V'.substitute(a:keyword, '\\', '\\\\', 'g').'''')
|
||||
endf "}}}
|
||||
|
||||
" return all lines matching an expression, returning only the matched part
|
||||
function! s:grep_buffer(expression) "{{{2
|
||||
let lines = map(getline(1, '$'), 'matchstr(v:val, '''.a:expression.''')')
|
||||
return filter(lines, 'v:val != ""')
|
||||
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 "}}}
|
55
vim/bundle/ledger/syntax/ledger.vim
Normal file
55
vim/bundle/ledger/syntax/ledger.vim
Normal file
|
@ -0,0 +1,55 @@
|
|||
" 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
|
||||
|
||||
if version < 600
|
||||
syntax clear
|
||||
elseif exists("b:current_sytax")
|
||||
finish
|
||||
endif
|
||||
|
||||
" for debugging
|
||||
syntax clear
|
||||
|
||||
" DATE[=EDATE] [*|!] [(CODE)] DESC <-- first line of transaction
|
||||
" ACCOUNT AMOUNT [; NOTE] <-- posting
|
||||
|
||||
syn region ledgerTransaction start=/^[[:digit:]~]/ skip=/^\s/ end=/^/
|
||||
\ fold keepend transparent contains=ledgerTransactionDate,ledgerMetadata,ledgerPosting
|
||||
syn match ledgerTransactionDate /^\d\S\+/ contained
|
||||
syn match ledgerPosting /^\s\+[^[:blank:];][^;]*\ze\%($\|;\)/
|
||||
\ contained transparent contains=ledgerAccount,ledgerMetadata
|
||||
" 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
|
||||
syn match ledgerAccount /^\s\+\zs\%(\S\@<= \S\|\S\)\+\ze\%( \|\t\|\s*$\)/ contained
|
||||
|
||||
syn match ledgerComment /^;.*$/
|
||||
" comments at eol must be preceeded by at least 2 spaces / 1 tab
|
||||
syn region ledgerMetadata start=/\%( \|\t\|^\s\+\);/ skip=/^\s\+;/ end=/^/
|
||||
\ keepend contained contains=ledgerTag,ledgerTypedTag
|
||||
syn match ledgerTag /:[^:]\+:/hs=s+1,he=e-1 contained
|
||||
syn match ledgerTag /\%(\%(;\|^tag\)[^:]\+\)\@<=[^:]\+:\ze[^:]\+$/ contained
|
||||
syn match ledgerTypedTag /\%(\%(;\|^tag\)[^:]\+\)\@<=[^:]\+::\ze[^:]\+$/ contained
|
||||
|
||||
syn region ledgerTagStack
|
||||
\ matchgroup=ledgerTagPush start=/^tag\>/
|
||||
\ matchgroup=ledgerTagPop end=/^pop\>/
|
||||
\ contains=ledgerTagHead,ledgerTagStack,ledgerTransaction,ledgerComment
|
||||
syn match ledgerTagHead /\%(^tag\s\+\)\@<=\S.*$/ contained contains=ledgerTag transparent
|
||||
|
||||
highlight default link ledgerTransactionDate Constant
|
||||
highlight default link ledgerMetadata Tag
|
||||
highlight default link ledgerTypedTag Keyword
|
||||
highlight default link ledgerTag Type
|
||||
highlight default link ledgerTagPop Tag
|
||||
highlight default link ledgerTagPush Tag
|
||||
highlight default link ledgerAccount Identifier
|
||||
|
||||
" syncinc is easy: search for the first transaction.
|
||||
syn sync clear
|
||||
syn sync match ledgerSync grouphere ledgerTransaction "^[[:digit:]~]"
|
||||
|
||||
let b:current_syntax = "ledger"
|
Loading…
Add table
Add a link
Reference in a new issue