ledger-reconcile.el 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. ;;; ledger-reconcile.el --- Helper code for use with the "ledger" command-line tool
  2. ;; Copyright (C) 2003-2016 John Wiegley (johnw AT gnu DOT org)
  3. ;; This file is not part of GNU Emacs.
  4. ;; This is free software; you can redistribute it and/or modify it under
  5. ;; the terms of the GNU General Public License as published by the Free
  6. ;; Software Foundation; either version 2, or (at your option) any later
  7. ;; version.
  8. ;;
  9. ;; This is distributed in the hope that it will be useful, but WITHOUT
  10. ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11. ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  12. ;; for more details.
  13. ;;
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs; see the file COPYING. If not, write to the
  16. ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  17. ;; MA 02110-1301 USA.
  18. ;; Reconcile mode
  19. ;;; Commentary:
  20. ;; Code to handle reconciling Ledger files wiht outside sources
  21. ;;; Code:
  22. (require 'easymenu)
  23. (require 'ledger-init)
  24. (defvar ledger-buf nil)
  25. (defvar ledger-bufs nil)
  26. (defvar ledger-acct nil)
  27. (defvar ledger-target nil)
  28. (defgroup ledger-reconcile nil
  29. "Options for Ledger-mode reconciliation"
  30. :group 'ledger)
  31. (defcustom ledger-recon-buffer-name "*Reconcile*"
  32. "Name to use for reconciliation buffer."
  33. :group 'ledger-reconcile)
  34. (defcustom ledger-narrow-on-reconcile t
  35. "If t, limit transactions shown in main buffer to those matching the reconcile regex."
  36. :type 'boolean
  37. :group 'ledger-reconcile)
  38. (defcustom ledger-buffer-tracks-reconcile-buffer t
  39. "If t, then when the cursor is moved to a new transaction in the reconcile buffer.
  40. Then that transaction will be shown in its source buffer."
  41. :type 'boolean
  42. :group 'ledger-reconcile)
  43. (defcustom ledger-reconcile-force-window-bottom nil
  44. "If t, make the reconcile window appear along the bottom of the register window and resize."
  45. :type 'boolean
  46. :group 'ledger-reconcile)
  47. (defcustom ledger-reconcile-toggle-to-pending t
  48. "If t, then toggle between uncleared and pending.
  49. reconcile-finish will mark all pending posting cleared."
  50. :type 'boolean
  51. :group 'ledger-reconcile)
  52. (defcustom ledger-reconcile-default-date-format ledger-default-date-format
  53. "Date format for the reconcile buffer.
  54. Default is ledger-default-date-format."
  55. :type 'string
  56. :group 'ledger-reconcile)
  57. (defcustom ledger-reconcile-target-prompt-string "Target amount for reconciliation "
  58. "Prompt for recon target."
  59. :type 'string
  60. :group 'ledger-reconcile)
  61. (defcustom ledger-reconcile-buffer-header "Reconciling account %s\n\n"
  62. "Default header string for the reconcile buffer.
  63. If non-nil, the name of the account being reconciled will be substituted
  64. into the '%s'. If nil, no header will be displayed."
  65. :type 'string
  66. :group 'ledger-reconcile)
  67. (defcustom ledger-reconcile-buffer-line-format "%(date)s %-4(code)s %-50(payee)s %-30(account)s %15(amount)s\n"
  68. "Format string for the ledger reconcile posting format.
  69. Available fields are date, status, code, payee, account,
  70. amount. The format for each field is %WIDTH(FIELD), WIDTH can be
  71. preced by a minus sign which mean to left justify and pad the
  72. field. WIDTH is the minimum number of characters to display;
  73. if string is longer, it is not truncated unless
  74. ledger-reconcile-buffer-payee-max-chars or
  75. ledger-reconcile-buffer-account-max-chars is defined."
  76. :type 'string
  77. :group 'ledger-reconcile)
  78. (defcustom ledger-reconcile-buffer-payee-max-chars -1
  79. "If positive, truncate payee name right side to max number of characters."
  80. :type 'integer
  81. :group 'ledger-reconcile)
  82. (defcustom ledger-reconcile-buffer-account-max-chars -1
  83. "If positive, truncate account name left side to max number of characters."
  84. :type 'integer
  85. :group 'ledger-reconcile)
  86. (defcustom ledger-reconcile-sort-key "(0)"
  87. "Key for sorting reconcile buffer.
  88. Possible values are '(date)', '(amount)', '(payee)' or '(0)' for no sorting, i.e. using ledger file order."
  89. :type 'string
  90. :group 'ledger-reconcile)
  91. (defcustom ledger-reconcile-insert-effective-date nil
  92. "If t, prompt for effective date when clearing transactions during reconciliation."
  93. :type 'boolean
  94. :group 'ledger-reconcile)
  95. (defcustom ledger-reconcile-finish-force-quit nil
  96. "If t, will force closing reconcile window after \\[ledger-reconcile-finish]."
  97. :type 'boolean
  98. :group 'ledger-reconcile)
  99. ;; s-functions below are copied from Magnars' s.el
  100. ;; prefix ledger-reconcile- is added to not conflict with s.el
  101. (defun ledger-reconcile-s-pad-left (len padding s)
  102. "If S is shorter than LEN, pad it with PADDING on the left."
  103. (let ((extra (max 0 (- len (length s)))))
  104. (concat (make-string extra (string-to-char padding))
  105. s)))
  106. (defun ledger-reconcile-s-pad-right (len padding s)
  107. "If S is shorter than LEN, pad it with PADDING on the right."
  108. (let ((extra (max 0 (- len (length s)))))
  109. (concat s
  110. (make-string extra (string-to-char padding)))))
  111. (defun ledger-reconcile-s-left (len s)
  112. "Return up to the LEN first chars of S."
  113. (if (> (length s) len)
  114. (substring s 0 len)
  115. s))
  116. (defun ledger-reconcile-s-right (len s)
  117. "Return up to the LEN last chars of S."
  118. (let ((l (length s)))
  119. (if (> l len)
  120. (substring s (- l len) l)
  121. s)))
  122. (defun ledger-reconcile-truncate-right (str len)
  123. "Truncate STR right side with max LEN characters, and pad with '…' if truncated."
  124. (if (and (>= len 0) (> (length str) len))
  125. (ledger-reconcile-s-pad-right len "…" (ledger-reconcile-s-left (- len 1) str))
  126. str))
  127. (defun ledger-reconcile-truncate-left (str len)
  128. "Truncate STR left side with max LEN characters, and pad with '…' if truncated."
  129. (if (and (>= len 0) (> (length str) len))
  130. (ledger-reconcile-s-pad-left len "…" (ledger-reconcile-s-right (- len 1) str))
  131. str))
  132. (defun ledger-reconcile-get-cleared-or-pending-balance (buffer account)
  133. "Use BUFFER to Calculate the cleared or pending balance of the ACCOUNT."
  134. ;; these vars are buffer local, need to hold them for use in the
  135. ;; temp buffer below
  136. (with-temp-buffer
  137. ;; note that in the line below, the --format option is
  138. ;; separated from the actual format string. emacs does not
  139. ;; split arguments like the shell does, so you need to
  140. ;; specify the individual fields in the command line.
  141. (if (ledger-exec-ledger buffer (current-buffer)
  142. "balance" "--limit" "cleared or pending" "--empty" "--collapse"
  143. "--format" "%(scrub(display_total))" account)
  144. (ledger-split-commodity-string
  145. (buffer-substring-no-properties (point-min) (point-max))))))
  146. (defun ledger-display-balance ()
  147. "Display the cleared-or-pending balance.
  148. And calculate the target-delta of the account being reconciled."
  149. (interactive)
  150. (let* ((pending (ledger-reconcile-get-cleared-or-pending-balance ledger-buf ledger-acct)))
  151. (when pending
  152. (if ledger-target
  153. (message "Cleared and Pending balance: %s, Difference from target: %s"
  154. (ledger-commodity-to-string pending)
  155. (ledger-commodity-to-string (-commodity ledger-target pending)))
  156. (message "Pending balance: %s"
  157. (ledger-commodity-to-string pending))))))
  158. (defun ledger-is-stdin (file)
  159. "True if ledger FILE is standard input."
  160. (or
  161. (equal file "")
  162. (equal file "<stdin>")
  163. (equal file "/dev/stdin")))
  164. (defun ledger-reconcile-get-buffer (where)
  165. "Return a buffer from WHERE the transaction is."
  166. (if (bufferp (car where))
  167. (car where)
  168. (error "Function ledger-reconcile-get-buffer: Buffer not set")))
  169. (defun ledger-reconcile-toggle ()
  170. "Toggle the current transaction, and mark the recon window."
  171. (interactive)
  172. (beginning-of-line)
  173. (let ((where (get-text-property (point) 'where))
  174. (inhibit-read-only t)
  175. status)
  176. (when (ledger-reconcile-get-buffer where)
  177. (with-current-buffer (ledger-reconcile-get-buffer where)
  178. (ledger-navigate-to-line (cdr where))
  179. (forward-char)
  180. (setq status (ledger-toggle-current (if ledger-reconcile-toggle-to-pending
  181. 'pending
  182. 'cleared)))
  183. (when ledger-reconcile-insert-effective-date
  184. ;; Ask for effective date & insert it
  185. (ledger-insert-effective-date)))
  186. ;; remove the existing face and add the new face
  187. (remove-text-properties (line-beginning-position)
  188. (line-end-position)
  189. (list 'face))
  190. (cond ((eq status 'pending)
  191. (add-text-properties (line-beginning-position)
  192. (line-end-position)
  193. (list 'face 'ledger-font-reconciler-pending-face )))
  194. ((eq status 'cleared)
  195. (add-text-properties (line-beginning-position)
  196. (line-end-position)
  197. (list 'face 'ledger-font-reconciler-cleared-face )))
  198. (t
  199. (add-text-properties (line-beginning-position)
  200. (line-end-position)
  201. (list 'face 'ledger-font-reconciler-uncleared-face )))))
  202. (forward-line)
  203. (beginning-of-line)
  204. (ledger-display-balance)))
  205. (defun ledger-reconcile-refresh ()
  206. "Force the reconciliation window to refresh.
  207. Return the number of uncleared xacts found."
  208. (interactive)
  209. (let ((inhibit-read-only t))
  210. (erase-buffer)
  211. (prog1
  212. (ledger-do-reconcile ledger-reconcile-sort-key)
  213. (set-buffer-modified-p t))))
  214. (defun ledger-reconcile-refresh-after-save ()
  215. "Refresh the recon-window after the ledger buffer is saved."
  216. (let ((curbufwin (get-buffer-window (current-buffer)))
  217. (curpoint (point))
  218. (recon-buf (get-buffer ledger-recon-buffer-name)))
  219. (when (buffer-live-p recon-buf)
  220. (with-current-buffer recon-buf
  221. (ledger-reconcile-refresh)
  222. (set-buffer-modified-p nil))
  223. (when curbufwin
  224. (select-window curbufwin)
  225. (goto-char curpoint)))))
  226. (defun ledger-reconcile-add ()
  227. "Use ledger xact to add a new transaction."
  228. (interactive)
  229. (with-current-buffer ledger-buf
  230. (call-interactively #'ledger-add-transaction))
  231. (ledger-reconcile-refresh))
  232. (defun ledger-reconcile-delete ()
  233. "Delete the transactions pointed to in the recon window."
  234. (interactive)
  235. (let ((where (get-text-property (point) 'where)))
  236. (when (ledger-reconcile-get-buffer where)
  237. (with-current-buffer (ledger-reconcile-get-buffer where)
  238. (ledger-navigate-to-line (cdr where))
  239. (ledger-delete-current-transaction (point)))
  240. (let ((inhibit-read-only t))
  241. (goto-char (line-beginning-position))
  242. (delete-region (point) (1+ (line-end-position)))
  243. (set-buffer-modified-p t))
  244. (ledger-reconcile-refresh))))
  245. (defun ledger-reconcile-visit (&optional come-back)
  246. "Recenter ledger buffer on transaction and COME-BACK if non-nil."
  247. (interactive)
  248. (beginning-of-line)
  249. (let* ((where (get-text-property (1+ (point)) 'where))
  250. (target-buffer (if where
  251. (ledger-reconcile-get-buffer where)
  252. nil))
  253. (cur-win (get-buffer-window (get-buffer ledger-recon-buffer-name))))
  254. (when target-buffer
  255. (switch-to-buffer-other-window target-buffer)
  256. (ledger-navigate-to-line (cdr where))
  257. (forward-char)
  258. (recenter)
  259. (ledger-highlight-xact-under-point)
  260. (forward-char -1)
  261. (when (and come-back cur-win)
  262. (select-window cur-win)
  263. (get-buffer ledger-recon-buffer-name)))))
  264. (defun ledger-reconcile-save ()
  265. "Save the ledger buffer."
  266. (interactive)
  267. (let ((cur-buf (current-buffer))
  268. (cur-point (point)))
  269. (dolist (buf (cons ledger-buf ledger-bufs))
  270. (with-current-buffer buf
  271. (basic-save-buffer)))
  272. (switch-to-buffer-other-window cur-buf)
  273. (goto-char cur-point)))
  274. (defun ledger-reconcile-finish ()
  275. "Mark all pending posting or transactions as cleared.
  276. Depends on ledger-reconcile-clear-whole-transactions, save the buffers
  277. and exit reconcile mode if `ledger-reconcile-finish-force-quit'"
  278. (interactive)
  279. (save-excursion
  280. (goto-char (point-min))
  281. (while (not (eobp))
  282. (let ((where (get-text-property (point) 'where))
  283. (face (get-text-property (point) 'face)))
  284. (if (eq face 'ledger-font-reconciler-pending-face)
  285. (with-current-buffer (ledger-reconcile-get-buffer where)
  286. (ledger-navigate-to-line (cdr where))
  287. (ledger-toggle-current 'cleared))))
  288. (forward-line 1)))
  289. (ledger-reconcile-save)
  290. (when ledger-reconcile-finish-force-quit
  291. (ledger-reconcile-quit)))
  292. (defun ledger-reconcile-quit ()
  293. "Quit the reconcile window without saving ledger buffer."
  294. (interactive)
  295. (let ((recon-buf (get-buffer ledger-recon-buffer-name))
  296. buf)
  297. (if recon-buf
  298. (with-current-buffer recon-buf
  299. (ledger-reconcile-quit-cleanup)
  300. (setq buf ledger-buf)
  301. ;; Make sure you delete the window before you delete the buffer,
  302. ;; otherwise, madness ensues
  303. (delete-window (get-buffer-window recon-buf))
  304. (kill-buffer recon-buf)
  305. (set-window-buffer (selected-window) buf)))))
  306. (defun ledger-reconcile-quit-cleanup ()
  307. "Cleanup all hooks established by reconcile mode."
  308. (interactive)
  309. (let ((buf ledger-buf))
  310. (if (buffer-live-p buf)
  311. (with-current-buffer buf
  312. (remove-hook 'after-save-hook 'ledger-reconcile-refresh-after-save t)
  313. (when ledger-narrow-on-reconcile
  314. (ledger-occur-mode -1)
  315. (ledger-highlight-xact-under-point))))))
  316. (defun ledger-marker-where-xact-is (emacs-xact posting)
  317. "Find the position of the EMACS-XACT in the `ledger-buf'.
  318. POSTING is used in `ledger-clear-whole-transactions' is nil."
  319. (let ((buf (if (ledger-is-stdin (nth 0 emacs-xact))
  320. ledger-buf
  321. (find-file-noselect (nth 0 emacs-xact)))))
  322. (cons
  323. buf
  324. (if ledger-clear-whole-transactions
  325. (nth 1 emacs-xact) ;; return line-no of xact
  326. (nth 0 posting))))) ;; return line-no of posting
  327. (defun ledger-reconcile-compile-format-string (fstr)
  328. "Return a function that implements the format string in FSTR."
  329. (let (fields
  330. (start 0))
  331. (while (string-match "(\\(.*?\\))" fstr start)
  332. (setq fields (cons (intern (match-string 1 fstr)) fields))
  333. (setq start (match-end 0)))
  334. (setq fields (list* 'format (replace-regexp-in-string "(.*?)" "" fstr) (nreverse fields)))
  335. `(lambda (date code status payee account amount)
  336. ,fields)))
  337. (defun ledger-reconcile-format-posting (beg where fmt date code status payee account amount)
  338. "Format posting for the reconcile buffer."
  339. (insert (funcall fmt date code status payee account amount))
  340. ; Set face depending on cleared status
  341. (if status
  342. (if (eq status 'pending)
  343. (set-text-properties beg (1- (point))
  344. (list 'face 'ledger-font-reconciler-pending-face
  345. 'where where))
  346. (set-text-properties beg (1- (point))
  347. (list 'face 'ledger-font-reconciler-cleared-face
  348. 'where where)))
  349. (set-text-properties beg (1- (point))
  350. (list 'face 'ledger-font-reconciler-uncleared-face
  351. 'where where))))
  352. (defun ledger-reconcile-format-xact (xact fmt)
  353. "Format XACT using FMT."
  354. (let ((date-format (or (cdr (assoc "date-format" ledger-environment-alist))
  355. ledger-default-date-format)))
  356. (dolist (posting (nthcdr 5 xact))
  357. (let ((beg (point))
  358. (where (ledger-marker-where-xact-is xact posting)))
  359. (ledger-reconcile-format-posting beg
  360. where
  361. fmt
  362. (format-time-string date-format (nth 2 xact)) ; date
  363. (if (nth 3 xact) (nth 3 xact) "") ; code
  364. (nth 3 posting) ; status
  365. (ledger-reconcile-truncate-right
  366. (nth 4 xact) ; payee
  367. ledger-reconcile-buffer-payee-max-chars)
  368. (ledger-reconcile-truncate-left
  369. (nth 1 posting) ; account
  370. ledger-reconcile-buffer-account-max-chars)
  371. (nth 2 posting)))))) ; amount
  372. (defun ledger-do-reconcile (&optional sort)
  373. "SORT the uncleared transactions in the account and display them in the *Reconcile* buffer.
  374. Return a count of the uncleared transactions."
  375. (let* ((buf ledger-buf)
  376. (account ledger-acct)
  377. (ledger-success nil)
  378. (sort-by (if sort
  379. sort
  380. "(date)"))
  381. (xacts
  382. (with-temp-buffer
  383. (when (ledger-exec-ledger buf (current-buffer)
  384. "--uncleared" "--real" "emacs" "--sort" sort-by account)
  385. (setq ledger-success t)
  386. (goto-char (point-min))
  387. (unless (eobp)
  388. (if (looking-at "(")
  389. (read (current-buffer))))))) ;current-buffer is the *temp* created above
  390. (fmt (ledger-reconcile-compile-format-string ledger-reconcile-buffer-line-format)))
  391. (if (and ledger-success (> (length xacts) 0))
  392. (progn
  393. (insert (format ledger-reconcile-buffer-header account))
  394. (dolist (xact xacts)
  395. (ledger-reconcile-format-xact xact fmt))
  396. (goto-char (point-max))
  397. (delete-char -1)) ;gets rid of the extra line feed at the bottom of the list
  398. (if ledger-success
  399. (insert (concat "There are no uncleared entries for " account))
  400. (insert "Ledger has reported a problem. Check *Ledger Error* buffer.")))
  401. (goto-char (point-min))
  402. (set-buffer-modified-p nil)
  403. (setq buffer-read-only t)
  404. (ledger-reconcile-ensure-xacts-visible)
  405. (length xacts)))
  406. (defun ledger-reconcile-ensure-xacts-visible ()
  407. "Ensure the last of the visible transactions in the ledger buffer is at the bottom of the main window.
  408. The key to this is to ensure the window is selected when the buffer point is
  409. moved and recentered. If they aren't strange things happen."
  410. (let ((recon-window (get-buffer-window (get-buffer ledger-recon-buffer-name))))
  411. (when recon-window
  412. (fit-window-to-buffer recon-window)
  413. (with-current-buffer ledger-buf
  414. (add-hook 'kill-buffer-hook 'ledger-reconcile-quit nil t)
  415. (if (get-buffer-window ledger-buf)
  416. (select-window (get-buffer-window ledger-buf)))
  417. (goto-char (point-max))
  418. (recenter -1))
  419. (select-window recon-window)
  420. (ledger-reconcile-visit t))
  421. (add-hook 'post-command-hook 'ledger-reconcile-track-xact nil t)))
  422. (defun ledger-reconcile-track-xact ()
  423. "Force the ledger buffer to recenter on the transaction at point in the reconcile buffer."
  424. (if (and ledger-buffer-tracks-reconcile-buffer
  425. (member this-command (list 'next-line
  426. 'previous-line
  427. 'mouse-set-point
  428. 'ledger-reconcile-toggle
  429. 'end-of-buffer
  430. 'beginning-of-buffer)))
  431. (save-excursion
  432. (ledger-reconcile-visit t))))
  433. (defun ledger-reconcile-open-windows (buf rbuf)
  434. "Ensure that the ledger buffer BUF is split by RBUF."
  435. (if ledger-reconcile-force-window-bottom
  436. ;;create the *Reconcile* window directly below the ledger buffer.
  437. (set-window-buffer (split-window (get-buffer-window buf) nil nil) rbuf)
  438. (pop-to-buffer rbuf)))
  439. (defun ledger-reconcile-check-valid-account (account)
  440. "Check to see if ACCOUNT exists in the ledger file"
  441. (if (> (length account) 0)
  442. (save-excursion
  443. (goto-char (point-min))
  444. (search-forward account nil t))))
  445. (defun ledger-reconcile ()
  446. "Start reconciling, prompt for account."
  447. (interactive)
  448. (let ((account (ledger-read-account-with-prompt "Account to reconcile"))
  449. (buf (current-buffer))
  450. (rbuf (get-buffer ledger-recon-buffer-name)))
  451. (when (ledger-reconcile-check-valid-account account)
  452. (add-hook 'after-save-hook 'ledger-reconcile-refresh-after-save nil t)
  453. (if rbuf ;; *Reconcile* already exists
  454. (with-current-buffer rbuf
  455. (set 'ledger-acct account) ;; already buffer local
  456. (when (not (eq buf rbuf))
  457. ;; called from some other ledger-mode buffer
  458. (ledger-reconcile-quit-cleanup)
  459. (setq ledger-buf buf)) ;; should already be buffer-local
  460. (unless (get-buffer-window rbuf)
  461. (ledger-reconcile-open-windows buf rbuf)))
  462. ;; no recon-buffer, starting from scratch.
  463. (with-current-buffer (setq rbuf
  464. (get-buffer-create ledger-recon-buffer-name))
  465. (ledger-reconcile-open-windows buf rbuf)
  466. (ledger-reconcile-mode)
  467. (make-local-variable 'ledger-target)
  468. (set (make-local-variable 'ledger-buf) buf)
  469. (set (make-local-variable 'ledger-acct) account)))
  470. ;; Narrow the ledger buffer
  471. (with-current-buffer rbuf
  472. (save-excursion
  473. (if ledger-narrow-on-reconcile
  474. (ledger-occur account)))
  475. (if (> (ledger-reconcile-refresh) 0)
  476. (ledger-reconcile-change-target))
  477. (ledger-display-balance)))))
  478. (defvar ledger-reconcile-mode-abbrev-table)
  479. (defun ledger-reconcile-change-target ()
  480. "Change the target amount for the reconciliation process."
  481. (interactive)
  482. (setq ledger-target (ledger-read-commodity-string ledger-reconcile-target-prompt-string)))
  483. (defmacro ledger-reconcile-change-sort-key-and-refresh (sort-by)
  484. "Set the sort-key to SORT-BY."
  485. `(lambda ()
  486. (interactive)
  487. (setq ledger-reconcile-sort-key ,sort-by)
  488. (ledger-reconcile-refresh)))
  489. (defvar ledger-reconcile-mode-map
  490. (let ((map (make-sparse-keymap)))
  491. (define-key map [(control ?m)] 'ledger-reconcile-visit)
  492. (define-key map [return] 'ledger-reconcile-visit)
  493. (define-key map [(control ?x) (control ?s)] 'ledger-reconcile-save)
  494. (define-key map [(control ?l)] 'ledger-reconcile-refresh)
  495. (define-key map [(control ?c) (control ?c)] 'ledger-reconcile-finish)
  496. (define-key map [? ] 'ledger-reconcile-toggle)
  497. (define-key map [?a] 'ledger-reconcile-add)
  498. (define-key map [?d] 'ledger-reconcile-delete)
  499. (define-key map [?g] 'ledger-reconcile);
  500. (define-key map [?n] 'next-line)
  501. (define-key map [?p] 'previous-line)
  502. (define-key map [?t] 'ledger-reconcile-change-target)
  503. (define-key map [?s] 'ledger-reconcile-save)
  504. (define-key map [?q] 'ledger-reconcile-quit)
  505. (define-key map [?b] 'ledger-display-balance)
  506. (define-key map [(control ?c) (control ?o)] (ledger-reconcile-change-sort-key-and-refresh "(0)"))
  507. (define-key map [(control ?c) (control ?a)] (ledger-reconcile-change-sort-key-and-refresh "(amount)"))
  508. (define-key map [(control ?c) (control ?d)] (ledger-reconcile-change-sort-key-and-refresh "(date)"))
  509. (define-key map [(control ?c) (control ?p)] (ledger-reconcile-change-sort-key-and-refresh "(payee)"))
  510. map)
  511. "Keymap for `ledger-reconcile-mode'.")
  512. (easy-menu-define ledger-reconcile-mode-menu ledger-reconcile-mode-map
  513. "Ledger reconcile menu"
  514. `("Reconcile"
  515. ["Save" ledger-reconcile-save]
  516. ["Refresh" ledger-reconcile-refresh]
  517. ["Finish" ledger-reconcile-finish]
  518. "---"
  519. ["Reconcile New Account" ledger-reconcile]
  520. "---"
  521. ["Change Target Balance" ledger-reconcile-change-target]
  522. ["Show Cleared Balance" ledger-display-balance]
  523. "---"
  524. ["Sort by payee" ,(ledger-reconcile-change-sort-key-and-refresh "(payee)")]
  525. ["Sort by date" ,(ledger-reconcile-change-sort-key-and-refresh "(date)")]
  526. ["Sort by amount" ,(ledger-reconcile-change-sort-key-and-refresh "(amount)")]
  527. ["Sort by file order" ,(ledger-reconcile-change-sort-key-and-refresh "(0)")]
  528. "---"
  529. ["Toggle Entry" ledger-reconcile-toggle]
  530. ["Add Entry" ledger-reconcile-add]
  531. ["Delete Entry" ledger-reconcile-delete]
  532. "---"
  533. ["Next Entry" next-line]
  534. ["Visit Source" ledger-reconcile-visit]
  535. ["Previous Entry" previous-line]
  536. "---"
  537. ["Quit" ledger-reconcile-quit]
  538. ))
  539. (define-derived-mode ledger-reconcile-mode text-mode "Reconcile"
  540. "A mode for reconciling ledger entries.")
  541. (provide 'ledger-reconcile)
  542. ;;; ledger-reconcile.el ends here