diff --git a/package.json b/package.json index 87b8378..9e5cfb6 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,11 @@ "default": true, "description": "Controls whether the auto completion list should include payee and narration fields." }, + "beancount.completeTransaction": { + "type": "boolean", + "default": true, + "description": "Controls whether the auto completion list should include whole transactions." + }, "beancount.mainBeanFile": { "type": "string", "default": "", diff --git a/pythonFiles/beancheck.py b/pythonFiles/beancheck.py index c4ec800..8bf551a 100644 --- a/pythonFiles/beancheck.py +++ b/pythonFiles/beancheck.py @@ -1,11 +1,13 @@ ''' load beancount file and print errors ''' +from datetime import date from sys import argv from beancount import loader from beancount.core import flags from beancount.core.data import Transaction, Open, Close from beancount.core.display_context import Align from beancount.core.realization import dump_balances, realize +from beancount.parser.printer import format_entry import io import json @@ -34,6 +36,7 @@ def get_flag_metadata(thing): entries, errors, options = loader.load_file(argv[1]) completePayeeNarration = "--payeeNarration" in argv +completeTransaction = "--transaction" in argv error_list = [{"file": e.source['filename'], "line": e.source['lineno'], "message": e.message} for e in errors] @@ -42,6 +45,8 @@ def get_flag_metadata(thing): commodities = set() payees = set() narrations = set() +transactions = {} +lastTransactionDate = date.fromtimestamp(0) tags = set() links = set() flagged_entries = [] @@ -50,17 +55,27 @@ def get_flag_metadata(thing): if hasattr(entry, 'flag') and entry.flag == "!": flagged_entries.append(get_flag_metadata(entry)) if isinstance(entry, Transaction): + lastTransactionDate = entry.date if entry.date > lastTransactionDate else lastTransactionDate if completePayeeNarration: - payees.add(f'{entry.payee}') + payees.add(str(entry.payee)) if not entry.narration.startswith("(Padding inserted"): if completePayeeNarration: - narrations.add(f'{entry.narration}') + narrations.add(str(entry.narration)) tags.update(entry.tags) links.update(entry.links) for posting in entry.postings: commodities.add(posting.units.currency) if hasattr(posting, 'flag') and posting.flag == "!": flagged_entries.append(get_flag_metadata(posting)) + if completeTransaction: + # Add transaction to dict (replace if already exists, i.e. newest transaction wins) + if entry.payee: + transaction_key = ' '.join([entry.flag, entry.payee, entry.narration]) + else: + transaction_key = ' '.join([entry.flag, entry.narration]) + transaction_value = format_entry(entry) + transaction_value = transaction_value.lstrip(entry.date.__str__()).lstrip() + transactions[transaction_key] = transaction_value elif isinstance(entry, Open): accounts[entry.account] = { 'open': entry.date.__str__(), @@ -97,6 +112,8 @@ def get_flag_metadata(thing): output['commodities'] = list(commodities) output['payees'] = list(payees) output['narrations'] = list(narrations) +output['transactions'] = transactions +output['lastTransactionDate'] = lastTransactionDate.isoformat() output['tags'] = list(tags) output['links'] = list(links) diff --git a/src/completer.ts b/src/completer.ts index b1f4802..ba8708e 100644 --- a/src/completer.ts +++ b/src/completer.ts @@ -25,6 +25,8 @@ interface CompletionData { commodities: string[]; payees: string[]; narrations: string[]; + transactions: { [key: string]: string }; + lastTransactionDate: string; tags: string[]; links: string[]; } @@ -36,6 +38,8 @@ export class Completer payees: string[]; narrations: string[]; commodities: string[]; + transactions: { [key: string]: string }; + lastTransactionDate: string; tags: string[]; links: string[]; wordPattern: RegExp; @@ -47,6 +51,8 @@ export class Completer this.payees = []; this.narrations = []; this.commodities = []; + this.transactions = {}; + this.lastTransactionDate = ''; this.tags = []; this.links = []; this.wordPattern = new RegExp('[A-Za-z:]+\\S+|"([^\\\\"]|\\\\")*"'); @@ -69,6 +75,8 @@ export class Completer this.commodities = data.commodities; this.payees = data.payees; this.narrations = data.narrations; + this.transactions = data.transactions; + this.lastTransactionDate = data.lastTransactionDate; this.tags = data.tags; this.links = data.links; } @@ -176,13 +184,35 @@ export class Completer (today.getMonth() + 1).toString(); const date = (today.getDate() < 10 ? '0' : '') + today.getDate().toString(); - const dateString = year + '-' + month + '-' + date; + + const dateStringToday = year + '-' + month + '-' + date; const itemToday = new CompletionItem( - dateString, + dateStringToday, + CompletionItemKind.Event + ); + itemToday.documentation = 'Today'; + + const dateStringYear = year + '-'; + const itemYear = new CompletionItem( + dateStringYear, CompletionItemKind.Event ); - itemToday.documentation = 'today'; - resolve([itemToday]); + itemYear.documentation = 'This year'; + + const dateStringLastTransactionDate = this.lastTransactionDate; + const itemLastTransactionDate = new CompletionItem( + dateStringLastTransactionDate, + CompletionItemKind.Event + ); + itemLastTransactionDate.documentation = 'Last transaction'; + + const dateStringLastTransactionMonth = this.lastTransactionDate.slice(0, -2); + const itemLastTransactionMonth = new CompletionItem( + dateStringLastTransactionMonth, + CompletionItemKind.Event + ); + + resolve([itemToday, itemYear, itemLastTransactionDate, itemLastTransactionMonth]); return; } else if ( triggerCharacter === '"' && @@ -193,6 +223,7 @@ export class Completer countOccurrences(textBefore, /\"/g) - countOccurrences(textBefore, /\\"/g); if (r != null && numQuotes % 2 === 1) { + const insertItemWithLetters = ( list: CompletionItem[], text: string, @@ -215,6 +246,7 @@ export class Completer list.push(new CompletionItem(text, kind)); } }; + const list: CompletionItem[] = []; if (numQuotes === 1) { this.payees.forEach((payee, i, a) => { @@ -271,9 +303,41 @@ export class Completer }); resolve(list); return; + } else if ( + vscode.workspace.getConfiguration('beancount')['completeTransaction'] && + textBefore.match(/([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/) + ) { // Match a date at the string beginning + + const instertTransactionItem = (list: CompletionItem[], key: string, transactionText: string) => { + let findOne = false; + const completionItemKind = CompletionItemKind.Value; + + for (const inputMethod of this.inputMethods) { + const letters = inputMethod.getLetterRepresentation(key); + if (letters.length > 0) { + findOne = true; + const item = new CompletionItem( + letters + '(' + key + ')', + completionItemKind + ); + item.insertText = transactionText; + list.push(item); + } + } + if (!findOne) { + const item = new CompletionItem(key, completionItemKind); + item.insertText = transactionText; + list.push(item); + } + }; + + const list: CompletionItem[] = []; + for (const key in this.transactions) { + instertTransactionItem(list, key, this.transactions[key]); + } + resolve(list); } } - resolve([]); }); } } diff --git a/src/extension.ts b/src/extension.ts index 76b5251..83493d2 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -150,12 +150,14 @@ export class Extension { this.logger.appendLine('find no valid bean files.'); return; } + const extConfig = vscode.workspace.getConfiguration('beancount'); const pyArgs = [checkpy, mainBeanFile]; - if ( - vscode.workspace.getConfiguration('beancount')['completePayeeNarration'] - ) { + if (extConfig['completePayeeNarration']) { pyArgs.push('--payeeNarration'); } + if (extConfig['completeTransaction']) { + pyArgs.push('--transaction'); + } this.logger.appendLine( `running ${python3Path} ${pyArgs} to refresh data...` );