Merge commit 'kljohann/master' into next

This commit is contained in:
John Wiegley 2009-06-29 16:17:22 +01:00
commit 752677edf0
2 changed files with 242 additions and 15 deletions

View file

@ -11,17 +11,60 @@ Configuration
Include the following let-statements somewhere in your .vimrc Include the following let-statements somewhere in your .vimrc
to modify the behaviour of the ledger filetype. to modify the behaviour of the ledger filetype.
Number of colums that will be used to display the foldtext. * 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. Set this when you think that the amount is too far off to the right.
let g:ledger_maxwidth = 80 let g:ledger_maxwidth = 80
String that will be used to fill the space between account name * 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 and amount in the foldtext. Set this to get some kind of lines
or visual aid. or visual aid.
let g:ledger_fillstring = ' -' 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.
Revision history * 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 implemented for account names and tags.
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.
Tags
----------------------------------------------------------------------
The support for completing tags is pretty basic right now
but it's useful to keep the spelling of your tags consistent.
You can call the completion after the ';' to get a list of all tags.
When you have a list of tags (:like: :this:) you can call
the completion too and everything up to the last ':' (excluding whitespace)
will be considered the beginning of the tag to search for.
Revision history (major changes)
======================================================================
2009-06-23 & 2009-06-25
J. Klähn: Omni-Completion for account names and tags
2009-06-17 J. Klähn: Highlight account text 2009-06-17 J. Klähn: Highlight account text
Updated documentation and added fillstring option. Updated documentation and added fillstring option.
2009-06-15 J. Klähn: Split into multiple files 2009-06-15 J. Klähn: Split into multiple files
@ -37,7 +80,7 @@ Revision history
2005-02-05 first version (partly copied from ledger.vim 0.0.1) 2005-02-05 first version (partly copied from ledger.vim 0.0.1)
License License
======= ======================================================================
Copyright 2009 by Johann Klähn Copyright 2009 by Johann Klähn
Copyright 2009 by Stefan Karrmann Copyright 2009 by Stefan Karrmann
Copyright 2005 by Wolfgang Oertl Copyright 2005 by Wolfgang Oertl

View file

