Closed josephmturner closed 5 months ago
Ping @purcell :)
I'm not in favour of supporting a customisable method for collecting payee names. Better would be to support Ledger's mechanism for pre-declaring payees with the payee
command directive (see section 4.7.1 of the manual), in a similar way to how we support pre-declared accounts (see the ledger-accounts-file
variable).
Thanks for your feedback @purcell ! Your suggestion to leverage the built-in Ledger functionality makes sense.
I use hledger, and I'm not familiar with this ledger feature. Until this feature is added, here's how I currently apply this patch with el-patch in my config:
;;; -*- lexical-binding: t; -*-
(require 'ledger-mode)
(require 'el-patch)
(defcustom ledger-payees-collection nil
"Collection of payees to be used for completion.
This option may be set to any kind of collection accepted by
`completing-read', which see."
:type 'sexp
:group 'ledger
:package-version '(ledger-mode . "4.0.0"))
(el-patch-feature ledger-complete-at-point)
(with-eval-after-load 'ledger-complete
(el-patch-defun ledger-complete-at-point ()
"Do appropriate completion for the thing at point."
(let ((end (point))
start collection
realign-after
delete-suffix)
(cond (;; Date
(looking-back (concat "^" ledger-incomplete-date-regexp) (line-beginning-position))
(setq collection (ledger-complete-date (match-string 1) (match-string 2))
start (match-beginning 0)
delete-suffix (save-match-data
(when (looking-at (rx (one-or-more (or digit (any ?/ ?-)))))
(length (match-string 0))))))
(;; Effective dates
(looking-back (concat "^" ledger-iso-date-regexp "=" ledger-incomplete-date-regexp)
(line-beginning-position))
(setq start (line-beginning-position))
(setq collection (ledger-complete-effective-date
(match-string 2) (match-string 3) (match-string 4)
(match-string 5) (match-string 6))))
(;; Payees
(eq (save-excursion (ledger-thing-at-point)) 'transaction)
(setq start (save-excursion (backward-word) (point)))
(setq collection (el-patch-swap
#'ledger-payees-in-buffer
(or ledger-payees-collection
#'ledger-payees-in-buffer))))
(;; Accounts
(save-excursion
(back-to-indentation)
(skip-chars-forward "([") ;; for virtual accounts
(setq start (point)))
(setq delete-suffix (save-excursion
(when (search-forward-regexp (rx (or eol (or ?\t (repeat 2 space)))) (line-end-position) t)
(- (match-beginning 0) end)))
realign-after t
collection (if ledger-complete-in-steps
#'ledger-accounts-tree
#'ledger-accounts-list))))
(when collection
(let ((prefix (buffer-substring-no-properties start end)))
(list start end
(if (functionp collection)
(completion-table-with-cache
(lambda (_)
(cl-remove-if (apply-partially 'string= prefix) (funcall collection))))
collection)
:exit-function (lambda (&rest _)
(when delete-suffix
(delete-char delete-suffix))
(when (and realign-after ledger-post-auto-align)
(ledger-post-align-postings (line-beginning-position) (line-end-position))))
'ignore))))))
(defvar-local my/ledger-payees-collection nil
;; TODO: If `project.el' adds support for project-local variables,
;; use that instead of `defvar-local'.
"Memoized payees collection returned by `my/ledger-payees-collection'.")
(defun my/ledger-payees-collection ()
"Return memoized list of payees contained in `my/payees-file'.
`my/payees-file' can be locally bound to a filepath string to a
file containing a newline-delimited list of payees, where the
filepath is relative to the current project root. With nil or
nonexistent `my/payees-file', return `ledger-payees-in-buffer.'"
(let ((payees-file
(and (bound-and-true-p my/payees-file)
(expand-file-name my/payees-file (project-root (project-current t))))))
(cond ((not (and payees-file (file-exists-p payees-file)))
#'ledger-payees-in-buffer)
((or (not my/ledger-payees-collection)
(file-has-changed-p payees-file
'my/ledger-payees-collection))
(setf my/ledger-payees-collection
(with-current-buffer (find-file-noselect payees-file)
(split-string (buffer-string) "\n"))))
(my/ledger-payees-collection))))
(setopt ledger-payees-collection #'my/ledger-payees-collection)
(provide 'my/ledger-complete)
Check out #411: it should now be enough to set ledger-payees-file
to a file that contains lines with payee
declarations, e.g. payee Whatever
.
Thank you @purcell! This solution works for me! Here's how I dynamically create an all-payees.txt
file based on a set of journal files whose payees I want to use for completion:
;; Update all-payees.txt from all manual journals
(with-temp-file "export/all-payees.txt"
(apply #'call-process "hledger" nil t nil "payees" "-I"
(mapcan (lambda (journal)
(list "-f" journal))
(directory-files-recursively
"import/manual" ".journal$")))
;; Prefix each payee with "payee"
(goto-char (point-min))
(while (not (eobp))
(insert "payee ")
(forward-line)))
This user option can be used to offer completion over a list of payees from a file:
To use this snippet, create a file containing a list of payees inside your project and set its relative filepath as a dir-local variable: