3131(require 'ledger-exec )
3232(require 'ledger-post )
3333(declare-function ledger-read-date " ledger-mode" (prompt))
34+ (declare-function ledger-mode " ledger-mode" ())
3435
3536; ; TODO: This file depends on code in ledger-mode.el, which depends on this.
3637
@@ -46,6 +47,15 @@ When nil, `ledger-add-transaction' will not prompt twice."
4647 :package-version '(ledger-mode . " 4.0.1" )
4748 :group 'ledger )
4849
50+ (defcustom ledger-add-transaction-idle-preview t
51+ " When non-nil, a live preview of the to-be-added transaction is shown.
52+ Requires `ledger-add-transaction-prompt-for-text' to be non-nil."
53+ :type '(choice (const :tag " Do not preview" nil )
54+ (const :tag " Preview when idle" t )
55+ (number :tag " Preview with custom delay" ))
56+ :package-version '(ledger-mode . " 4.1" )
57+ :group 'ledger )
58+
4959(defvar-local ledger-xact-highlight-overlay (list ))
5060
5161(defun ledger-highlight-make-overlay ()
@@ -172,12 +182,111 @@ Leave point on the first amount, if any, otherwise the first account."
172182(defvar ledger-add-transaction-last-date nil
173183 " Last date entered using `ledger-read-transaction' ." )
174184
185+ (defvar ledger-xact--preview-buffer-name " *ledger xact preview*" )
186+ (defvar-local ledger-xact--preview-timer nil )
187+ (defvar-local ledger-xact--date nil
188+ " In a minibuffer for the transaction text, the transaction date." )
189+ (defvar-local ledger-xact--ledger-buf nil
190+ " In a minibuffer for the transaction text, the original ledger buffer." )
191+
192+ (defun ledger-xact--create-preview-buffer ()
193+ " Create a buffer to display ledger xact output previews and return it.
194+
195+ If the buffer already exists, return it as-is."
196+ (or (get-buffer ledger-xact--preview-buffer-name)
197+ (with-current-buffer (get-buffer-create ledger-xact--preview-buffer-name)
198+ ; ; Enable `ledger-mode' just for syntax highlighting. Skip all minor
199+ ; ; modes except for `font-lock-mode' .
200+ (delay-mode-hooks (ledger-mode))
201+ (font-lock-mode )
202+ (setq buffer-read-only t )
203+ (set-buffer-modified-p nil )
204+ (current-buffer ))))
205+
206+ (defun ledger-xact--preview (ledger-buf date args )
207+ " Run \" ledger xact\" with DATE and ARGS and display the output.
208+
209+ LEDGER-BUF's contents are passed as input to \" ledger xact\" .
210+
211+ Return the window displaying the output buffer, or nil if it was not
212+ displayed."
213+ (let ((preview-buf (ledger-xact--create-preview-buffer)))
214+ (with-current-buffer preview-buf
215+ (with-silent-modifications
216+ ; ; Don't use `ledger-exec-ledger' because it pops up any error
217+ ; ; output in a separate buffer. For this use case, it is
218+ ; ; preferable to display the error in the preview buffer
219+ ; ; instead.
220+ (with-current-buffer ledger-buf
221+ (apply #'call-process-region
222+ nil nil ledger-binary-path
223+ nil preview-buf t
224+ " xact" date args))
225+ (ledger-post-align-postings (point-min ) (point-max )))
226+ (goto-char (point-min )))
227+ (display-buffer preview-buf
228+ '((display-buffer-reuse-window display-buffer-at-bottom)
229+ (window-height . fit-window-to-buffer)))))
230+
231+ (defun ledger-xact--preview-timer (minibuffer )
232+ " Preview the ledger xact output from MINIBUFFER's current contents."
233+ (setq ledger-xact--preview-timer nil )
234+ ; ; TODO: It would be more correct to use `minibufferp' and pass a non-nil LIVE
235+ ; ; argument, but that feature isn't available until Emacs 28.3.
236+ (when (and (buffer-live-p minibuffer)
237+ (eq minibuffer (window-buffer (active-minibuffer-window ))))
238+ (with-current-buffer minibuffer
239+ (let ((ledger-buf ledger-xact--ledger-buf)
240+ (date ledger-xact--date))
241+ (when-let* ((args (ledger-parse-transaction-text (minibuffer-contents ))))
242+ (while-no-input
243+ (let ((window
244+ (ledger-xact--preview ledger-buf date args)))
245+ (setq-local minibuffer-scroll-window window))))))))
246+
247+ (defun ledger-xact--after-change-function (_beg _end _len )
248+ " Added to `after-change-functions' in transaction-reading minibuffers."
249+ (when-let* ((buf (get-buffer ledger-xact--preview-buffer-name)))
250+ (with-current-buffer buf
251+ (with-silent-modifications
252+ (erase-buffer ))))
253+ (unless ledger-xact--preview-timer
254+ (setq ledger-xact--preview-timer
255+ (run-with-idle-timer
256+ (if (numberp ledger-add-transaction-idle-preview)
257+ ledger-add-transaction-idle-preview
258+ 0.1 )
259+ nil #'ledger-xact--preview-timer (current-buffer )))))
260+
261+ (defun ledger-xact--hide-preview-window ()
262+ " Similar to `minibuffer-restore-windows' , for transaction-reading minibuffers."
263+ ; ; This variable was introduced in Emacs 28.1. The default, matching the
264+ ; ; behavior in previous versions of Emacs, is equivalent to non-nil. We only
265+ ; ; want to delete the window if the default window configuration restore logic
266+ ; ; wouldn't have.
267+ (when (and (boundp 'read-minibuffer-restore-windows )
268+ (not read-minibuffer-restore-windows))
269+ (when-let* ((window (get-buffer-window ledger-xact--preview-buffer-name)))
270+ (delete-window window))))
271+
272+ (defun ledger-read-transaction-text (date )
273+ " Read the text of a transaction with date DATE."
274+ (let ((ledger-buf (current-buffer )))
275+ (minibuffer-with-setup-hook
276+ (lambda ()
277+ (setq ledger-xact--date date
278+ ledger-xact--ledger-buf ledger-buf)
279+ (when ledger-add-transaction-idle-preview
280+ (add-hook 'after-change-functions #'ledger-xact--after-change-function nil t )
281+ (add-hook 'minibuffer-exit-hook #'ledger-xact--hide-preview-window nil t )))
282+ (read-string (concat " xact " date " : " ) nil 'ledger-minibuffer-history ))))
283+
175284(defun ledger-read-transaction ()
176285 " Read the text of a transaction, which is at least the current date."
177286 (let ((date (ledger-read-date " Date: " )))
178287 (concat date " "
179288 (when ledger-add-transaction-prompt-for-text
180- (read-string ( concat " xact " date " : " ) nil 'ledger-minibuffer-history )))))
289+ (ledger- read-transaction-text date)))))
181290
182291(defun ledger-parse-iso-date (date )
183292 " Try to parse DATE using `ledger-iso-date-regexp' and return a time value or nil."
@@ -190,6 +299,9 @@ Leave point on the first amount, if any, otherwise the first account."
190299 " Parse TRANSACTION-TEXT as a date and maybe some arguments.
191300
192301Return (DATE . ARGS), a list of strings."
302+ ; ; TODO: This whole function could just be replaced with
303+ ; ; `split-string-shell-command' when the minimum supported Emacs version is
304+ ; ; Emacs 28.
193305 (with-temp-buffer
194306 (insert transaction-text)
195307 (mapcar #'eval (eshell-parse-arguments (point-min ) (point-max )))))
0 commit comments