diff --git a/.vim/pack/plugins/start/vim-ledger/COPYING b/.vim/pack/plugins/start/vim-ledger/COPYING new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/.vim/pack/plugins/start/vim-ledger/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/.vim/pack/plugins/start/vim-ledger/README.mkd b/.vim/pack/plugins/start/vim-ledger/README.mkd new file mode 100644 index 0000000..eaaccc7 --- /dev/null +++ b/.vim/pack/plugins/start/vim-ledger/README.mkd @@ -0,0 +1,128 @@ +vim-ledger +========== + +This is the ledger filetype for Vim. + +Usage +----- + +Copy each file to the corresponding directory in your `~/.vim` directory or +install using [Pathogen](https://github.com/tpope/vim-pathogen). + +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 ledger#transaction_date_set(line('.'), 'auxiliary')` + + will set today's date as the auxiliary date of the current transaction. You + can use also `primary` or `unshift` in place of `auxiliary`. When you pass + "unshift" the old primary date will be set as the auxiliary date and today's + date will be set as the new primary date. + To use a different date pass a date measured in seconds since 1st Jan 1970 + as the third argument. + +* `:call ledger#transaction_state_set(line('.'), '*')` + + sets the state of the current transaction to '*'. You can use this in custom + mappings. + +* `:call ledger#transaction_state_toggle(line('.'), ' *?!')` + + will toggle through the provided transaction states. You can map this to + double-clicking for example: + + noremap <2-LeftMouse>\ + :call ledger#transaction_state_toggle(line('.'), ' *?!') + +* Align commodities at the decimal point. See `help ledger-tips`. + +* `:call ledger#entry()` + + will replace the text on the current line with a new transaction based + on the replaced text. + +Configuration +------------- + +Include the following let-statements somewhere in your `.vimrc` to modify the +behaviour of the ledger filetype. + +* Number of columns 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 = ' -' + +* 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 + +* By default vim will fold ledger transactions, leaving surrounding blank lines + unfolded. You can use `g:ledger_fold_blanks` to hide blank lines following a + transaction. + + let g:ledger_fold_blanks = 0 + + A value of 0 will disable folding of blank lines, 1 will allow folding of a + single blank line between transactions; any larger value will enable folding + undconditionally. + + Note that only lines containing no trailing spaces are considered for + folding. You can take advantage of this to disable this feature on a + case-by-case basis. + +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 + +You will get a list of top-level accounts that start like this. + +Go ahead and try something like: + + As:Ban:Che + +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 2019 Caleb Maclennan +Copyright 2009–2017 Johann Klähn +Copyright 2009 Stefan Karrmann +Copyright 2005 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 . diff --git a/.vim/pack/plugins/start/vim-ledger/autoload/ledger.vim b/.vim/pack/plugins/start/vim-ledger/autoload/ledger.vim new file mode 100644 index 0000000..5a11d59 --- /dev/null +++ b/.vim/pack/plugins/start/vim-ledger/autoload/ledger.vim @@ -0,0 +1,733 @@ +" vim:ts=2:sw=2:sts=2:foldmethod=marker +function! ledger#transaction_state_toggle(lnum, ...) + if a:0 == 1 + let chars = a:1 + else + let chars = ' *' + endif + let trans = s:transaction.from_lnum(a:lnum) + if empty(trans) || has_key(trans, 'expr') + 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! ledger#transaction_state_set(lnum, char) + " modifies or sets the state of the transaction at the cursor, + " removing the state altogether if a:char is empty + let trans = s:transaction.from_lnum(a:lnum) + if empty(trans) || has_key(trans, 'expr') + return + endif + + call trans.set_state(a:char) + + call setline(trans['head'], trans.format_head()) +endf + +function! ledger#transaction_date_set(lnum, type, ...) "{{{1 + let time = a:0 == 1 ? a:1 : localtime() + let trans = s:transaction.from_lnum(a:lnum) + if empty(trans) || has_key(trans, 'expr') + return + endif + + let formatted = strftime(g:ledger_date_format, time) + if has_key(trans, 'date') && ! empty(trans['date']) + let date = split(trans['date'], '=') + else + let date = [formatted] + endif + + if a:type =~? 'effective\|actual' + echoerr "actual/effective arguments were replaced by primary/auxiliary" + return + endif + + if a:type ==? 'primary' + let date[0] = formatted + elseif a:type ==? 'auxiliary' + if time < 0 + " remove auxiliary date + let date = [date[0]] + else + " set auxiliary date + if len(date) >= 2 + let date[1] = formatted + else + call add(date, formatted) + endif + endif + elseif a:type ==? 'unshift' + let date = [formatted, date[0]] + endif + + let trans['date'] = join(date[0:1], '=') + + call setline(trans['head'], trans.format_head()) +endf "}}} + +" == get transactions == + +function! ledger#transaction_from_lnum(lnum) + return s:transaction.from_lnum(a:lnum) +endf + +function! ledger#transactions(...) + 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:]]', 'cW') + endw + + " restore view / position + let &foldenable = fe + call winrestview(view) + + return transactions +endf + +" == transaction object implementation == + +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] ==# '=' + let trans['auto'] = 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 + let [state, rest] = matchlist(line[0], '^\s\+\([*!]\?\)\s*\(.*\)$')[1:2] + if rest =~ '\t\| ' + let [account, amount] = matchlist(rest, '^\(.\{-}\)\%(\t\| \)\s*\(.\{-}\)\s*$')[1:2] + else + let amount = '' + let account = matchstr(rest, '^\s*\zs.\{-}\ze\s*$') + endif + call add(postings, {'account': account, 'amount': amount, 'state': state}) + 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'] + elseif has_key(self, 'auto') + return '= '.self['auto'] + endif + + let parts = [] + if has_key(self, 'date') | call add(parts, self['date']) | endif + if has_key(self, 'state') | call add(parts, self['state']) | endif + if has_key(self, 'code') | call add(parts, '('.self['code'].')') | 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 == + +" get a list of declared accounts in the buffer +function! ledger#declared_accounts(...) + if a:0 == 2 + let lnum = a:1 + let lend = a:2 + elseif a:0 == 0 + let lnum = 1 + let lend = line('$') + else + throw "wrong number of arguments for ledger#declared_accounts()" + return [] + endif + + " save view / position + let view = winsaveview() + let fe = &foldenable + set nofoldenable + + let accounts = [] + call cursor(lnum, 0) + while 1 + let lnum = search('^account\s', 'cW', lend) + if !lnum || lnum > lend + break + endif + + " remove comments at the end and "account" at the front + let line = split(getline(lnum), '\s\+;')[0] + let line = matchlist(line, 'account\s\+\(.\+\)')[1] + + if len(line) > 1 + call add(accounts, line) + endif + + call cursor(lnum+1,0) + endw + + " restore view / position + let &foldenable = fe + call winrestview(view) + + return accounts +endf + +function! s:get_transaction_extents(lnum) + if ! (indent(a:lnum) || getline(a:lnum) =~ '^[~=[:digit:]]') + " 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:]]', '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! ledger#find_in_tree(tree, levels) + if empty(a:levels) + return [] + endif + let results = [] + let currentlvl = a:levels[0] + let nextlvls = a:levels[1:] + let branches = ledger#filter_items(keys(a:tree), currentlvl) + for branch in branches + let exact = empty(nextlvls) + call add(results, [branch, exact]) + if ! empty(nextlvls) + for [result, exact] in ledger#find_in_tree(a:tree[branch], nextlvls) + call add(results, [branch.':'.result, exact]) + endfor + endif + endfor + return results +endf + +function! ledger#filter_items(list, keyword) + " return only those items that start with a specified keyword + return filter(copy(a:list), 'v:val =~ ''^\V'.substitute(a:keyword, '\\', '\\\\', 'g').'''') +endf + +function! s:findall(text, rx) + " 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 + +" Move the cursor to the specified column, filling the line with spaces if necessary. +" Ensure that at least min_spaces are added, and go to the end of the line if +" the line is already too long +function! s:goto_col(pos, min_spaces) + exec "normal!" "$" + let diff = max([a:min_spaces, a:pos - virtcol('.')]) + if diff > 0 | exec "normal!" diff . "a " | endif +endf + +" Return character position of decimal separator (multibyte safe) +function! s:decimalpos(expr) + let pos = match(a:expr, '\V' . g:ledger_decimal_sep) + if pos > 0 + let pos = strchars(a:expr[:pos]) - 1 + endif + return pos +endf + +" Align the amount expression after an account name at the decimal point. +" +" This function moves the amount expression of a posting so that the decimal +" separator is aligned at the column specified by g:ledger_align_at. +" +" For example, after selecting: +" +" 2015/05/09 Some Payee +" Expenses:Other $120,23 ; Tags here +" Expenses:Something $-4,99 +" Expenses:More ($12,34 + $16,32) +" +" :'<,'>call ledger#align_commodity() produces: +" +" 2015/05/09 Some Payee +" Expenses:Other $120,23 ; Tags here +" Expenses:Something $-4,99 +" Expenses:More ($12,34 + $16,32) +" +function! ledger#align_commodity() + " Extract the part of the line after the account name (excluding spaces): + let rhs = matchstr(getline('.'), '\m^\s\+[^;[:space:]].\{-}\(\t\| \)\s*\zs.*$') + if rhs != '' + " Remove everything after the account name (including spaces): + .s/\m^\s\+[^[:space:]].\{-}\zs\(\t\| \).*$// + let pos = -1 + if g:ledger_decimal_sep != '' + " Find the position of the first decimal separator: + let pos = s:decimalpos(rhs) + endif + if pos < 0 + " Find the position after the first digits + let pos = matchend(rhs, '\m\d[^[:space:]]*') + endif + " Go to the column that allows us to align the decimal separator at g:ledger_align_at: + if pos > 0 + call s:goto_col(g:ledger_align_at - pos - 1, 2) + else + call s:goto_col(g:ledger_align_at - strdisplaywidth(rhs) - 2, 2) + endif " Append the part of the line that was previously removed: + exe 'normal! a' . rhs + endif +endf! + +" Align the amount under the cursor and append/prepend the default currency. +function! ledger#align_amount_at_cursor() + " Select and cut text: + normal! viWd + " Find the position of the decimal separator + let pos = s:decimalpos(@") " Returns zero when the separator is the empty string + if pos <= 0 + let pos = len(@") + endif + " Paste text at the correct column and append/prepend default commodity: + if g:ledger_commodity_before + call s:goto_col(g:ledger_align_at - pos - len(g:ledger_default_commodity) - len(g:ledger_commodity_sep) - 1, 2) + exe 'normal! a' . g:ledger_default_commodity . g:ledger_commodity_sep + normal! p + else + call s:goto_col(g:ledger_align_at - pos - 1, 2) + exe 'normal! pa' . g:ledger_commodity_sep . g:ledger_default_commodity + endif +endf! + +" Report generation {{{1 + +" Helper functions and variables {{{2 +" Position of report windows +let s:winpos_map = { + \ "T": "to new", "t": "abo new", "B": "bo new", "b": "bel new", + \ "L": "to vnew", "l": "abo vnew", "R": "bo vnew", "r": "bel vnew" + \ } + +function! s:error_message(msg) + redraw " See h:echo-redraw + echohl ErrorMsg + echo "\r" + echomsg a:msg + echohl NONE +endf + +function! s:warning_message(msg) + redraw " See h:echo-redraw + echohl WarningMsg + echo "\r" + echomsg a:msg + echohl NONE +endf + +" Open the quickfix/location window when it is not empty, +" closes it if it is empty. +" +" Optional parameters: +" a:1 Quickfix window title. +" a:2 Message to show when the window is empty. +" +" Returns 0 if the quickfix window is empty, 1 otherwise. +function! s:quickfix_toggle(...) + if g:ledger_use_location_list + let l:list = 'l' + let l:open = (len(getloclist(winnr())) > 0) + else + let l:list = 'c' + let l:open = (len(getqflist()) > 0) + endif + + if l:open + execute (g:ledger_qf_vertical ? 'vert' : 'botright') l:list.'open' g:ledger_qf_size + " Set local mappings to quit the quickfix window or lose focus. + nnoremap + execute 'nnoremap q :' l:list.'close' + " Note that the following settings do not persist (e.g., when you close and re-open the quickfix window). + " See: https://superuser.com/questions/356912/how-do-i-change-the-quickix-title-status-bar-in-vim + if g:ledger_qf_hide_file + setl conceallevel=2 + setl concealcursor=nc + syntax match qfFile /^[^|]*/ transparent conceal + endif + if a:0 > 0 + let w:quickfix_title = a:1 + endif + return 1 + endif + + execute l:list.'close' + call s:warning_message((a:0 > 1) ? a:2 : 'No results') + return 0 +endf + +" Populate a quickfix/location window with data. The argument must be a String +" or a List. +function! s:quickfix_populate(data) + " Note that cexpr/lexpr always uses the global value of errorformat + let l:efm = &errorformat " Save global errorformat + set errorformat=%EWhile\ parsing\ file\ \"%f\"\\,\ line\ %l:,%ZError:\ %m,%-C%.%# + set errorformat+=%tarning:\ \"%f\"\\,\ line\ %l:\ %m + " Format to parse command-line errors: + set errorformat+=Error:\ %m + " Format to parse reports: + set errorformat+=%f:%l\ %m + set errorformat+=%-G%.%# + execute (g:ledger_use_location_list ? 'l' : 'c').'getexpr' 'a:data' + let &errorformat = l:efm " Restore global errorformat + return +endf + +" Build a ledger command to process the given file. +function! s:ledger_cmd(file, args) + return join([g:ledger_bin, g:ledger_extra_options, '-f', shellescape(expand(a:file)), a:args]) +endf +" }}} + +function! ledger#autocomplete_and_align() + if pumvisible() + return "\" + endif + " Align an amount only if there is a digit immediately before the cursor and + " such digit is preceded by at least one space (the latter condition is + " necessary to avoid situations where a date starting at the first column is + " confused with a commodity to be aligned). + if match(getline('.'), '\s.*\d\%'.col('.').'c') > -1 + norm h + call ledger#align_amount_at_cursor() + return "\A" + endif + return "\\" +endf + +" Use current line as input to ledger entry and replace with output. If there +" are errors, they are echoed instead. +func! ledger#entry() + let l:output = systemlist(s:ledger_cmd(g:ledger_main, join(["entry", getline('.')]))) + " Filter out warnings + let l:output = filter(l:output, "v:val !~? '^Warning: '") + " Errors may occur + if v:shell_error + echomsg join(l:output) + return + endif + " Append output so we insert instead of overwrite, then delete line + call append('.', l:output) + normal! "_dd +endfunc + +" Run an arbitrary ledger command and show the output in a new buffer. If +" there are errors, no new buffer is opened: the errors are displayed in a +" quickfix window instead. +" +" Parameters: +" file The file to be processed. +" args A string of Ledger command-line arguments. +" +" Returns: +" Ledger's output as a String. +function! ledger#report(file, args) + let l:output = systemlist(s:ledger_cmd(a:file, a:args)) + if v:shell_error " If there are errors, show them in a quickfix/location list. + call s:quickfix_populate(l:output) + call s:quickfix_toggle('Errors', 'Unable to parse errors') + endif + return l:output +endf + +" Open the output of a Ledger's command in a new buffer. +" +" Parameters: +" report A String containing the output of a Ledger's command. +" +" Returns: +" 1 if a new buffer is created; 0 otherwise. +function! ledger#output(report) + if empty(a:report) + call s:warning_message('No results') + return 0 + endif + " Open a new buffer to show Ledger's output. + execute get(s:winpos_map, g:ledger_winpos, "bo new") + setlocal buftype=nofile bufhidden=wipe modifiable nobuflisted noswapfile nowrap + call append(0, a:report) + setlocal nomodifiable + " Set local mappings to quit window or lose focus. + nnoremap + nnoremap q @=winnr("#")c + " Add some coloring to the report + syntax match LedgerNumber /-\@1 call finish_reconciling() + augroup END + " Add refresh shortcut + execute "nnoremap :call ledger#reconcile('" + \ . l:file . "','" . a:account . "'," . string(a:target_amount) . ")" + call ledger#show_balance(l:file, a:account) + endif +endf + +function! s:finish_reconciling() + unlet g:ledger_target_amount + augroup reconcile + autocmd! + augroup END + augroup! reconcile +endf + +" Show the pending/cleared balance of an account. +" This function has an optional parameter: +" +" a:1 An account name +" +" If no account if given, the account in the current line is used. +function! ledger#show_balance(file, ...) + let l:account = a:0 > 0 && !empty(a:1) ? a:1 : matchstr(getline('.'), '\m\( \|\t\)\zs\S.\{-}\ze\( \|\t\|$\)') + if empty(l:account) + call s:error_message('No account found') + return + endif + let l:cmd = s:ledger_cmd(a:file, join([ + \ "cleared", + \ l:account, + \ "--empty", + \ "--collapse", + \ "--format='%(scrub(get_at(display_total, 0)))|%(scrub(get_at(display_total, 1)))|%(quantity(scrub(get_at(display_total, 1))))'", + \ (empty(g:ledger_default_commodity) ? '' : "-X " . shellescape(g:ledger_default_commodity)) + \ ])) + let l:output = systemlist(l:cmd) + " Errors may occur, for example, when the account has multiple commodities + " and g:ledger_default_commodity is empty. + if v:shell_error + call s:quickfix_populate(l:output) + call s:quickfix_toggle('Errors', 'Unable to parse errors') + return + endif + let l:amounts = split(l:output[-1], '|') + redraw " Necessary in some cases to overwrite previous messages. See :h echo-redraw + if len(l:amounts) < 3 + call s:error_message("Could not determine balance. Did you use a valid account?") + return + endif + echo g:ledger_pending_string + echohl LedgerPending + echon l:amounts[0] + echohl NONE + echon ' ' g:ledger_cleared_string + echohl LedgerCleared + echon l:amounts[1] + echohl NONE + if exists('g:ledger_target_amount') + echon ' ' g:ledger_target_string + echohl LedgerTarget + echon printf('%.2f', (g:ledger_target_amount - str2float(l:amounts[2]))) + echohl NONE + endif +endf +" }}} diff --git a/.vim/pack/plugins/start/vim-ledger/compiler/ledger.vim b/.vim/pack/plugins/start/vim-ledger/compiler/ledger.vim new file mode 100644 index 0000000..837cda3 --- /dev/null +++ b/.vim/pack/plugins/start/vim-ledger/compiler/ledger.vim @@ -0,0 +1,29 @@ +" 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 +endif + +" default value will be set in ftplugin +if ! exists("g:ledger_bin") || empty(g:ledger_bin) || ! executable(g:ledger_bin) + finish +endif + +" Capture Ledger errors (%-C ignores all lines between "While parsing..." and "Error:..."): +CompilerSet errorformat=%EWhile\ parsing\ file\ \"%f\"\\,\ line\ %l:,%ZError:\ %m,%-C%.%# +" Capture Ledger warnings: +CompilerSet errorformat+=%tarning:\ \"%f\"\\,\ line\ %l:\ %m +" Skip all other lines: +CompilerSet errorformat+=%-G%.%# + +" Check file syntax +exe 'CompilerSet makeprg='.substitute(g:ledger_bin, ' ', '\\ ', 'g').'\ '.substitute(g:ledger_extra_options, ' ', '\\ ', 'g').'\ source\ %:S' + diff --git a/.vim/pack/plugins/start/vim-ledger/doc/ledger.txt b/.vim/pack/plugins/start/vim-ledger/doc/ledger.txt new file mode 100644 index 0000000..fc11807 --- /dev/null +++ b/.vim/pack/plugins/start/vim-ledger/doc/ledger.txt @@ -0,0 +1,423 @@ +*ledger.txt* Plugin for the ledger filetype. + + + *ledger* *ledger-plugin* + +Contents: + + Commands............|ledger-invoking| + Source................|ledger-source| + Usage..................|ledger-usage| + Tips....................|ledger-tips| + Reports..............|ledger-reports| + Settings............|ledger-settings| + Completion........|ledger-completion| + License..............|ledger-license| + + +============================================================================== +USAGE *ledger-usage* + +Copy each file to the corresponding directory in your ~/.vim directory or +install using Pathogen. + +You can also use a modeline like this in every ledger file: + + vim:filetype=ledger + +============================================================================== +TIPS *ledger-tips* + +Tips and useful commands + +* vim-ledger can do syntax-sensitive folding when you set `foldmethod=syntax` + in the |modeline| of your ledger file. This way transactions can shrink down + to just one line. + +* Try account-completion (as explained below). If you use YouCompleteMe, you + should disable it for Ledger files. Put this in your .vimrc: + + if exists('g:ycm_filetype_blacklist') + call extend(g:ycm_filetype_blacklist, { 'ledger': 1 }) + endif + +* You may use `:make` for syntax checking. It may be convenient to define a + mapping for the following command: + + :silent make | redraw! | cwindow + + It is recommended to set the value of `g:ledger_extra_options` (see below) + as follows: + + let g:ledger_extra_options = '--pedantic --explicit --check-payees' + + to catch most potential problems in your source file. + +* Remap vim paragraph motion to move by transaction. + + In vim, the "{" and "}" keystrokes move the cursor up and down by whole + paragraphs. They can be redefined in ledger files to move by transaction + instead. Add these lines to .vimrc: + + au FileType ledger noremap { ?^\d + au FileType ledger noremap } /^\d + + The default definitions already work in ledger files that separate + transactions with blank lines. + +* `:call ledger#transaction_date_set('.'), "auxiliary")` + + will set today's date as the auxiliary date of the current transaction. You + can use also "primary" or "unshift" in place of "auxiliary". When you pass + "unshift" the old primary date will be set as the auxiliary date and today's + date will be set as the new primary date. + To use a different date pass a date measured in seconds since 1st Jan 1970 + as the third argument. + +* `:call ledger#transaction_state_set(line('.'), '*')` + + sets the state of the current transaction to '*'. You can use this in custom + mappings. + +* `:call ledger#transaction_state_toggle(line('.'), ' *?!')` + + will toggle through the provided transaction states. You can map this to + double-clicking for example: + + noremap <2-LeftMouse>\ + :call ledger#transaction_state_toggle(line('.'), ' *?!') + +* `:LedgerAlign` + + moves the amount expression of a posting so that the decimal separator is + aligned at the column specified by g:ledger_align_at. If an amount has no + decimal point, the imaginary decimal point to the right of the least + significant digit will align. The command acts on a range, with the default + being the current line. + + The decimal separator can be set using `g:ledger_decimal_sep`. The default + value of `g:ledger_decimal_sep` is `'.'`. + + See below for the recommended mappings. + +* `:call ledger#align_amount_at_cursor()` + + aligns the amount under the cursor and append/prepend the default currency. + The default currency can be set using `g:ledger_default_commodity`. Whether + the commodity should be inserted before the amount or appended to it can be + configured with the boolean flag `g:ledger_commodity_before` (the default + value is 1). A separator between the commodity and the amount may be set + using `g:ledger_commodity_sep`. + + See below for the recommended mappings. + +* `:call ledger#autocomplete_and_align()` + + when the cursor is on a number or immediately after it, invokes + `ledger#align_amount_at_cursor()` to align it and add the default currency; + otherwise, performs autocompletion. If you define the following mappings in + your `.vimrc` then you may perform both autocompletion and alignment using + the key: + + au FileType ledger inoremap \ + =ledger#autocomplete_and_align() + au FileType ledger vnoremap :LedgerAlign + + Alternatively, you may create a file `.vim/after/ftplugin/ledger.vim` + containing the following definitions: + + inoremap \ + =ledger#autocomplete_and_align() + vnoremap :LedgerAlign + + Now, you may type `asset:check123.45`, and have the + account name autocompleted and `$123.45` properly aligned (assuming your + default commodity is set to `'$'`). Or you may press in Visual mode + to align a number of transactions at once. + +* `:call ledger#entry()` + + enters a new transaction based on the text in the current line. + The text in the current line is replaced by the new transaction. + This is a front end to `ledger entry`. + +============================================================================== +REPORTS *ledger-reports* + +* `:Ledger` + + Executes an arbitrary Ledger command and sends the output to a new buffer. + For example: + + :Ledger bal ^assets ^liab + + Errors are displayed in a quickfix window. The command offers account and + payee autocompletion (by pressing ): every name starting with `@` is + autocompleted as a payee; any other name is autocompleted as an account. + + In a report buffer or in the quickfix window, you may press to switch + back to your source file, and you may press `q` to dismiss the current window. + + There are three highlight groups that are used to color the report: + + * `LedgerNumber` + + This is used to color nonnegative numbers. + + * `LedgerNegativeNumber` + + This is used to color negative numbers. + + * `LedgerImproperPerc` + + This is used to color improper percentages. + +* `:Balance` + + Show the pending and cleared balance of a given account below the status + line. For example: + + :Balance checking:savings + + The command offers payee and account autocompletion (see `:Ledger`). The + account argument is optional: if no argument is given, the first account + name found in the current line is used. + + Two highlight groups can be used to customize the colors of the line: + + * `LedgerCleared` + + This is used to color the cleared balance. + + * `LedgerPending` + + This is used to color the pending balance. + +* `:Register` + + Opens an arbitrary register report in the quickfix window. For example: + + :Register groceries -p 'this month' + + The command offers account and payee autocompletion (see |:Ledger|). You + may use the standard quickfix commands to jump from an entry in the register + report to the corresponding location in the source file. If you use GUI Vim + or if your terminal has support for the mouse (e.g., iTerm2, or even + Terminal.app in OS X 10.11 or later), you may also double-click on a line + number in the quickfix window to jump to the corresponding posting. + + It is strongly recommended that you add mappings for common quickfix + commands like `:cprev` and `:cnext`, or that you use T. Pope's Unimpaired + plugin. + +* :`Reconcile` + + Reconcile an account. For example: + + :Reconcile checking + + After you press Enter, you will be asked to enter a target amount (use + Vim's syntax for numbers, not your ledger's format). For example, for a + checking account, the target amount may be the balance of your latest bank + statement. The list of uncleared postings appears in the quickfix window. + The current balance of the account, together with the difference between the + target amount and the cleared balance, is shown at the bottom of the screen. + You may use standard quickfix commands to navigate through the postings. You + may use |ledger#transaction_state_set()| to update a transaction's state. + Every time you save your file, the balance and the difference from the + target amount are updated at the bottom of the screen. The goal, of course, + is to get such difference to zero. You may press `` to refresh the + Reconcile buffer. To finish reconciling an account, simply close the + quickfix window. + + There is a highlight group to customize the color of the difference from + target: + + * `LedgerTarget` + + This is used to color the difference between the target amount and the + cleared balance. + +============================================================================== +SETTINGS *ledger-settings* + +Configuration + +Include the following let-statements somewhere in your `.vimrc` to modify the +behaviour of the ledger filetype. + +* Path to the `ledger` executable: + + let g:ledger_bin = 'ledger' + +* Additional default options for the `ledger` executable: + + let g:ledger_extra_options = '' + +* Number of columns 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 = ' -' + +* 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 + +* By default vim will fold ledger transactions, leaving surrounding blank lines + unfolded. You can use 'g:ledger_fold_blanks' to hide blank lines following a + transaction. + + let g:ledger_fold_blanks = 0 + + A value of 0 will disable folding of blank lines, 1 will allow folding of a + single blank line between transactions; any larger value will enable folding + unconditionally. + + Note that only lines containing no trailing spaces are considered for + folding. You can take advantage of this to disable this feature on a + case-by-case basis. + +* Decimal separator: + + let g:ledger_decimal_sep = '.' + +* Specify at which column decimal separators should be aligned: + + let g:ledger_align_at = 60 + +* Default commodity used by `ledger#align_amount_at_cursor()`: + + let g:ledger_default_commodity = '' + +* Flag that tells whether the commodity should be prepended or appended to the + amount: + + let g:ledger_commodity_before = 1 + +* String to be put between the commodity and the amount: + + let g:ledger_commodity_sep = '' + +* Flag that enable the spelling of the amount: + + let g:ledger_commodity_spell = 1 + +* Format of transaction date: + + let g:ledger_date_format = '%Y/%m/%d' + +* The file to be used to generate reports: + + let g:ledger_main = '%' + + The default is to use the current file. + +* Position of a report buffer: + + let g:ledger_winpos = 'B' + + Use `b` for bottom, `t` for top, `l` for left, `r` for right. Use uppercase letters + if you want the window to always occupy the full width or height. + +* Format of quickfix register reports (see |:Register|): + + let g:ledger_qf_register_format = \ + '%(date) %-50(payee) %-30(account) %15(amount) %15(total)\n' + + The format is specified using the standard Ledger syntax for --format. + +* Format of the reconcile quickfix window (see |:Reconcile|): + + let g:ledger_qf_reconcile_format = \ + '%(date) %-4(code) %-50(payee) %-30(account) %15(amount)\n' + + The format is specified using the standard Ledger syntax for --format. + +* Flag that tells whether a location list or a quickfix list should be used: + + let g:ledger_use_location_list = 0 + + The default is to use the quickfix window. Set to 1 to use a location list. + +* Position of the quickfix/location list: + + let g:ledger_qf_vertical = 0 + + Set to 1 to open the quickfix window in a vertical split. + +* Size of the quickfix window: + + let g:ledger_qf_size = 10 + + This is the number of lines of a horizontal quickfix window, or the number + of columns of a vertical quickfix window. + +* Flag to show or hide filenames in the quickfix window: + + let g:ledger_qf_hide_file = 1 + + Filenames in the quickfix window are hidden by default. Set this to 1 is + you want filenames to be visible. + +* Text of the output of the |:Balance| command: + + let g:ledger_cleared_string = 'Cleared: ' + let g:ledger_pending_string = 'Cleared or pending: ' + let g:ledger_target_string = 'Difference from target: ' + +============================================================================== +COMPLETION *ledger-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 + +You will get a list of top-level accounts that start like this. + +Go ahead and try something like: + + As:Ban:Che + +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 *ledger-license* + +https://github.com/ledger/vim-ledger + +Copyright 2019 Caleb Maclennan +Copyright 2009–2017 Johann Klähn +Copyright 2009 Stefan Karrmann +Copyright 2005 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 . + + +vim:ts=8 sw=8 noexpandtab tw=78 ft=help: + diff --git a/.vim/pack/plugins/start/vim-ledger/ftdetect/ledger.vim b/.vim/pack/plugins/start/vim-ledger/ftdetect/ledger.vim new file mode 100644 index 0000000..ec27577 --- /dev/null +++ b/.vim/pack/plugins/start/vim-ledger/ftdetect/ledger.vim @@ -0,0 +1 @@ +autocmd BufNewFile,BufRead *.ldg,*.ledger,*.journal setlocal filetype=ledger diff --git a/.vim/pack/plugins/start/vim-ledger/ftplugin/ledger.vim b/.vim/pack/plugins/start/vim-ledger/ftplugin/ledger.vim new file mode 100644 index 0000000..93532d0 --- /dev/null +++ b/.vim/pack/plugins/start/vim-ledger/ftplugin/ledger.vim @@ -0,0 +1,449 @@ +" 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 ". + \ "foldtext< ". + \ "include< comments< commentstring< omnifunc< formatprg<" + +if !exists('current_compiler') + compiler ledger +endif + +setl foldtext=LedgerFoldText() +setl include=^!\\?include +setl comments=b:; +setl commentstring=;%s +setl omnifunc=LedgerComplete + +" set location of ledger binary for checking and auto-formatting +if ! exists("g:ledger_bin") || empty(g:ledger_bin) || ! executable(g:ledger_bin) + if executable('ledger') + let g:ledger_bin = 'ledger' + else + unlet! g:ledger_bin + echohl WarningMsg + echomsg "ledger command not found. Set g:ledger_bin or extend $PATH ". + \ "to enable error checking and auto-formatting." + echohl None + endif +endif + +if exists("g:ledger_bin") + exe 'setl formatprg='.substitute(g:ledger_bin, ' ', '\\ ', 'g').'\ -f\ -\ print' +endif + +if !exists('g:ledger_extra_options') + let g:ledger_extra_options = '' +endif + +if !exists('g:ledger_date_format') + let g:ledger_date_format = '%Y/%m/%d' +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 !exists('g:ledger_decimal_sep') + let g:ledger_decimal_sep = '.' +endif + +if !exists('g:ledger_align_at') + let g:ledger_align_at = 60 +endif + +if !exists('g:ledger_default_commodity') + let g:ledger_default_commodity = '' +endif + +if !exists('g:ledger_commodity_before') + let g:ledger_commodity_before = 1 +endif + +if !exists('g:ledger_commodity_sep') + let g:ledger_commodity_sep = '' +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 = 1 +endif + +" only display exact matches (no parent accounts etc.) +if !exists('g:ledger_exact_only') + let g:ledger_exact_only = 0 +endif + +" display original text / account name as completion +if !exists('g:ledger_include_original') + let g:ledger_include_original = 0 +endif + +" Settings for Ledger reports {{{ +if !exists('g:ledger_main') + let g:ledger_main = '%' +endif + +if !exists('g:ledger_winpos') + let g:ledger_winpos = 'B' " Window position (see s:winpos_map) +endif + +if !exists('g:ledger_use_location_list') + let g:ledger_use_location_list = 0 " Use quickfix list by default +endif + +if !exists('g:ledger_cleared_string') + let g:ledger_cleared_string = 'Cleared: ' +endif + +if !exists('g:ledger_pending_string') + let g:ledger_pending_string = 'Cleared or pending: ' +endif + +if !exists('g:ledger_target_string') + let g:ledger_target_string = 'Difference from target: ' +endif +" }}} + +" Settings for the quickfix window {{{ +if !exists('g:ledger_qf_register_format') + let g:ledger_qf_register_format = '%(date) %-50(payee) %-30(account) %15(amount) %15(total)\n' +endif + +if !exists('g:ledger_qf_reconcile_format') + let g:ledger_qf_reconcile_format = '%(date) %-4(code) %-50(payee) %-30(account) %15(amount)\n' +endif + +if !exists('g:ledger_qf_size') + let g:ledger_qf_size = 10 " Size of the quickfix window +endif + +if !exists('g:ledger_qf_vertical') + let g:ledger_qf_vertical = 0 +endif + +if !exists('g:ledger_qf_hide_file') + let g:ledger_qf_hide_file = 1 +endif +" }}} + +" Highlight groups for Ledger reports {{{ +hi link LedgerNumber Number +hi link LedgerNegativeNumber Special +hi link LedgerCleared Constant +hi link LedgerPending Todo +hi link LedgerTarget Statement +hi link LedgerImproperPerc Special +" }}} + +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 + 1 + 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 and 'cleared' marks) + " is the beginning of the account name + let b:compl_context = 'account' + return matchend(line, '^\s\+[*!]\?\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 update_cache = 0 + + let results = [] + if b:compl_context == 'account' "{{{2 (account) + let hierarchy = split(a:base, ':') + if a:base =~ ':$' + call add(hierarchy, '') + endif + + let results = ledger#find_in_tree(b:compl_cache.accounts, hierarchy) + let exacts = filter(copy(results), 'v:val[1]') + + if len(exacts) < 1 + " update cache if we have no exact matches + let update_cache = 1 + endif + + if g:ledger_exact_only + let results = exacts + endif + + call map(results, 'v:val[0]') + + if g:ledger_detailed_first + let results = reverse(sort(results, 's:sort_accounts_by_depth')) + else + let results = sort(results) + endif + elseif b:compl_context == 'description' "{{{2 (description) + let results = ledger#filter_items(b:compl_cache.descriptions, a:base) + + if len(results) < 1 + let update_cache = 1 + endif + elseif b:compl_context == 'new' "{{{2 (new line) + return [strftime(g:ledger_date_format)] + endif "}}} + + + if g:ledger_include_original + call insert(results, a:base) + endif + + " no completion (apart from a:base) found. update cache if file has changed + if update_cache && b:compl_cache['#'] != changenr() + unlet b:compl_cache + return LedgerComplete(a:findstart, a:base) + else + unlet! b:compl_context + return results + endif + endif +endf "}}} + +" Deprecated functions {{{1 +let s:deprecated = { + \ 'LedgerToggleTransactionState': 'ledger#transaction_state_toggle', + \ 'LedgerSetTransactionState': 'ledger#transaction_state_set', + \ 'LedgerSetDate': 'ledger#transaction_date_set' + \ } + +for [s:old, s:new] in items(s:deprecated) + let s:fun = "function! {s:old}(...)\nechohl WarningMsg\necho '" . s:old . + \ " is deprecated. Use ".s:new." instead!'\nechohl None\n" . + \ "call call('" . s:new . "', a:000)\nendf" + exe s:fun +endfor +unlet s:old s:new s:fun +" }}}1 + +function! s:collect_completion_data() "{{{1 + let transactions = ledger#transactions() + let cache = {'descriptions': [], 'tags': {}, 'accounts': {}} + let accounts = ledger#declared_accounts() + for xact in transactions + " collect descriptions + if has_key(xact, 'description') && 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 "}}} + +" Helper functions {{{1 + +" 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 "}}} + +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 "}}} + +function! s:autocomplete_account_or_payee(argLead, cmdLine, cursorPos) "{{{2 + return (a:argLead =~ '^@') ? + \ map(filter(systemlist(g:ledger_bin . ' -f ' . shellescape(expand(g:ledger_main)) . ' payees'), + \ "v:val =~? '" . strpart(a:argLead, 1) . "' && v:val !~? '^Warning: '"), '"@" . escape(v:val, " ")') + \ : + \ map(filter(systemlist(g:ledger_bin . ' -f ' . shellescape(expand(g:ledger_main)) . ' accounts'), + \ "v:val =~? '" . a:argLead . "' && v:val !~? '^Warning: '"), 'escape(v:val, " ")') +endf "}}} + +function! s:reconcile(file, account) "{{{2 + " call inputsave() + let l:amount = input('Target amount' . (empty(g:ledger_default_commodity) ? ': ' : ' (' . g:ledger_default_commodity . '): ')) + " call inputrestore() + call ledger#reconcile(a:file, a:account, str2float(l:amount)) +endf "}}} + +" Commands {{{1 +command! -buffer -nargs=? -complete=customlist,s:autocomplete_account_or_payee + \ Balance call ledger#show_balance(g:ledger_main, ) + +command! -buffer -nargs=+ -complete=customlist,s:autocomplete_account_or_payee + \ Ledger call ledger#output(ledger#report(g:ledger_main, )) + +command! -buffer -range LedgerAlign ,call ledger#align_commodity() + +command! -buffer -nargs=1 -complete=customlist,s:autocomplete_account_or_payee + \ Reconcile call reconcile(g:ledger_main, ) + +command! -buffer -complete=customlist,s:autocomplete_account_or_payee -nargs=* + \ Register call ledger#register(g:ledger_main, ) +" }}} + diff --git a/.vim/pack/plugins/start/vim-ledger/indent/ledger.vim b/.vim/pack/plugins/start/vim-ledger/indent/ledger.vim new file mode 100644 index 0000000..ce5d508 --- /dev/null +++ b/.vim/pack/plugins/start/vim-ledger/indent/ledger.vim @@ -0,0 +1,46 @@ +" Vim filetype indent 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_indent") + finish +endif +let b:did_indent = 1 + +setl autoindent +setl indentexpr=GetLedgerIndent() + +if exists("*GetLedgerIndent") + finish +endif + +function GetLedgerIndent(...) + " You can pass in a line number when calling this function manually. + let lnum = a:0 > 0 ? a:1 : v:lnum + " If this line is empty look at (the indentation of) the last line. + " Note that inside of a transaction no blank lines are allowed. + let line = getline(lnum) + let prev = getline(lnum - 1) + + if line =~ '^\s\+\S' + " Lines that already are indented (→postings, sub-directives) keep their indentation. + return &sw + elseif line =~ '^\s*$' + " Current line is empty, try to guess its type based on the previous line. + if prev =~ '^\([[:digit:]~=]\|\s\+\S\)' + " This is very likely a posting or a sub-directive. + " While lines following the start of a transaction are automatically + " indented you will have to indent the first line following a + " pre-declaration manually. This makes it easier to type long lists of + " 'account' pre-declarations without sub-directives, for example. + return &sw + else + return 0 + endif + else + " Everything else is not indented: + " start of transactions, pre-declarations, apply/end-lines + return 0 + endif +endf diff --git a/.vim/pack/plugins/start/vim-ledger/syntax/ledger.vim b/.vim/pack/plugins/start/vim-ledger/syntax/ledger.vim new file mode 100644 index 0000000..3538bbd --- /dev/null +++ b/.vim/pack/plugins/start/vim-ledger/syntax/ledger.vim @@ -0,0 +1,107 @@ +" 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 + +" Force old regex engine (:help two-engines) +let s:oe = v:version < 704 ? '' : '\%#=1' +let s:lb1 = v:version < 704 ? '\@<=' : '\@1<=' + +let s:fb = get(g:, 'ledger_fold_blanks', 0) +let s:skip = s:fb > 0 ? '\|^\n' : '' +if s:fb == 1 + let s:skip .= '\n\@!' +endif + +let s:ledgerAmount_contains = '' +if get(g:, 'ledger_commodity_spell', 0) == 0 + let s:ledgerAmount_contains .= '@NoSpell' +endif + +" for debugging +syntax clear + +" DATE[=EDATE] [*|!] [(CODE)] DESC <-- first line of transaction +" ACCOUNT AMOUNT [; NOTE] <-- posting + +exe 'syn region ledgerTransaction start=/^[[:digit:]~=]/ '. + \ 'skip=/^\s'. s:skip . '/ end=/^/ fold keepend transparent '. + \ 'contains=ledgerTransactionDate,ledgerMetadata,ledgerPosting,ledgerTransactionExpression' +syn match ledgerTransactionDate /^\d\S\+/ contained +syn match ledgerTransactionExpression /^[=~]\s\+\zs.*/ contained +syn match ledgerPosting /^\s\+[^[:blank:];][^;]*\ze\%($\|;\)/ + \ contained transparent contains=ledgerAccount,ledgerAmount,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 +exe 'syn match ledgerAccount '. + \ '/'.s:oe.'^\s\+\zs\%(\S'.s:lb1.' \S\|\S\)\+\ze\%( \|\t\|\s*$\)/ contained' +exe 'syn match ledgerAmount '. + \ '/'.s:oe.'\S'.s:lb1.'\%( \|\t\)\s*\zs\%([^;[:space:]]\|\s\+[^;[:space:]]\)\+/ contains='.s:ledgerAmount_contains.' contained' + +syn region ledgerPreDeclaration start=/^\(account\|payee\|commodity\|tag\)/ skip=/^\s/ end=/^/ + \ keepend transparent + \ contains=ledgerPreDeclarationType,ledgerPreDeclarationName,ledgerPreDeclarationDirective +syn match ledgerPreDeclarationType /^\(account\|payee\|commodity\|tag\)/ contained +syn match ledgerPreDeclarationName /^\S\+\s\+\zs.*/ contained +syn match ledgerPreDeclarationDirective /^\s\+\zs\S\+/ contained + +syn match ledgerDirective + \ /^\%(alias\|assert\|bucket\|capture\|check\|define\|expr\|fixed\|include\|year\)\s/ +syn match ledgerOneCharDirective /^\%(P\|A\|Y\|N\|D\|C\)\s/ + +syn region ledgerBlockComment start=/^comment/ end=/^end comment/ +syn region ledgerBlockTest start=/^test/ end=/^end test/ +syn match ledgerComment /^[;|*#].*$/ +" comments at eol must be preceded by at least 2 spaces / 1 tab +syn region ledgerMetadata start=/\%( \|\t\|^\s\+\);/ skip=/^\s\+;/ end=/^/ + \ keepend contained contains=ledgerTags,ledgerValueTag,ledgerTypedTag +exe 'syn match ledgerTags '. + \ '/'.s:oe.'\%(\%(;\s*\|^tag\s\+\)\)\@<='. + \ ':[^:[:space:]][^:]*\%(::\?[^:[:space:]][^:]*\)*:\s*$/ '. + \ 'contained contains=ledgerTag' +syn match ledgerTag /:\zs[^:]\+\ze:/ contained +exe 'syn match ledgerValueTag '. + \ '/'.s:oe.'\%(\%(;\|^tag\)[^:]\+\)\@<=[^:]\+:\ze.\+$/ contained' +exe 'syn match ledgerTypedTag '. + \ '/'.s:oe.'\%(\%(;\|^tag\)[^:]\+\)\@<=[^:]\+::\ze.\+$/ contained' + +syn region ledgerApply + \ matchgroup=ledgerStartApply start=/^apply\>/ + \ matchgroup=ledgerEndApply end=/^end\s\+apply\>/ + \ contains=ledgerApplyHead,ledgerApply,ledgerTransaction,ledgerComment +exe 'syn match ledgerApplyHead '. + \ '/'.s:oe.'\%(^apply\s\+\)\@<=\S.*$/ contained' + +highlight default link ledgerComment Comment +highlight default link ledgerBlockComment Comment +highlight default link ledgerBlockTest Comment +highlight default link ledgerTransactionDate Constant +highlight default link ledgerTransactionExpression Statement +highlight default link ledgerMetadata Tag +highlight default link ledgerTypedTag Keyword +highlight default link ledgerValueTag Type +highlight default link ledgerTag Type +highlight default link ledgerStartApply Tag +highlight default link ledgerEndApply Tag +highlight default link ledgerApplyHead Type +highlight default link ledgerAccount Identifier +highlight default link ledgerAmount Number +highlight default link ledgerPreDeclarationType Type +highlight default link ledgerPreDeclarationName Identifier +highlight default link ledgerPreDeclarationDirective Type +highlight default link ledgerDirective Type +highlight default link ledgerOneCharDirective Type + +" syncinc is easy: search for the first transaction. +syn sync clear +syn sync match ledgerSync grouphere ledgerTransaction "^[[:digit:]~=]" + +let b:current_syntax = "ledger"