@ -12,7 +12,7 @@ let b:did_ftplugin = 1
let b:undo_ftplugin = "setlocal ". let b:undo_ftplugin = "setlocal ".
\ "foldmethod< foldtext< ". \ "foldmethod< foldtext< ".
\ "include< comments< iskeyword< " \ "include< comments< omnifunc< "
" don't fill fold lines --> cleaner look " don't fill fold lines --> cleaner look
setl fillchars="fold: " setl fillchars="fold: "
@ -20,10 +20,7 @@ setl foldtext=LedgerFoldText()
setl foldmethod=syntax setl foldmethod=syntax
setl include=^!include setl include=^!include
setl comments=b:; setl comments=b:;
" so you can use C-X C-N completion on accounts setl omnifunc=LedgerComplete
" FIXME: Does not work with something like:
" Assets:Accountname with Spaces
setl iskeyword+=:
" You can set a maximal number of columns the fold text (excluding amount) " You can set a maximal number of columns the fold text (excluding amount)
" will use by overriding g:ledger_maxwidth in your .vimrc. " will use by overriding g:ledger_maxwidth in your .vimrc.
@ -37,6 +34,30 @@ if !exists('g:ledger_fillstring')
let g:ledger_fillstring = ' ' let g:ledger_fillstring = ' '
endif 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 = '\('. let s:rx_amount = '\('.
\ '\%([0-9]\+\)'. \ '\%([0-9]\+\)'.
\ '\%([,.][0-9]\+\)*'. \ '\%([,.][0-9]\+\)*'.
@ -54,7 +75,7 @@ function! LedgerFoldText() "{{{1
let line = getline(lnum) let line = getline(lnum)
" Skip metadata/leading comment " Skip metadata/leading comment
if line !~ '^\s\+;' if line !~ '^\%(\s\+;\|\d\)'
" No comment, look for amount... " No comment, look for amount...
let groups = matchlist(line, s:rx_amount) let groups = matchlist(line, s:rx_amount)
if ! empty(groups) if ! empty(groups)
@ -97,11 +118,149 @@ function! LedgerFoldText() "{{{1
return printf(fmt, foldtext, amount) return printf(fmt, foldtext, amount)
endfunction "}}} endfunction "}}}
function! LedgerComplete(findstart, base) "{{{1
if a:findstart
let lnum = line('.')
let line = getline('.')
let lastcol = col('.') - 2
if line =~ '^\d' "{{{2 (date / payee / description)
let b:compl_context = 'payee'
return -1
elseif line =~ '^\s\+;' "{{{2 (metadata / tags)
let b:compl_context = 'meta-tag'
let first_possible = matchend(line, '^\s\+;')
" find first column of text to be replaced
let firstcol = lastcol
while firstcol >= 0
if firstcol <= first_possible
" Stop before the ';' don't ever include it
let firstcol = first_possible
break
elseif line[firstcol] =~ ':'
" Stop before first ':'
let firstcol += 1
break
endif
let firstcol -= 1
endwhile
" strip whitespace starting from firstcol
let end_of_whitespace = matchend(line, '^\s\+', firstcol)
if end_of_whitespace != -1
let firstcol = end_of_whitespace
endif
return firstcol
elseif line =~ '^\s\+' "{{{2 (account)
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
" the start of the first non-blank character
" (excluding virtual-transaction-marks)
" is the beginning of the account name
return matchend(line, '^\s\+[\[(]\?')
else "}}}
return -1
endif
else
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(LedgerGetAccountHierarchy(), hierarchy)
" sort by alphabet and reverse because it will get reversed one more time
let results = reverse(sort(results))
if g:ledger_detailed_first
let results = sort(results, 's:sort_accounts_by_depth')
endif
call add(results, a:base)
return reverse(results)
elseif b:compl_context == 'meta-tag' "{{{2
unlet! b:compl_context
let results = [a:base]
call extend(results, sort(s:filter_items(keys(LedgerGetTags()), a:base)))
return results
else "}}}
unlet! b:compl_context
return []
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! LedgerGetAccountHierarchy() "{{{1
let hierarchy = {}
let accounts = s:grep_buffer('^\s\+\zs[^[:blank:];]\%(\S \S\|\S\)\+\ze')
for name in accounts
" remove virtual-transaction-marks
let name = substitute(name, '\%(^\s*[\[(]\?\|[\])]\?\s*$\)', '', 'g')
let last = hierarchy
for part in split(name, ':')
let last[part] = get(last, part, {})
let last = last[part]
endfor
endfor
return hierarchy
endf "}}}
function! LedgerGetTags() "{{{1
let alltags = {}
let metalines = s:grep_buffer('^\s\+;\s*\zs.*$')
for line in metalines
" (spaces at beginning are stripped by matchstr!)
if line[0] == ':'
" multiple tags
for val in split(line, ':')
if val !~ '^\s*$'
let name = s:strip_spaces(val)
let alltags[name] = get(alltags, name, [])
endif
endfor
elseif line =~ '^.*:.*$'
" line with tag=value
let name = s:strip_spaces(split(line, ':')[0])
let val = s:strip_spaces(join(split(line, ':')[1:], ':'))
let values = get(alltags, name, [])
call add(values, val)
let alltags[name] = values
endif
endfor
return alltags
endf "}}}
" Helper functions {{{1 " Helper functions {{{1
" return length of string with fix for multibyte characters
function! s:multibyte_strlen(text) "{{{2 function! s:multibyte_strlen(text) "{{{2
return strlen(substitute(a:text, ".", "x", "g")) return strlen(substitute(a:text, ".", "x", "g"))
endfunction "}}} endfunction "}}}
" get # of visible/usable columns in current window
function! s:get_columns(win) "{{{2 function! s:get_columns(win) "{{{2
" As long as vim doesn't provide a command natively, " As long as vim doesn't provide a command natively,
" we have to compute the available columns. " we have to compute the available columns.
@ -116,3 +275,28 @@ function! s:get_columns(win) "{{{2
return columns return columns
endfunction "}}} endfunction "}}}
" 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(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 "}}}