2021-04-13 20:09:08 -04:00
|
|
|
;;; lang/beancount/autoload.el -*- lexical-binding: t; -*-
|
|
|
|
|
|
|
|
;;
|
|
|
|
;;; Helpers
|
|
|
|
|
2023-09-20 15:44:29 +02:00
|
|
|
;; Lifted from ledger-mode
|
2021-04-13 20:09:08 -04:00
|
|
|
(defconst +beancount--payee-any-status-regex
|
|
|
|
"^[0-9]+[-/][-/.=0-9]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+\\(.+?\\)\\s-*\\(;\\|$\\)")
|
|
|
|
|
|
|
|
(defun +beancount--sort-startkey ()
|
|
|
|
"Return the actual date so the sort subroutine doesn't sort on the entire first line."
|
|
|
|
(buffer-substring-no-properties (point) (+ 10 (point))))
|
|
|
|
|
|
|
|
(defun +beancount--navigate-next-xact ()
|
|
|
|
"Move point to beginning of next xact."
|
|
|
|
;; make sure we actually move to the next xact, even if we are the beginning
|
|
|
|
;; of one now.
|
|
|
|
(if (looking-at +beancount--payee-any-status-regex)
|
|
|
|
(forward-line))
|
|
|
|
(if (re-search-forward +beancount--payee-any-status-regex nil t)
|
|
|
|
(goto-char (match-beginning 0))
|
|
|
|
(goto-char (point-max))))
|
|
|
|
|
|
|
|
(defun +beancount--navigate-start-xact-or-directive-p ()
|
|
|
|
"Return t if at the beginning of an empty or all-whitespace line."
|
|
|
|
(not (looking-at "[ \t]\\|\\(^$\\)")))
|
|
|
|
|
|
|
|
(defun +beancount--navigate-next-xact-or-directive ()
|
|
|
|
"Move to the beginning of the next xact or directive."
|
|
|
|
(interactive)
|
|
|
|
(beginning-of-line)
|
|
|
|
(if (+beancount--navigate-start-xact-or-directive-p) ; if we are the start of an xact, move forward to the next xact
|
|
|
|
(progn
|
|
|
|
(forward-line)
|
|
|
|
(if (not (+beancount--navigate-start-xact-or-directive-p)) ; we have moved forward and are not at another xact, recurse forward
|
|
|
|
(+beancount--navigate-next-xact-or-directive)))
|
|
|
|
(while (not (or (eobp) ; we didn't start off at the beginning of an xact
|
|
|
|
(+beancount--navigate-start-xact-or-directive-p)))
|
|
|
|
(forward-line))))
|
|
|
|
|
|
|
|
(defun +beancount--navigate-next-xact ()
|
|
|
|
"Move point to beginning of next xact."
|
|
|
|
;; make sure we actually move to the next xact, even if we are the
|
|
|
|
;; beginning of one now.
|
|
|
|
(if (looking-at +beancount--payee-any-status-regex)
|
|
|
|
(forward-line))
|
|
|
|
(if (re-search-forward +beancount--payee-any-status-regex nil t)
|
|
|
|
(goto-char (match-beginning 0))
|
|
|
|
(goto-char (point-max))))
|
|
|
|
|
|
|
|
(defun +beancount--navigate-beginning-of-xact ()
|
|
|
|
"Move point to the beginning of the current xact."
|
|
|
|
;; need to start at the beginning of a line in case we are in the first line of an xact already.
|
|
|
|
(beginning-of-line)
|
|
|
|
(let ((sreg (concat "^[=~[:digit:]]")))
|
|
|
|
(unless (looking-at sreg)
|
|
|
|
(re-search-backward sreg nil t)
|
|
|
|
(beginning-of-line)))
|
|
|
|
(point))
|
|
|
|
|
|
|
|
(defun +beancount--navigate-end-of-xact ()
|
|
|
|
"Move point to end of xact."
|
|
|
|
(+beancount--navigate-next-xact-or-directive)
|
|
|
|
(re-search-backward ".$")
|
|
|
|
(end-of-line)
|
|
|
|
(point))
|
|
|
|
|
|
|
|
|
|
|
|
;;
|
|
|
|
;;; Commands
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun +beancount/sort-buffer (&optional reverse)
|
|
|
|
"Sort all transactions in the buffer.
|
|
|
|
If REVERSE (the prefix arg) is non-nil, sort them in reverse."
|
|
|
|
(interactive "P")
|
|
|
|
(+beancount/sort-region (point-min) (point-max) reverse))
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun +beancount/sort-region (beg end &optional reverse)
|
|
|
|
"Sort the transactions inside BEG and END.
|
|
|
|
If REVERSE (the prefix arg) is non-nil, sort the transactions in reverst order."
|
|
|
|
(interactive
|
|
|
|
(list (region-beginning)
|
|
|
|
(region-end)
|
|
|
|
(and current-prefix-arg t)))
|
|
|
|
(let* ((new-beg beg)
|
|
|
|
(new-end end)
|
|
|
|
(bounds (save-excursion
|
|
|
|
(list (+beancount--navigate-beginning-of-xact)
|
|
|
|
(+beancount--navigate-end-of-xact))))
|
|
|
|
(point-delta (- (point) (car bounds)))
|
|
|
|
(target-xact (buffer-substring (car bounds) (cadr bounds)))
|
|
|
|
(inhibit-modification-hooks t))
|
|
|
|
(save-excursion
|
|
|
|
(save-restriction
|
|
|
|
(goto-char beg)
|
|
|
|
;; make sure beg of region is at the beginning of a line
|
|
|
|
(beginning-of-line)
|
|
|
|
;; make sure point is at the beginning of a xact
|
|
|
|
(unless (looking-at +beancount--payee-any-status-regex)
|
|
|
|
(+beancount--navigate-next-xact))
|
|
|
|
(setq new-beg (point))
|
|
|
|
(goto-char end)
|
|
|
|
(+beancount--navigate-next-xact)
|
|
|
|
;; make sure end of region is at the beginning of next record after the
|
|
|
|
;; region
|
|
|
|
(setq new-end (point))
|
|
|
|
(narrow-to-region new-beg new-end)
|
|
|
|
(goto-char new-beg)
|
|
|
|
(let ((inhibit-field-text-motion t))
|
|
|
|
(sort-subr
|
|
|
|
reverse
|
2023-09-20 15:44:29 +02:00
|
|
|
#'+beancount--navigate-next-xact
|
|
|
|
#'+beancount--navigate-end-of-xact
|
|
|
|
#'+beancount--sort-startkey))))
|
2021-04-13 20:09:08 -04:00
|
|
|
(goto-char (point-min))
|
|
|
|
(re-search-forward (regexp-quote target-xact))
|
|
|
|
(goto-char (+ (match-beginning 0) point-delta))))
|
|
|
|
|
|
|
|
(defvar compilation-read-command)
|
|
|
|
;;;###autoload
|
2024-07-07 05:11:10 -04:00
|
|
|
(defun +beancount/balance (&optional all-accounts)
|
2023-09-20 15:44:29 +02:00
|
|
|
"Display a balance report with bean-report (bean-report bal)."
|
2024-07-07 05:11:10 -04:00
|
|
|
(interactive "P")
|
|
|
|
(let ((args (unless all-accounts '("-e" "Assets|Liabilities")))
|
|
|
|
compilation-read-command
|
|
|
|
current-prefix-arg)
|
|
|
|
(apply #'beancount--run "bean-report" buffer-file-name "balances" args)))
|
2022-02-09 14:54:49 +01:00
|
|
|
|
|
|
|
;;;###autoload
|
2022-04-03 20:42:22 +02:00
|
|
|
(defun +beancount/clone-transaction ()
|
2023-09-20 15:44:29 +02:00
|
|
|
"Clones a transaction from (and to the bottom of) the current ledger buffer.
|
|
|
|
|
|
|
|
Updates the date to today."
|
2022-04-03 20:42:22 +02:00
|
|
|
(interactive)
|
|
|
|
(save-restriction
|
|
|
|
(widen)
|
|
|
|
(when-let (transaction
|
|
|
|
(completing-read
|
|
|
|
"Clone transaction: "
|
|
|
|
(string-lines (buffer-string))
|
|
|
|
(doom-partial #'string-match-p "^[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [*!] ")
|
|
|
|
t))
|
|
|
|
(goto-char (point-min))
|
|
|
|
(re-search-forward (concat "^" (regexp-quote transaction)))
|
|
|
|
(+beancount/clone-this-transaction t))))
|
2022-02-09 14:54:49 +01:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun +beancount/clone-this-transaction (&optional arg)
|
2023-09-20 15:44:29 +02:00
|
|
|
"Clones the transaction at point to the bottom of the ledger.
|
|
|
|
|
|
|
|
Updates the date to today."
|
2022-02-09 14:54:49 +01:00
|
|
|
(interactive "P")
|
|
|
|
(if (and (not arg) (looking-at-p "^$"))
|
|
|
|
(call-interactively #'+beancount/clone-transaction)
|
2022-04-03 20:42:22 +02:00
|
|
|
(save-restriction
|
|
|
|
(widen)
|
|
|
|
(let ((transaction
|
|
|
|
(buffer-substring-no-properties
|
|
|
|
(save-excursion
|
|
|
|
(beancount-goto-transaction-begin)
|
|
|
|
(re-search-forward " " nil t)
|
|
|
|
(point))
|
|
|
|
(save-excursion
|
|
|
|
(beancount-goto-transaction-end)
|
|
|
|
(point)))))
|
|
|
|
(goto-char (point-max))
|
|
|
|
(delete-blank-lines)
|
|
|
|
(beancount-insert-date)
|
|
|
|
(insert transaction)))))
|
2023-02-25 20:45:15 -05:00
|
|
|
|
2023-09-20 20:18:53 +02:00
|
|
|
;;;###autoload
|
|
|
|
(defun +beancount/occur (account &optional disable?)
|
|
|
|
"Hide transactions that don't involve ACCOUNT.
|
|
|
|
|
|
|
|
If DISABLE? (universal arg), reveal hidden accounts without prompting."
|
|
|
|
(interactive
|
|
|
|
(list (unless current-prefix-arg
|
|
|
|
;; REVIEW: Could/should this be generalized to search for arbitrary
|
|
|
|
;; regexps, if desired?
|
|
|
|
(completing-read "Account: " #'beancount-account-completion-table))
|
|
|
|
current-prefix-arg))
|
|
|
|
(with-silent-modifications
|
|
|
|
(save-excursion
|
|
|
|
(setq header-line-format nil)
|
|
|
|
;; TODO: Namespace these text-properties, in case of conflicts
|
|
|
|
(remove-text-properties (point-min) (point-max) '(invisible nil display nil))
|
|
|
|
(unless disable?
|
|
|
|
;; TODO: Prettier header-line display
|
|
|
|
(setq header-line-format `("" "Filtering by account: " ,account))
|
|
|
|
(let ((start (point-min))
|
|
|
|
(placeholder (propertize "[...]\n" 'face 'shadow)))
|
|
|
|
(goto-char start)
|
|
|
|
(while (re-search-forward (concat "\\_<" (regexp-quote account) "\\_>") nil t)
|
|
|
|
(save-excursion
|
|
|
|
(seq-let (beg end) (beancount-find-transaction-extents (point))
|
|
|
|
;; TODO: Highlight entry (ala org-occur)
|
|
|
|
(if (= beg end)
|
|
|
|
(setq end (save-excursion (goto-char end) (1+ (eol)))))
|
|
|
|
(put-text-property start beg 'invisible t)
|
|
|
|
(put-text-property start beg 'display placeholder)
|
|
|
|
(setq start end))))
|
|
|
|
(put-text-property start (point-max) 'invisible t)
|
|
|
|
(put-text-property start (point-max) 'display placeholder))))))
|
|
|
|
|
2023-02-25 20:45:15 -05:00
|
|
|
;;;###autoload
|
|
|
|
(defun +beancount/next-transaction (&optional count)
|
|
|
|
"Jump to the start of the next COUNT-th transaction."
|
|
|
|
(interactive "p")
|
2023-09-20 16:05:34 +02:00
|
|
|
(let ((beancount-transaction-regexp
|
|
|
|
;; Don't skip over timestamped directives (like balance or event
|
|
|
|
;; declarations).
|
|
|
|
(concat beancount-timestamped-directive-regexp
|
|
|
|
"\\|" beancount-transaction-regexp)))
|
|
|
|
(dotimes (_ (or count 1))
|
|
|
|
(beancount-goto-next-transaction))))
|
2023-02-25 20:45:15 -05:00
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun +beancount/previous-transaction (&optional count)
|
2023-09-20 16:19:18 +02:00
|
|
|
"Jump to the start of current or previous COUNT-th transaction.
|
|
|
|
|
|
|
|
Return non-nil if successful."
|
2023-02-25 20:45:15 -05:00
|
|
|
(interactive "p")
|
2023-09-20 16:19:18 +02:00
|
|
|
(let ((pos (point)))
|
|
|
|
(condition-case e
|
|
|
|
(progn
|
|
|
|
;; Ensures "jump to top of current transaction" behavior that is
|
|
|
|
;; common for jump-to-previous commands like this in other Emacs modes
|
|
|
|
;; (like org-mode).
|
|
|
|
(or (bolp) (goto-char (eol)))
|
|
|
|
(re-search-backward
|
|
|
|
(concat beancount-timestamped-directive-regexp
|
|
|
|
"\\|" beancount-transaction-regexp)))
|
|
|
|
('search-failed (goto-char pos) nil))))
|