<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -12,7 +12,7 @@ let b:did_ftplugin = 1
 
 let b:undo_ftplugin = &quot;setlocal &quot;.
                     \ &quot;foldmethod&lt; foldtext&lt; &quot;.
-                    \ &quot;include&lt; comments&lt; iskeyword&lt; &quot;
+                    \ &quot;include&lt; comments&lt; omnifunc&lt; &quot;
 
 &quot; don't fill fold lines --&gt; cleaner look
 setl fillchars=&quot;fold: &quot;
@@ -20,10 +20,7 @@ setl foldtext=LedgerFoldText()
 setl foldmethod=syntax
 setl include=^!include
 setl comments=b:;
-&quot; so you can use C-X C-N completion on accounts
-&quot; FIXME: Does not work with something like:
-&quot;          Assets:Accountname with Spaces
-setl iskeyword+=:
+setl omnifunc=LedgerComplete
 
 &quot; You can set a maximal number of columns the fold text (excluding amount)
 &quot; will use by overriding g:ledger_maxwidth in your .vimrc.
@@ -37,6 +34,30 @@ if !exists('g:ledger_fillstring')
   let g:ledger_fillstring = ' '
 endif
 
+&quot; If enabled this will list the most detailed matches at the top {{{
+&quot; of the completion list.
+&quot; For example when you have some accounts like this:
+&quot;   A:Ba:Bu
+&quot;   A:Bu:Bu
+&quot; and you complete on A:B:B normal behaviour may be the following
+&quot;   A:B:B
+&quot;   A:Bu:Bu
+&quot;   A:Bu
+&quot;   A:Ba:Bu
+&quot;   A:Ba
+&quot;   A
+&quot; with this option turned on it will be
+&quot;   A:B:B
+&quot;   A:Bu:Bu
+&quot;   A:Ba:Bu
+&quot;   A:Bu
+&quot;   A:Ba
+&quot;   A
+&quot; }}}
+if !exists('g:ledger_detailed_first')
+  let g:ledger_detailed_first = 0
+endif
+
 let s:rx_amount = '\('.
                 \   '\%([0-9]\+\)'.
                 \   '\%([,.][0-9]\+\)*'.
@@ -54,7 +75,7 @@ function! LedgerFoldText() &quot;{{{1
     let line = getline(lnum)
 
     &quot; Skip metadata/leading comment
-    if line !~ '^\s\+;'
+    if line !~ '^\%(\s\+;\|\d\)'
       &quot; No comment, look for amount...
       let groups = matchlist(line, s:rx_amount)
       if ! empty(groups)
@@ -97,11 +118,149 @@ function! LedgerFoldText() &quot;{{{1
   return printf(fmt, foldtext, amount)
 endfunction &quot;}}}
 
+function! LedgerComplete(findstart, base) &quot;{{{1
+  if a:findstart
+    let lnum = line('.')
+    let line = getline('.')
+    let lastcol = col('.') - 2
+    if line =~ '^\d' &quot;{{{2 (date / payee / description)
+      let b:compl_context = 'payee'
+      return -1
+    elseif line =~ '^\s\+;' &quot;{{{2 (metadata / tags)
+      let b:compl_context = 'meta-tag'
+      let first_possible = matchend(line, '^\s\+;')
+
+      &quot; find first column of text to be replaced
+      let firstcol = lastcol
+      while firstcol &gt;= 0
+        if firstcol &lt;= first_possible
+          &quot; Stop before the ';' don't ever include it
+          let firstcol = first_possible
+          break
+        elseif line[firstcol] =~ ':'
+          &quot; Stop before first ':'
+          let firstcol += 1
+          break
+        endif
+
+        let firstcol -= 1
+      endwhile
+
+      &quot; 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\+' &quot;{{{2 (account)
+      let b:compl_context = 'account'
+      if matchend(line, '^\s\+\%(\S \S\|\S\)\+') &lt;= lastcol
+        &quot; only allow completion when in or at end of account name
+        return -1
+      endif
+      &quot; the start of the first non-blank character
+      &quot; (excluding virtual-transaction-marks)
+      &quot; is the beginning of the account name
+      return matchend(line, '^\s\+[\[(]\?')
+    else &quot;}}}
+      return -1
+    endif
+  else
+    if b:compl_context == 'account' &quot;{{{2 (account)
+      unlet! b:compl_context
+      let hierarchy = split(a:base, ':')
+      if a:base =~ ':$'
+        call add(hierarchy, '')
+      endif
+
+      let results = LedgerFindInTree(LedgerGetAccountHierarchy(), hierarchy)
+      &quot; 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' &quot;{{{2
+      unlet! b:compl_context
+      let results = [a:base]
+      call extend(results, sort(s:filter_items(keys(LedgerGetTags()), a:base)))
+      return results
+    else &quot;}}}
+      unlet! b:compl_context
+      return []
+    endif
+  endif
+endf &quot;}}}
+
+function! LedgerFindInTree(tree, levels) &quot;{{{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 &quot;}}}
+
+function! LedgerGetAccountHierarchy() &quot;{{{1
+  let hierarchy = {}
+  let accounts = s:grep_buffer('^\s\+\zs[^[:blank:];]\%(\S \S\|\S\)\+\ze')
+  for name in accounts
+    &quot; 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 &quot;}}}
+
+function! LedgerGetTags() &quot;{{{1
+  let alltags = {}
+  let metalines = s:grep_buffer('^\s\+;\s*\zs.*$')
+  for line in metalines
+    &quot; (spaces at beginning are stripped by matchstr!)
+    if line[0] == ':'
+      &quot; 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 =~ '^.*:.*$'
+      &quot; 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 &quot;}}}
+
 &quot; Helper functions {{{1
+
+&quot; return length of string with fix for multibyte characters
 function! s:multibyte_strlen(text) &quot;{{{2
    return strlen(substitute(a:text, &quot;.&quot;, &quot;x&quot;, &quot;g&quot;))
 endfunction &quot;}}}
 
+&quot; get # of visible/usable columns in current window
 function! s:get_columns(win) &quot;{{{2
   &quot; As long as vim doesn't provide a command natively,
   &quot; we have to compute the available columns.
@@ -116,3 +275,28 @@ function! s:get_columns(win) &quot;{{{2
   return columns
 endfunction &quot;}}}
 
+&quot; remove spaces at start and end of string
+function! s:strip_spaces(text) &quot;{{{2
+  return matchstr(a:text, '^\s*\zs\S\%(.*\S\)\?\ze\s*$')
+endf &quot;}}}
+
+&quot; return only those items that start with a specified keyword
+function! s:filter_items(list, keyword) &quot;{{{2
+  return filter(a:list, 'v:val =~ ''^\V'.substitute(a:keyword, '\\', '\\\\', 'g').'''')
+endf &quot;}}}
+
+&quot; return all lines matching an expression, returning only the matched part
+function! s:grep_buffer(expression) &quot;{{{2
+  let lines = map(getline(1, '$'), 'matchstr(v:val, '''.a:expression.''')')
+  return filter(lines, 'v:val != &quot;&quot;')
+endf &quot;}}}
+
+function! s:sort_accounts_by_depth(name1, name2) &quot;{{{2
+  let depth1 = s:count_expression(a:name1, ':')
+  let depth2 = s:count_expression(a:name2, ':')
+  return depth1 == depth2 ? 0 : depth1 &gt; depth2 ? 1 : -1
+endf &quot;}}}
+
+function! s:count_expression(text, expression) &quot;{{{2
+  return len(split(a:text, a:expression, 1))-1
+endf &quot;}}}</diff>
      <filename>contrib/vim/ftplugin/ledger.vim</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>5ac73e1a1f97d92f5ab873f40255ba126a2361e8</id>
    </parent>
    <parent>
      <id>2aa9f5115cde5db8b98af45eb0fedfa4955ce6a0</id>
    </parent>
  </parents>
  <author>
    <name>John Wiegley</name>
    <email>johnw@newartisans.com</email>
  </author>
  <url>http://github.com/jwiegley/ledger/commit/752677edf05f3bac8b950ac449723a44740cbfa0</url>
  <id>752677edf05f3bac8b950ac449723a44740cbfa0</id>
  <committed-date>2009-06-29T08:17:22-07:00</committed-date>
  <authored-date>2009-06-29T08:17:22-07:00</authored-date>
  <message>Merge commit 'kljohann/master' into next</message>
  <tree>84d2884302beaaf833461e6c3c0fade1f123aa9e</tree>
  <committer>
    <name>John Wiegley</name>
    <email>johnw@newartisans.com</email>
  </committer>
</commit>
