Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
lawlist opened this issue Jan 10, 2021 · 1 comment
Open

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

lawlist opened this issue Jan 10, 2021 · 1 comment

Comments

@lawlist
Copy link

lawlist commented Jan 10, 2021

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
Copy link
Author

lawlist commented Jan 16, 2021

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant