ledger / ledger-mode

Emacs Lisp files for interacting with the C++Ledger accounting system
GNU General Public License v2.0
386 stars 75 forks source link

Narrowing to Entity: ledger-mode and report-mode #284

Open lawlist opened 3 years ago

lawlist commented 3 years ago

In a ledger-mode or report-mode register buffer, place the cursor on a transaction and type: M-x ledger-edit-fn or C-M-s-RET [with the s being the super / Command key on Apple keyboards]

In the narrowed indirect buffer, do your editing and when done, type: M-x ledger-edit-finalize or C-c C-c

When calling ledger-edit-finalize, you will be transported back to the initial buffer -- if it was a report buffer and if it was modified, the new report will be generated automatically due to the save-buffer command baked into the aforementioned finalize command.

I use a custom register format that is the following, and the ledger-edit-register-report-regexp is based on this:

(let ((register-format
  (concat "\"%(!cleared"
             " ? ansify_if (date, bold)"
             " : ansify_if (date, blue if color))"
          "  %(ansify_if (justify (truncated (display_account, int(account_width), int(abbrev_len)), int(account_width)),"
                         " white if color))"
          " %((display_amount < 0)"
             " ? (!cleared"
                 " ? ansify_if (justify (scrub (display_amount), 13, -1, true, color), bold)"
                 " : justify (scrub (display_amount), 13, -1, true, color))"
             " : ansify_if (justify (scrub (display_amount), 13, -1, true, color), black if color))"
          " %((display_total < 0)"
             " ? justify (scrub (display_total), 13, -1, true, color)"
             " : ansify_if (justify (scrub (display_total), 13, -1, true, color), black if color))"
          "  %(cleared"
              " ? ansify_if (\\\"*\\\", yellow if color)"
              " : (pending"
                  " ? ansify_if (\\\"!\\\", magenta if color)"
                  " : \\\" \\\"))"
          "  %(ansify_if (payee, blink if color and !cleared and actual))\n\n\"")))
  [INSERT CODE TO CREATE THE LEDGER COMMAND LINE WITH ARGS])

The following code is brand new and only very lightly tested. Feel free to use anything, whether it be just the concept or some/all of the code. I don't plan on making a public package, but will continue to work on perfecting this in my own setup as I use it for myself. The positions of the cursor that are tracked and potentially restored when switching buffers are: (1) date, (2) transaction amount, (3) payment status, and (4) payee. When the cursor is on another location (e.g., a line containing an account name), then point will be placed either at the beginning of the entity when going to a different buffer or stay right where it was.

(define-key ledger-mode-map (kbd "<C-M-s-return>") 'ledger-edit-fn)

(define-key ledger-report-mode-map (kbd "<C-M-s-return>") 'ledger-edit-fn)

(define-key ledger-report-mode-map [(control ?c) (control ?c)] 'ledger-edit-toggle-status)

;;; Fix for the infinity loop:  https://github.com/ledger/ledger-mode/issues/274
(defun ledger-toggle-current (&optional style)
  "Toggle the current thing at point with optional STYLE."
  (interactive)
  (if (or ledger-clear-whole-transactions
          (eq 'transaction (ledger-thing-at-point)))
      (let ((point-a -1)
            (point-b 0))
        (save-excursion
          (forward-line)
          (goto-char (line-beginning-position))
          (while (and (not (= point-a point-b))
                      (not (eolp))
                      (save-excursion
                        (not (eq 'transaction (ledger-thing-at-point)))))
            (if (looking-at "\\s-+[*!]")
                (ledger-toggle-current-posting style))
            (setq point-a (point))
            (forward-line)
            (goto-char (line-beginning-position))
            (setq point-b (point))))
        (ledger-toggle-current-transaction style))
    (ledger-toggle-current-posting style)))

;;; 3-Way Toggle of Status:  https://github.com/ledger/ledger-mode/issues/283
(defun ledger-toggle-current-transaction (&optional style)
  "Toggle the transaction at point using optional STYLE."
  (interactive)
  (save-excursion
    (when (or (looking-at "^[0-9]")
              (re-search-backward "^[0-9]" nil t))
      (skip-chars-forward "0-9./=\\-")
      (delete-horizontal-space)
      (let ((current-state (ledger-state-from-char (char-after))))
        (when (or (eq current-state 'pending)
                  (eq current-state 'cleared))
          (delete-char 1))
        (cond
          ((and style (eq style 'cleared))
            (insert " *")
            'cleared)
          ((and style (eq style 'pending))
            (insert " ! ")
            'pending)
          ((eq current-state 'cleared)
            (insert "")
            'uncleared)
          ((eq current-state nil) ;; 'uncleared
            (insert " ! ")
            'pending)
          ((eq current-state 'pending)
            (insert " *")
            'cleared))))))

(defgroup ledger-edit nil
  "This is the ledger-edit-fn group doc-string."
  :tag "Ledger Edit"
  :group 'ledger)

(defvar ledger-edit-display-method (list 'split-window-below 'below '((window-height . 20)))
"Expected value is one of the following:
     (list 'split-window-below direction alist)
     (list 'switch-to-buffer nil nil)")

(defvar ledger-payee-regexp
  "^\\(?:\\(\\([=~]\\).*\\)\\|\\([[:digit:]][^ \t\n]*\\)\\(?:[ \t]+\\([*!]\\)\\)?\\(?:[ \t]+\\((.*?)\\)\\)?\\(?:[ \t]+\\(.+?\\)\\)?\\(?:\\(?:\t\\|[ \t]\\{2,\\}\\)\\(;[^\n]*\\)\\)?$\\)"
  "A regexp that matches the entire payee line.  Extracted from the variable
`ledger-font-lock-keywords' that is used by `font-lock-keywords'.")

(defvar ledger-edit-register-report-regexp
  "^\\([0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}\\)[\s]+\\([a-zA-Z0-9.:_-]+\\)[\s]+\\([$-.,0-9]+\\)[\s]+\\([$-.,0-9]+\\)[\s]+\\([*!]+\\)?[\s]+\\(.*\\)$"
"A regexp that matches each line of the register report.  The following groups
are expected to be present:  date, display amount, payment status, and payee.")

(defvar ledger-edit-account-regexp "^[\s]+\\([a-zA-Z0-9.:_,\s-]+[^\s0-9(]\\)\\([\s]+\\([$0-9,.()-*]+\\).*\\)?$"
"A regexp that matches the account line underneath the payee line.")

(defvar ledger-edit-variable nil
"A cons cell -- `car` = `initial-buffer`; and `cdr` = `ledger-file-buffer`.")
(make-variable-buffer-local 'ledger-edit-variable)

(defvar ledger-edit-prefix-key [(control ?c)]
  "The common prefix key used in ledger-edit-fn mode.")

(defvar ledger-edit-prefix-map
  (let ((map (make-sparse-keymap)))
    (define-key map "\C-c"  'ledger-edit-finalize)
    map)
  "The key bindings provided in ledger-edit-fn mode.")

(defvar ledger-edit-mode-map
  (let ((map (make-sparse-keymap)))
    (define-key map ledger-edit-prefix-key ledger-edit-prefix-map)
    (define-key map (kbd "C-S-c C-S-c") #'ledger-toggle-current)
    (define-key map [?\M-w] (lambda ()
                              (interactive)
                              (if (y-or-n-p "This is an indirect buffer.  Widen anyway?")
                                (widen)
                                (message "You have chosen to exit this function.  Goodbye!"))))
    (define-key map [?\s-s] (lambda ()
                              (interactive)
                              (let* ((ledger-edit-buffer (current-buffer))
                                     (initial-buffer
                                       (with-current-buffer ledger-edit-buffer
                                         (car ledger-edit-variable)))
                                     (ledger-file-buffer
                                       (with-current-buffer ledger-edit-buffer
                                         (cdr ledger-edit-variable))))
                                (if (and (eq initial-buffer (get-buffer ledger-report-buffer-name))
                                         (buffer-live-p ledger-file-buffer))
                                  (if (y-or-n-p "Save + Kill Indirect + Generate Report?")
                                    (progn
                                      (save-buffer)
                                      (kill-buffer ledger-edit-buffer))
                                    (message "You have chosen to exit this function.  Goodbye!"))
                                  (save-buffer)))))
    (define-key map [M-up] 'backward-paragraph)
    (define-key map [M-down] 'forward-paragraph)
    map)
  "Keymap to use in `ledger-edit-mode'.")

(defun ledger-edit-narrow-to-entity (initial-buffer target-group target-pos &optional toggle-status)
"Narrow buffer to the current subtree."
  (interactive)
  (let* ((exts (ledger-navigate-find-element-extents (point)))
         (beg (car exts))
         (end (cadr exts))
         (pt (point))
         (ledger-file-buffer (current-buffer))
         (indirect-buffer-name
           (cond
             ((not (and (> (- end beg) 1)            ; not an empty line
                        (<= pt end) (>= pt beg)        ; point is within the boundaries
                        (not (region-active-p)))) ; no active region
                (let ((debug-on-quit nil)
                      (quit-message (format "ledger-edit-narrow-to-entity:  Ooops -- beg (%s) | end (%s)" beg end)))
                  (signal 'quit `(,quit-message))))
             ((get-buffer (format "*%s*" ledger-file-buffer))
                (let ((debug-on-quit nil)
                      (quit-message (format "ledger-edit-narrow-to-entity:  (%s) is already in use!" (format "*%s*" ledger-file-buffer))))
                  (signal 'quit `(,quit-message))))
             ((buffer-base-buffer)
                (let ((debug-on-quit nil)
                      (quit-message (format "ledger-edit-narrow-to-entity::  (%s) is an indirect buffer!" (current-buffer))))
                  (signal 'quit `(,quit-message))))
             (t
                (format "*%s*" ledger-file-buffer))))
         (indirect-buffer (make-indirect-buffer ledger-file-buffer indirect-buffer-name 'clone)))
    (cond
      ((and (null toggle-status)
            (eq (nth 0 ledger-edit-display-method) 'split-window-below))
         (let* ((direction (nth 1 ledger-edit-display-method))
                (alist (nth 2 ledger-edit-display-method))
                (target-window
                  (cond
                    ((get-buffer-window indirect-buffer (selected-frame)))
                    ((window-in-direction direction))
                    (t
                      (split-window (selected-window) nil direction)))))
           (window--display-buffer indirect-buffer target-window 'window alist)
           (select-window target-window)))
      ((and (null toggle-status)
            (eq (nth 0 ledger-edit-display-method) 'switch-to-buffer))
        (switch-to-buffer indirect-buffer))
      (toggle-status
        (set-buffer indirect-buffer))
      (t
        (let ((debug-on-quit nil)
              (quit-message (format "ledger-edit-narrow-to-entity::  (%s) is not an expected display-method!" ledger-edit-display-method)))
          (signal 'quit `(,quit-message)))))
    (setq ledger-edit-variable (cons initial-buffer ledger-file-buffer))
    ;; overlay removal works best if done before narrowing to region, because line numbers change
    ;; and this caused a problem when left over overlays (invisible to the human eye) remained in
    ;; the buffer and caused a conflict with line numbers with +-mode.
    (narrow-to-region beg end)
    (ledger-edit-mode 1)
    (when (and (not (null target-group))
               (not (null target-pos)))
      (goto-char (point-min))
      (when (looking-at ledger-payee-regexp)
        (let ((indirect-buffer--date-beg (match-beginning 3))
              (indirect-buffer--status-beg (match-beginning 4))
              (indirect-buffer--payee-beg (match-beginning 6)))
          (cond
            ((and (not (null indirect-buffer--date-beg))
                  (eq target-group 'date))
               (goto-char (+ indirect-buffer--date-beg target-pos)))
            ((eq target-group 'amount)
               (forward-line 1)
               (when (looking-at ledger-edit-account-regexp)
                 ;;; . Subtract the dollar sign which is included in the report buffer,
                 ;;; but excluded from both the master ledger file and the indirect buffer.
                 ;;; . When looking at an automatic calculation in parentheses, stop there.
                 ;;; . When target-pos is 0, do not subtract anything.
                 ;;; . _must_ use `save-match-data' here!
                 ;;; . FIXME:  When the report buffer has a negative amount, there is an extra character that needs to be dealt with.
                 (cond ((save-match-data (string-match "^\(" (match-string 3)))
                          (goto-char (match-beginning 3)))
                       ((= target-pos 0)
                          (goto-char (match-beginning 3)))
                       (t
                          (goto-char (+ (match-beginning 3) (1- target-pos)))))))
            ((and (not (null indirect-buffer--status-beg))
                  (eq target-group 'status))
               (goto-char (+ indirect-buffer--status-beg target-pos)))
            ((and (not (null indirect-buffer--payee-beg))
                  (eq target-group 'payee))
               (goto-char (+ indirect-buffer--payee-beg target-pos)))
            (t
               (message "ledger-edit-narrow-to-entity:  catch-all (no match found)"))))))
    (when (not (null toggle-status))
      ;;; @lawlist is unsure what the usage would be to toggle status on a line
      ;;; containing an account, rather than a line containing the payee.
      ;;; As such, the default behavior here is to place point at the beginning
      ;;; of the transaction.
      (save-excursion
        (goto-char (point-min))
        (ledger-toggle-current nil))
      (ledger-edit-finalize 'toggle-status))))

(defun ledger-edit-fn (&optional toggle-status)
  (interactive)
  (let ((initial-buffer (current-buffer))
        target-group target-pos)
    (cond
      ((equal initial-buffer (get-buffer ledger-report-buffer-name))
        (let* ((ledger-init-pos (point))
               (prop (get-text-property (point) 'ledger-source))
               (file (car prop))
               (line (cdr prop))
               (ledger-file-buffer (and file (get-file-buffer file))))
          (cond
            ((null ledger-file-buffer)
               (let ((debug-on-quit nil)
                     (quit-message (format "ledger-edit-fn:  buffer of file (%s) cannot be located!" file)))
                 (signal 'quit `(,quit-message))))
            ((not (and file line))
               (let ((debug-on-quit nil)
                     (quit-message (format "ledger-edit-fn:  Cannot locate both file (%s) and line (%s)!" file line)))
                 (signal 'quit `(,quit-message))))
            ((= line 1)
              (when (not (y-or-n-p "ledger-edit-fn:  line = 1, which may indicate a problem.  Proceed?"))
                (let ((debug-on-quit nil))
                  (signal 'quit '("ledger-edit-fn:  You have chosen to a abort."))))))
          (save-excursion
            (goto-char (line-beginning-position))
            (when (looking-at ledger-edit-register-report-regexp)
              (let ((report-buffer--date (match-string 1))
                    (report-buffer--date-beg (match-beginning 1))
                    (report-buffer--date-end (match-end 1))
                    (report-buffer--amount (match-string 3))
                    (report-buffer--amount-beg (match-beginning 3))
                    (report-buffer--amount-end (match-end 3))
                    (report-buffer--status (match-string 5))
                    (report-buffer--status-beg (match-beginning 5))
                    (report-buffer--status-end (match-end 5))
                    (report-buffer--payee (match-string 6))
                    (report-buffer--payee-beg (match-beginning 6))
                    (report-buffer--payee-end (match-end 6)))
                ;;; We do something useless with unused let-bound variables so
                ;;; that we can have them handy for potential future use and/or
                ;;; debugging, but to silence the byte-compiler.
                (list report-buffer--payee report-buffer--status report-buffer--amount report-buffer--date)
                (cond
                  ((and (not (null report-buffer--date-beg))
                        (not (null report-buffer--date-end))
                        (>= ledger-init-pos report-buffer--date-beg)
                        (< ledger-init-pos report-buffer--date-end))
                    (setq target-group 'date
                          target-pos (- ledger-init-pos report-buffer--date-beg)))
                  ((and (not (null report-buffer--amount-beg))
                        (not (null report-buffer--amount-end))
                        (>= ledger-init-pos report-buffer--amount-beg)
                        (< ledger-init-pos report-buffer--amount-end))
                    (setq target-group 'amount
                          target-pos (- ledger-init-pos report-buffer--amount-beg)))
                  ((and (not (null report-buffer--status-beg))
                        (not (null report-buffer--status-end))
                        (>= ledger-init-pos report-buffer--status-beg)
                        (< ledger-init-pos report-buffer--status-end))
                    (setq target-group 'status
                          target-pos (- ledger-init-pos report-buffer--status-beg)))
                  ((and (not (null report-buffer--payee-beg))
                        (not (null report-buffer--payee-end))
                        (>= ledger-init-pos report-buffer--payee-beg)
                        (< ledger-init-pos report-buffer--payee-end))
                    (setq target-group 'payee
                          target-pos (- ledger-init-pos report-buffer--payee-beg)))
                  (t
                    (message "ledger-edit-fn:  catch-all (no match found)"))))))
          ;;; Since the report buffer still has focus, we need to temporarily
          ;;; work with the ledger-file-buffer.
          (with-current-buffer ledger-file-buffer
            (widen)
            (goto-char (point-min))
            (forward-line (1- line))
            (ledger-edit-narrow-to-entity initial-buffer target-group target-pos toggle-status))))
      (t
        (ledger-edit-narrow-to-entity initial-buffer target-group target-pos toggle-status)))))

(defun ledger-edit-finalize (&optional toggle-status)
  (interactive)
  (let* ((ledger-init-pos (point))
         (ledger-edit-buffer (current-buffer))
         (initial-buffer (with-current-buffer ledger-edit-buffer (car ledger-edit-variable)))
         (ledger-file-buffer (with-current-buffer ledger-edit-buffer (cdr ledger-edit-variable)))
         (orig-buffer-modified (buffer-modified-p ledger-file-buffer))
         (payee-matched-data-p
           (save-excursion
             (goto-char (point-min))
             (looking-at ledger-payee-regexp)))
         (indirect-buffer--date (and payee-matched-data-p (match-string 3)))
         (indirect-buffer--date-beg (and payee-matched-data-p (match-beginning 3)))
         (indirect-buffer--date-end (and payee-matched-data-p (match-end 3)))
         (indirect-buffer--status (and payee-matched-data-p (match-string 4)))
         (indirect-buffer--status-beg (and payee-matched-data-p (match-beginning 4)))
         (indirect-buffer--status-end (and payee-matched-data-p (match-end 4)))
         (indirect-buffer--payee (and payee-matched-data-p (match-string 6)))
         (indirect-buffer--payee-beg (and payee-matched-data-p (match-beginning 6)))
         (indirect-buffer--payee-end (and payee-matched-data-p (match-end 6)))
         (account-matched-data-p
           (save-excursion
             (goto-char (point-min))
             (forward-line 1)
             (looking-at ledger-edit-account-regexp)))
         (indirect-buffer--amount (and account-matched-data-p (match-string 3)))
         (indirect-buffer--amount-beg (and account-matched-data-p (match-beginning 3)))
         (indirect-buffer--amount-end (and account-matched-data-p (match-end 3)))
         target-group target-pos)
    (cond
      ((and (not (null indirect-buffer--date-beg))
            (not (null indirect-buffer--date-end))
            (>= ledger-init-pos indirect-buffer--date-beg)
            (< ledger-init-pos indirect-buffer--date-end))
        (setq target-group 'date
              target-pos (- ledger-init-pos indirect-buffer--date-beg)))
      ((and (not (null indirect-buffer--amount-beg))
            (not (null indirect-buffer--amount-end))
            (>= ledger-init-pos indirect-buffer--amount-beg)
            (< ledger-init-pos indirect-buffer--amount-end))
        (setq target-group 'amount
              target-pos (- ledger-init-pos indirect-buffer--amount-beg)))
      ((and (not (null indirect-buffer--status-beg))
            (not (null indirect-buffer--status-end))
            (>= ledger-init-pos indirect-buffer--status-beg)
            (< ledger-init-pos indirect-buffer--status-end))
        (setq target-group 'status
              target-pos (- ledger-init-pos indirect-buffer--status-beg)))
      ((and (not (null indirect-buffer--payee-beg))
            (not (null indirect-buffer--payee-end))
            (>= ledger-init-pos indirect-buffer--payee-beg)
            (< ledger-init-pos indirect-buffer--payee-end))
        (setq target-group 'payee
              target-pos (- ledger-init-pos indirect-buffer--payee-beg))))
    (when ledger-edit-buffer
      (cond
        ((and (null toggle-status)
              (eq (nth 0 ledger-edit-display-method) 'split-window-below))
           (delete-window))
        ((and (null toggle-status)
              (eq (nth 0 ledger-edit-display-method) 'switch-to-buffer)
          (message "ledger-edit-finalize:  Maybe write some code here.")))
        (toggle-status
          (message "ledger-edit-finalize:  toggle-status is non-nil."))
        (t
          (let ((debug-on-quit nil)
                (quit-message (format "ledger-edit-finalize:  (%s) is not an expected display-method!" ledger-edit-display-method)))
            (signal 'quit `(,quit-message)))))
      (kill-buffer ledger-edit-buffer))
    (if (and (eq initial-buffer (get-buffer ledger-report-buffer-name))
             (buffer-live-p ledger-file-buffer))
      (let* ((ledger-edit-after-save-fn (list (lambda ()
               (unless (or (derived-mode-p 'ledger-mode)
                           (derived-mode-p 'ledger-report-mode))
                 (user-error "Not in a ledger-mode or ledger-report-mode buffer"))
               (when (and ledger-report-auto-refresh
                          (get-buffer ledger-report-buffer-name))
                 (with-current-buffer (get-buffer ledger-report-buffer-name)
                   (with-silent-modifications
                     (erase-buffer)
                     (ledger-do-report ledger-report-cmd)
                     (when ledger-report-is-reversed
                       (ledger-report-reverse-lines))))
                 (set-window-buffer (selected-window) (get-buffer ledger-report-buffer-name))
                 (run-hooks 'ledger-report-after-report-hook)))))
             (report-buffer-finalize-fn `(lambda ()
               (with-current-buffer (get-buffer ledger-report-buffer-name)
                 (catch 'done
                   (goto-char (point-min))
                   (while (re-search-forward (regexp-quote ,indirect-buffer--payee) nil t)
                     (when (save-excursion
                             (goto-char (line-beginning-position))
                             (looking-at ledger-edit-register-report-regexp))
                       (let ((report-buffer--date (match-string 1))
                             (report-buffer--date-beg (match-beginning 1))
                             (report-buffer--date-end (match-end 1))
                             (report-buffer--amount (match-string 3))
                             (report-buffer--amount-beg (match-beginning 3))
                             (report-buffer--amount-end (match-end 3))
                             (report-buffer--status (match-string 5))
                             (report-buffer--status-beg (match-beginning 5))
                             (report-buffer--status-end (match-end 5))
                             (report-buffer--payee (match-string 6))
                             (report-buffer--payee-beg (match-beginning 6))
                             (report-buffer--payee-end (match-end 6)))
                         (when (and (equal ,indirect-buffer--date report-buffer--date)
                                    (equal ,indirect-buffer--status report-buffer--status)
                                    (equal ,indirect-buffer--payee report-buffer--payee))
                           (cond
                             ((and (not (null report-buffer--date-beg))
                                   (eq ',target-group 'date))
                               (goto-char (+ report-buffer--date-beg ,target-pos)))
                             ((and (not (null report-buffer--amount-beg))
                                   (eq ',target-group 'amount))
                               (if (equal ,indirect-buffer--amount report-buffer--amount)
                                 (goto-char (+ report-buffer--amount-beg ,target-pos))
                                 (goto-char report-buffer--amount-beg)))
                             ((and (not (null report-buffer--status-beg))
                                   (eq ',target-group 'status))
                               (goto-char (+ report-buffer--status-beg ,target-pos)))
                             ((and (not (null report-buffer--payee-beg))
                                   (eq ',target-group 'payee))
                               (goto-char (+ report-buffer--payee-beg ,target-pos)))
                             (t
                               (goto-char (match-beginning 0))
                               (message "ledger-edit-finalize:  catch-all (no match found)")))
                           (recenter)
                           (throw 'done nil)))))))))
             (ledger-report-after-report-hook (list report-buffer-finalize-fn)))
        (if orig-buffer-modified
          (with-current-buffer ledger-file-buffer
            ;;; Because `ledger-mode.el' locally binds `ledger-report-redo' to
            ;;; the `after-save-hook', the override needs to be local too.
            (let ((after-save-hook ledger-edit-after-save-fn))
              (save-buffer)))
          (run-hooks 'ledger-report-after-report-hook)))
      ;;; ELSE:  We are not dealing with an _initial_ report-buffer situation.
      (with-current-buffer ledger-file-buffer
        ;;; Because `ledger-mode.el' locally binds `ledger-report-redo' to
        ;;; the `after-save-hook', the override needs to be local too.
        (when orig-buffer-modified
          (save-buffer))
        ;;; FIXME:  When NULL target-group/pos, would it be preferable to leave
        ;;; point in the file buffer right where it was at the outset, or instead
        ;;; goto to the beginning of the xact?  If the latter, then _move_
        ;;; `ledger-navigate-beginning-of-xact' to just below this comment.
        (if (or (null target-group)
                (null target-pos))
          (message "ledger-edit-finalize:  target-group (%s) | target-pos (%s)" target-group target-pos)
          (ledger-navigate-beginning-of-xact)
          (when (looking-at ledger-payee-regexp)
            (let ((file-buffer--date-beg (match-beginning 3))
                  (file-buffer--status-beg (match-beginning 4))
                  (file-buffer--payee-beg (match-beginning 6)))
              (cond
                ((and (not (null file-buffer--date-beg))
                      (eq target-group 'date))
                   (goto-char (+ file-buffer--date-beg target-pos)))
                ((eq target-group 'amount)
                   (forward-line 1)
                   (when (looking-at ledger-edit-account-regexp)
                     (goto-char (+ (match-beginning 3) target-pos))))
                ((and (not (null file-buffer--status-beg))
                      (eq target-group 'status))
                   (goto-char (+ file-buffer--status-beg target-pos)))
                ((and (not (null file-buffer--payee-beg))
                      (eq target-group 'payee))
                   (goto-char (+ file-buffer--payee-beg target-pos)))
                (t
                   (message "ledger-edit-finalize:  catch-all (no match found)"))))))))))

(defun ledger-edit-toggle-status ()
"From the report buffer, toggle the status of an entry at point."
  (interactive)
  (ledger-edit-fn 'toggle-status))

(define-minor-mode ledger-edit-mode
"By virtue of `(make-indirect-buffer buffer bname 'clone)` within `ledger-edit-fn`,
the argument `'clone` keeps the existing settings -- i.e., `ledger-mode` with ...."
  :init-value nil
  :lighter " ledger-edit-fn"
  :keymap ledger-edit-mode-map
  :global nil
  :group 'ledger-edit-fn
  (cond
    (ledger-edit-mode
      (setq buffer-read-only nil)
      (setq truncate-lines nil)
      (when (called-interactively-p 'any)
        (message "Turned ON `ledger-edit-mode'.")))
    (t
      (when (called-interactively-p 'any)
        (message "Turned OFF `ledger-edit-mode'.")))))
lawlist commented 3 years ago

Initial post has been edited to add support for toggling the status of the payee from the report buffer using the command C-c C-c.