branch: elpa/beancount commit ec4b299edf703149fca4b2a9598d0b4f43549f4b Author: Daniele Nicolodi <dani...@grinta.net> Commit: Daniele Nicolodi <dani...@grinta.net>
beancount.el: Bring back outline folding functionality Add integration with outline-minor-mode for outline folding functionality. To enable it outline-minor-mode must be loaded when beancount-mode is enabled. To do so add (add-hook 'beancount-mode-hook 'outline-minor-mode) to your configuration. Some code lifted from outshine.el extends outline-minor-mode to implement cycling outline viibility a la org-mode. --- beancount.el | 148 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/beancount.el b/beancount.el index 47fb8ce9a2..68435173bc 100644 --- a/beancount.el +++ b/beancount.el @@ -32,6 +32,7 @@ (autoload 'ido-completing-read "ido") (require 'subr-x) +(require 'outline) (defgroup beancount () "Editing mode for Beancount files." @@ -215,6 +216,8 @@ to align all amounts." (defconst beancount-metadata-regexp "^\\s-+\\([a-z][A-Za-z0-9]+:\\)\\s-+\\(.+\\)") +(defvar beancount-outline-regexp "\\(;;;+\\|\\*+\\)") + (defun beancount-face-by-state (state) (cond ((string-equal state "*") 'beancount-narrative-cleared) ((string-equal state "!") 'beancount-narrative-pending) @@ -239,12 +242,20 @@ to align all amounts." ;; Accounts not covered by previous rules. (,beancount-account-regexp . 'beancount-account) )) +(defun beancount-tab-dwim (&optional arg) + (interactive "P") + (if (and outline-minor-mode + (or arg (outline-on-heading-p))) + (beancount-outline-cycle arg) + (indent-for-tab-command))) + (defvar beancount-mode-map-prefix [(control c)] "The prefix key used to bind Beancount commands in Emacs") (defvar beancount-mode-map (let ((map (make-sparse-keymap)) (p beancount-mode-map-prefix)) + (define-key map (kbd "TAB") #'beancount-tab-dwim) (define-key map (vconcat p [(\')]) #'beancount-insert-account) (define-key map (vconcat p [(control g)]) #'beancount-transaction-set-flag) (define-key map (vconcat p [(l)]) #'beancount-check) @@ -287,7 +298,9 @@ to align all amounts." (add-hook 'completion-at-point-functions #'beancount-completion-at-point nil t) (setq-local font-lock-defaults '(beancount-font-lock-keywords)) - (setq-local font-lock-syntax-table t)) + (setq-local font-lock-syntax-table t) + + (setq-local outline-regexp beancount-outline-regexp)) (defun beancount-collect-pushed-tags (begin end) "Return list of all pushed (and not popped) tags in the region." @@ -790,6 +803,139 @@ Only useful if you have not installed Beancount properly in your PATH.") (call-process beancount-price-program nil t nil (file-relative-name buffer-file-name))) +;;; Outline minor mode support. + +(defun beancount-outline-cycle (&optional arg) + "Implement visibility cycling a la `org-mode'. + +The behavior of this command is determined by the first matching +condition among the following: + + 1. When point is at the beginning of the buffer, or when called + with a `\\[universal-argument]' universal argument, rotate the entire buffer + through 3 states: + + - OVERVIEW: Show only top-level headlines. + - CONTENTS: Show all headlines of all levels, but no body text. + - SHOW ALL: Show everything. + + 2. When point is at the beginning of a headline, rotate the + subtree starting at this line through 3 different states: + + - FOLDED: Only the main headline is shown. + - CHILDREN: The main headline and its direct children are shown. + From this state, you can move to one of the children + and zoom in further. + + - SUBTREE: Show the entire subtree, including body text." + (interactive "P") + (setq deactivate-mark t) + (cond + ;; Beginning of buffer or called with C-u: Global cycling + ((or (equal arg '(4)) + (and (bobp) + ;; org-mode style behaviour - only cycle if not on a heading + (not (outline-on-heading-p)))) + (beancount-cycle-buffer)) + + ;; At a heading: rotate between three different views + ((save-excursion (beginning-of-line 1) (looking-at outline-regexp)) + (outline-back-to-heading) + (let ((goal-column 0) eoh eol eos) + ;; First, some boundaries + (save-excursion + (save-excursion (beancount-next-line) (setq eol (point))) + (outline-end-of-heading) (setq eoh (point)) + (outline-end-of-subtree) (setq eos (point))) + ;; Find out what to do next and set `this-command' + (cond + ((= eos eoh) + ;; Nothing is hidden behind this heading + (beancount-message "EMPTY ENTRY")) + ((>= eol eos) + ;; Entire subtree is hidden in one line: open it + (outline-show-entry) + (outline-show-children) + (beancount-message "CHILDREN") + (setq + this-command 'beancount-cycle-children)) + ((eq last-command 'beancount-cycle-children) + ;; We just showed the children, now show everything. + (outline-show-subtree) + (beancount-message "SUBTREE")) + (t + ;; Default action: hide the subtree. + (outline-hide-subtree) + (beancount-message "FOLDED"))))))) + +(defvar beancount-current-buffer-visibility-state nil + "Current visibility state of buffer.") +(make-variable-buffer-local 'beancount-current-buffer-visibility-state) + +(defvar beancount-current-buffer-visibility-state) + +(defun beancount-cycle-buffer (&optional arg) + "Rotate the visibility state of the buffer through 3 states: + - OVERVIEW: Show only top-level headlines. + - CONTENTS: Show all headlines of all levels, but no body text. + - SHOW ALL: Show everything. + +With a numeric prefix ARG, show all headlines up to that level." + (interactive "P") + (save-excursion + (cond + ((integerp arg) + (outline-show-all) + (outline-hide-sublevels arg)) + ((eq last-command 'beancount-cycle-overview) + ;; We just created the overview - now do table of contents + ;; This can be slow in very large buffers, so indicate action + ;; Visit all headings and show their offspring + (goto-char (point-max)) + (while (not (bobp)) + (condition-case nil + (progn + (outline-previous-visible-heading 1) + (outline-show-branches)) + (error (goto-char (point-min))))) + (beancount-message "CONTENTS") + (setq this-command 'beancount-cycle-toc + beancount-current-buffer-visibility-state 'contents)) + ((eq last-command 'beancount-cycle-toc) + ;; We just showed the table of contents - now show everything + (outline-show-all) + (beancount-message "SHOW ALL") + (setq this-command 'beancount-cycle-showall + beancount-current-buffer-visibility-state 'all)) + (t + ;; Default action: go to overview + (let ((toplevel + (cond + (current-prefix-arg + (prefix-numeric-value current-prefix-arg)) + ((save-excursion + (beginning-of-line) + (looking-at outline-regexp)) + (max 1 (funcall outline-level))) + (t 1)))) + (outline-hide-sublevels toplevel)) + (beancount-message "OVERVIEW") + (setq this-command 'beancount-cycle-overview + beancount-current-buffer-visibility-state 'overview))))) + +(defun beancount-message (msg) + "Display MSG, but avoid logging it in the *Messages* buffer." + (let ((message-log-max nil)) + (message msg))) + +(defun beancount-next-line () + "Forward line, but mover over invisible line ends. +Essentially a much simplified version of `next-line'." + (interactive) + (beginning-of-line 2) + (while (and (not (eobp)) + (get-char-property (1- (point)) 'invisible)) + (beginning-of-line 2))) (provide 'beancount) ;;; beancount.el ends here