diff --git a/docs/modules.org b/docs/modules.org index 4fffdbaae..f4ed06901 100644 --- a/docs/modules.org +++ b/docs/modules.org @@ -95,6 +95,7 @@ Modules that reconfigure or augment packages or features built into Emacs. Modules that bring support for a language or group of languages to Emacs. + [[file:../modules/lang/agda/README.org][agda]] =+local= - TODO ++ [[file:../modules/lang/beancount/README.org][beancount]] =+lsp= - TODO + [[file:../modules/lang/cc/README.org][cc]] =+lsp= - TODO + [[file:../modules/lang/clojure/README.org][clojure]] =+lsp= - TODO + common-lisp - TODO diff --git a/init.example.el b/init.example.el index 0e3cddb28..fe0b64988 100644 --- a/init.example.el +++ b/init.example.el @@ -111,6 +111,7 @@ :lang ;;agda ; types of types of types of types... + ;;beancount ; the accounting system in Emacs ;;cc ; C/C++/Obj-C madness ;;clojure ; java with a lisp ;;common-lisp ; if you've seen one lisp, you've seen them all @@ -140,7 +141,7 @@ ;;latex ; writing papers in Emacs has never been so fun ;;lean ;;factor - ;;ledger ; an accounting system in Emacs + ;;ledger ; an other accounting system in Emacs ;;lua ; one-based indices? one-based indices markdown ; writing docs for people to ignore ;;nim ; python + lisp at the speed of c diff --git a/modules/lang/beancount/README.org b/modules/lang/beancount/README.org new file mode 100644 index 000000000..e958e5950 --- /dev/null +++ b/modules/lang/beancount/README.org @@ -0,0 +1,53 @@ +#+TITLE: lang/beancount +#+DATE: April 13, 2021 +#+SINCE: v3.0.0 +#+STARTUP: inlineimages nofold + +* Table of Contents :TOC_3:noexport: +- [[#description][Description]] + - [[#maintainers][Maintainers]] + - [[#module-flags][Module Flags]] + - [[#plugins][Plugins]] + - [[#hacks][Hacks]] +- [[#prerequisites][Prerequisites]] +- [[#features][Features]] +- [[#configuration][Configuration]] +- [[#troubleshooting][Troubleshooting]] + +* Description +This module adds support for [[https://beancount.github.io/][Beancount]] to Emacs. Beancount, like ledger, lets +you [[https://plaintextaccounting.org/][manage your money in plain text]]. + ++ Supports [[https://github.com/polarmutex/beancount-language-server][beancount-language-server]] (if module is enabled with the =+lsp= + flag). + +** Maintainers +This module has no dedicated maintainers. + +** Module Flags ++ =+lsp= Enable support for [beancount-language-server]. Requires Doom's =:tools + lsp= module. + +** Plugins ++ [[https://github.com/beancount/beancount-mode][beancount]] + +** Hacks ++ Associates the material =attach_money= icon with *.beancount files in the + =all-the-icons= package. + +* Prerequisites +This module has no hard prerequisites, but assumes you have [[https://github.com/beancount/beancount][beancount]] installed +in order to generate reports with ~bean-report~. + +#+begin_quote +Also: the ~beancount-fava~ command requires [[https://beancount.github.io/fava/][fava]]. +#+end_quote + +* TODO Features +# An in-depth list of features, how to use them, and their dependencies. + +* TODO Configuration +# How to configure this module, including common problems and how to address them. + +* TODO Troubleshooting +# Common issues and their solution, or places to look for help. diff --git a/modules/lang/beancount/autoload.el b/modules/lang/beancount/autoload.el new file mode 100644 index 000000000..84e79716d --- /dev/null +++ b/modules/lang/beancount/autoload.el @@ -0,0 +1,127 @@ +;;; lang/beancount/autoload.el -*- lexical-binding: t; -*- + +;; +;;; Helpers + +;; Lifted from ledger +(defconst +beancount--payee-any-status-regex + "^[0-9]+[-/][-/.=0-9]+\\(\\s-+\\*\\)?\\(\\s-+(.*?)\\)?\\s-+\\(.+?\\)\\s-*\\(;\\|$\\)") + +(defun +beancount--sort-startkey () + "Return the actual date so the sort subroutine doesn't sort on the entire first line." + (buffer-substring-no-properties (point) (+ 10 (point)))) + +(defun +beancount--navigate-next-xact () + "Move point to beginning of next xact." + ;; make sure we actually move to the next xact, even if we are the beginning + ;; of one now. + (if (looking-at +beancount--payee-any-status-regex) + (forward-line)) + (if (re-search-forward +beancount--payee-any-status-regex nil t) + (goto-char (match-beginning 0)) + (goto-char (point-max)))) + +(defun +beancount--navigate-start-xact-or-directive-p () + "Return t if at the beginning of an empty or all-whitespace line." + (not (looking-at "[ \t]\\|\\(^$\\)"))) + +(defun +beancount--navigate-next-xact-or-directive () + "Move to the beginning of the next xact or directive." + (interactive) + (beginning-of-line) + (if (+beancount--navigate-start-xact-or-directive-p) ; if we are the start of an xact, move forward to the next xact + (progn + (forward-line) + (if (not (+beancount--navigate-start-xact-or-directive-p)) ; we have moved forward and are not at another xact, recurse forward + (+beancount--navigate-next-xact-or-directive))) + (while (not (or (eobp) ; we didn't start off at the beginning of an xact + (+beancount--navigate-start-xact-or-directive-p))) + (forward-line)))) + +(defun +beancount--navigate-next-xact () + "Move point to beginning of next xact." + ;; make sure we actually move to the next xact, even if we are the + ;; beginning of one now. + (if (looking-at +beancount--payee-any-status-regex) + (forward-line)) + (if (re-search-forward +beancount--payee-any-status-regex nil t) + (goto-char (match-beginning 0)) + (goto-char (point-max)))) + +(defun +beancount--navigate-beginning-of-xact () + "Move point to the beginning of the current xact." + ;; need to start at the beginning of a line in case we are in the first line of an xact already. + (beginning-of-line) + (let ((sreg (concat "^[=~[:digit:]]"))) + (unless (looking-at sreg) + (re-search-backward sreg nil t) + (beginning-of-line))) + (point)) + +(defun +beancount--navigate-end-of-xact () + "Move point to end of xact." + (+beancount--navigate-next-xact-or-directive) + (re-search-backward ".$") + (end-of-line) + (point)) + + +;; +;;; Commands + +;;;###autoload +(defun +beancount/sort-buffer (&optional reverse) + "Sort all transactions in the buffer. +If REVERSE (the prefix arg) is non-nil, sort them in reverse." + (interactive "P") + (+beancount/sort-region (point-min) (point-max) reverse)) + +;;;###autoload +(defun +beancount/sort-region (beg end &optional reverse) + "Sort the transactions inside BEG and END. +If REVERSE (the prefix arg) is non-nil, sort the transactions in reverst order." + (interactive + (list (region-beginning) + (region-end) + (and current-prefix-arg t))) + (let* ((new-beg beg) + (new-end end) + (bounds (save-excursion + (list (+beancount--navigate-beginning-of-xact) + (+beancount--navigate-end-of-xact)))) + (point-delta (- (point) (car bounds))) + (target-xact (buffer-substring (car bounds) (cadr bounds))) + (inhibit-modification-hooks t)) + (save-excursion + (save-restriction + (goto-char beg) + ;; make sure beg of region is at the beginning of a line + (beginning-of-line) + ;; make sure point is at the beginning of a xact + (unless (looking-at +beancount--payee-any-status-regex) + (+beancount--navigate-next-xact)) + (setq new-beg (point)) + (goto-char end) + (+beancount--navigate-next-xact) + ;; make sure end of region is at the beginning of next record after the + ;; region + (setq new-end (point)) + (narrow-to-region new-beg new-end) + (goto-char new-beg) + (let ((inhibit-field-text-motion t)) + (sort-subr + reverse + '+beancount--navigate-next-xact + '+beancount--navigate-end-of-xact + '+beancount--sort-startkey)))) + (goto-char (point-min)) + (re-search-forward (regexp-quote target-xact)) + (goto-char (+ (match-beginning 0) point-delta)))) + +(defvar compilation-read-command) +;;;###autoload +(defun +beancount/balance () + "Run 'bean-report bal'." + (interactive) + (let (compilation-read-command) + (beancount--run "bean-report" buffer-file-name "bal"))) diff --git a/modules/lang/beancount/config.el b/modules/lang/beancount/config.el new file mode 100644 index 000000000..9706f2198 --- /dev/null +++ b/modules/lang/beancount/config.el @@ -0,0 +1,29 @@ +;;; lang/beancount/config.el -*- lexical-binding: t; -*- + +(use-package! beancount-mode + :mode "\\.beancount\\'" + :init + (add-hook 'beancount-mode-hook #'outline-minor-mode) + + (after! all-the-icons + (add-to-list 'all-the-icons-icon-alist + '("\\.beancount\\'" all-the-icons-material "attach_money" :face all-the-icons-lblue)) + (add-to-list 'all-the-icons-mode-icon-alist + '(beancount-mode all-the-icons-material "attach_money" :face all-the-icons-lblue))) + :config + (when (featurep! +lsp) + (add-hook 'beancount-mode-local-vars-hook #'lsp!)) + + (setq beancount-electric-currency t) + + (map! :map beancount-mode-map + :localleader + "b" #'+beancount/balance + "c" #'beancount-check + "l" #'beancount-linked + "q" #'beancount-query + "x" #'beancount-context + (:prefix ("i" . "insert") + "a" #'beancount-insert-account + "p" #'beancount-insert-prices + "d" #'beancount-insert-date))) diff --git a/modules/lang/beancount/packages.el b/modules/lang/beancount/packages.el new file mode 100644 index 000000000..93a3c2b12 --- /dev/null +++ b/modules/lang/beancount/packages.el @@ -0,0 +1,7 @@ +;; -*- no-byte-compile: t; -*- +;;; lang/beancount/packages.el + +(package! beancount + :recipe (:host github + :repo "beancount/beancount-mode") + :pin "3c04745fa539c25dc007683ad257239067c24cfe")