diff --git a/ledger-mode.el b/ledger-mode.el index cbebbd74..51d56958 100644 --- a/ledger-mode.el +++ b/ledger-mode.el @@ -187,34 +187,67 @@ the balance into that." nil 'noerr) (replace-match "")))))))) -(defun ledger-insert-effective-date (&optional date) +(defun ledger-insert-effective-date (&optional date start end) "Insert effective date `DATE' to the transaction or posting. -If `DATE' is nil, prompt the user a date. +If `DATE' is nil, prompt the user for a date. Replace the current effective date if there's one in the same line. -With a prefix argument, remove the effective date." - (interactive) - (if (and (listp current-prefix-arg) - (= 4 (prefix-numeric-value current-prefix-arg))) - (ledger-remove-effective-date) - (let* ((context (car (ledger-context-at-point))) - (date-string (or date (ledger-read-date "Effective date: ")))) - (save-restriction - (narrow-to-region (line-beginning-position) (line-end-position)) - (cond - ((eq 'xact context) - (beginning-of-line) - (re-search-forward ledger-iso-date-regexp) - (when (= (char-after) ?=) - (ledger-remove-effective-date)) - (insert "=" date-string)) - ((eq 'acct-transaction context) - (end-of-line) - (ledger-remove-effective-date) - (insert " ; [=" date-string "]"))))))) +With a prefix argument, remove the effective date. + +With an active region (`START' and `END' non-nil), insert or +remove for all transactions starting within the region." + (interactive + (if (use-region-p) + (list nil (region-beginning) (region-end)) + (list nil nil nil))) + + (if (and start end) (ledger-insert-effective-date-region start end date) + (if (and (listp current-prefix-arg) + (= 4 (prefix-numeric-value current-prefix-arg))) + (ledger-remove-effective-date) + (let* ((context (car (ledger-context-at-point))) + (date-string (or date (ledger-read-date "Effective date: ")))) + (save-restriction + (narrow-to-region (line-beginning-position) (line-end-position)) + (cond + ((eq 'xact context) + (beginning-of-line) + (re-search-forward ledger-iso-date-regexp) + (when (= (char-after) ?=) + (ledger-remove-effective-date)) + (insert "=" date-string)) + ((eq 'acct-transaction context) + (end-of-line) + (ledger-remove-effective-date) + (insert " ; [=" date-string "]")))))))) + +(defun ledger-insert-effective-date-region (start end &optional date) + "Insert effective date `DATE' to all transactions starting within +the region. + +If `DATE' is nil, prompt the user for a date. + +Replace the current effective date if there is one. + +With a prefix argument, remove any effective dates." + (interactive "r") + (let* ((should-remove + (and (listp current-prefix-arg) + (= 4 (prefix-numeric-value current-prefix-arg)))) + (date-string + (unless should-remove + (or date (ledger-read-date "Effective date: "))))) + (save-excursion + (setq end (copy-marker end)) + (goto-char start) + (while (< (point) end) + (let ((context (car (ledger-context-at-point)))) + (when (eq 'xact context) + (ledger-insert-effective-date date-string))) + (forward-line 1))))) (defun ledger-mode-remove-extra-lines () "Get rid of multiple empty lines." diff --git a/ledger-reconcile.el b/ledger-reconcile.el index db5cf126..5a4c75fd 100644 --- a/ledger-reconcile.el +++ b/ledger-reconcile.el @@ -36,7 +36,8 @@ (require 'ledger-exec) (require 'ledger-navigate) (require 'ledger-state) -(declare-function ledger-insert-effective-date "ledger-mode" (&optional date)) +(declare-function ledger-insert-effective-date "ledger-mode" + (&optional start end date)) (declare-function ledger-read-account-with-prompt "ledger-mode" (prompt)) (declare-function ledger-read-date "ledger-mode" (prompt)) diff --git a/test/mode-test.el b/test/mode-test.el index abfa14a7..77fbb2e7 100644 --- a/test/mode-test.el +++ b/test/mode-test.el @@ -284,6 +284,79 @@ http://bugs.ledger-cli.org/show_bug.cgi?id=256" (call-interactively 'ledger-insert-effective-date)) (should (equal (buffer-string) orig-file-contents))))) +(ert-deftest ledger-mode/test-009 () + "Baseline test for `ledger-insert-effective-date-region'." + :tags '(mode baseline) + + (cl-flet ((file-with-dates (date-1 date-2) + ;; A file with two optional effective dates. + (format + "\ +2024-01-01%s Grocery Store + Expenses:Groceries $30 + Liabilities:Credit Card + +2024-01-02%s Grocery Store + Expenses:Groceries $10 + Expenses:Tax $1.50 + Liabilities:Credit Card -$11.50 +" + (if date-1 (concat "=" date-1) "") + (if date-2 (concat "=" date-2) "")))) + (ledger-tests-with-temp-file + (file-with-dates nil nil) + + ;; With no prefix arg, insert or replace effective date for xacts that + ;; start on a line that overlaps the range. With one, remove the + ;; effective date. + + ;; Range fully contained in start line. + (save-excursion + (let ((min (progn (forward-char 1) (point))) + (max (progn (forward-char 1) (point)))) + (ledger-insert-effective-date-region min max "2024-03-01") + (should (equal (buffer-string) (file-with-dates "2024-03-01" nil))))) + + ;; Range doesn't overlap the start of an xact: don't do anything. + (save-excursion + (let ((min (progn (forward-line 1) (point))) + (max (progn (forward-line 3) (point)))) + (ledger-insert-effective-date-region min max "2024-03-02") + (should (equal (buffer-string) (file-with-dates "2024-03-01" nil))))) + + ;; Range overlaps multiple xacts: update them all. + (save-excursion + (let ((min (point)) + (max (progn (forward-line 4) (forward-char 1) (point)))) + (ledger-insert-effective-date-region min max "2024-03-03") + (should (equal (buffer-string) + (file-with-dates "2024-03-03" "2024-03-03"))))) + + ;; Remove effective date from multiple. + (save-excursion + (let ((min (point)) + (max (progn (forward-line 4) (forward-char 1) (point))) + (current-prefix-arg '(4))) + (ledger-insert-effective-date-region min max nil) + (should (equal (buffer-string) (file-with-dates nil nil))))) + + ;; Add it back to both. + (save-excursion + (let ((min (point)) + (max (progn (forward-line 4) (forward-char 1) (point)))) + (ledger-insert-effective-date-region min max "2024-03-04") + (should (equal (buffer-string) + (file-with-dates "2024-03-04" "2024-03-04"))))) + + ;; Remove from just the first. + (save-excursion + (let ((min (progn (forward-char 1) (point))) + (max (progn (forward-char 1) (point))) + (current-prefix-arg '(4))) + (ledger-insert-effective-date-region min max nil) + (should (equal (buffer-string) + (file-with-dates nil "2024-03-04")))))))) + (provide 'mode-test) ;;; mode-test.el ends here