Compare commits

...

14 commits

14 changed files with 625 additions and 407 deletions

View file

@ -78,22 +78,22 @@
(slot . 6)
(window-width . 80)))
(require 'org-capture)
;; (require 'org-capture)
(defun my/is-org-capture-buffer (buffer &optional _rest)
(with-current-buffer buffer
(and (eq major-mode 'org-mode)
org-capture-mode)))
;; (defun my/is-org-capture-buffer (buffer &optional _rest)
;; (with-current-buffer (get-buffer buffer)
;; (and (eq major-mode 'org-mode)
;; org-capture-mode)))
(add-to-list 'display-buffer-alist
`(my/is-org-capture-buffer
display-buffer-in-side-window
(side . left)
(select . t)
(window-width . 85)
(window-parameters
. ((no-delete-other-windows . t)
(dedicated . t)))))
;; (add-to-list 'display-buffer-alist
;; `(my/is-org-capture-buffer
;; display-buffer-in-side-window
;; (side . left)
;; (select . t)
;; (window-width . 85)
;; (window-parameters
;; . ((no-delete-other-windows . t)
;; (dedicated . t)))))
;; (defun my/is-org-capture-buffer (buffer &optional _rest)
;; (with-current-buffer buffer

View file

@ -61,238 +61,6 @@
("SPC" . 'ace-jump-mode)))
#+END_SRC
* Various tools
** ledger
#+begin_src emacs-lisp
(use-package ledger-mode
:mode "\\.dat\\'"
:config
(setq ledger-narrow-on-reconcile nil)
(setq ledger-reports
`(("account" "%(binary) -f %(ledger-file) reg %(account)")
("credit card" "%(binary) -f %(ledger-file) reg %(account) --aux-date --sort -d")
("bal" "%(binary) -f %(ledger-file) bal")
("reg" "%(binary) -f %(ledger-file) reg")
("equity" "%(binary) -f %(ledger-file) bal ^Exp ^RE ^Rev")
("uncleared" "%(binary) -f %(ledger-file) reg --uncleared --limit=\"payee!='Texas Instruments Income'\"")
("last-superfluous" "%(binary) -f %(ledger-file) bal --limit='account =~ /^Exp:(Food|Luxury|NewTech|People)/ && date >= [this month]'")
("superfluous" "%(binary) -f %(ledger-file) reg --limit='account =~ /^Exp:(Food|Luxury|NewTech|People)/'")
("recurring" "%(binary) -f %(ledger-file) reg --limit='has_tag(\"RECURRING\")' ^Exp")
("expmonth" "%(binary) -f %(ledger-file) -M reg Expenses")
("owedmom" "%(binary) -f %(ledger-file) reg Liabilities")
("progress" "%(binary) -f %(ledger-file) reg Assets Equity Liabilities")
("payee" "%(binary) -f %(ledger-file) reg @%(payee)")
("lia1" "%(binary) -f %(ledger-file) bal ^Lia --cleared")
("lia2" "%(binary) -f %(ledger-file) reg ^Lia --uncleared")
("Ast:AR" "%(binary) -f %(ledger-file) bal ^Ast:AR")
("earned-money" "%(binary) -f %(ledger-file) bal ^Rev:TI ^Exp:Necessary:Tax ^Exp:Necessary:Insurance ^Exp:Necessary:GroupLife")))
(setq dynamic-reports
'(("budgetcal" "%(binary) -f ~/MEGA/org/entries/food.ledger --daily --add-budget reg Expenses")))
(use-package stripes)
(add-hook 'ledger-report-after-report-hook
#'(lambda ()
(stripes-mode 2)))
(require 'parse-time)
(defun ledger-narrow-to-date-range ()
(interactive)
(goto-char (line-beginning-position))
(when (looking-at
(rx (and
(separated-list " - "
(group (= 2 digit)) "-" (group (= 3 alpha))
"-" (= 2 digit)))))
(let ((year (match-string 1))
(month-start (cdr (assoc (downcase (match-string 2)) parse-time-months))))
(setq ledger-report-cmd
(--> ledger-report-cmd
(string-replace " -M" "" it)
(string-replace " -n" "" it)
(string-replace " -A" "" it)
(concat it
" "
(format " -b 20%s-%d"
year
month-start)
(format " -e 20%s-%d" year (1+ month-start)))))
(ledger-report-redo))))
(define-key ledger-report-mode-map (kbd "n")
#'ledger-narrow-to-date-range)
(defun ledger-accounts-expand-includes (orig)
(let (includes)
(save-excursion
(goto-char (point-min))
(while (re-search-forward (rx line-start "include "
(group (+ nonl)))
nil t)
(push (match-string 1) includes)))
(append
(cl-mapcan #'(lambda (file)
(with-current-buffer (find-file-noselect
(expand-file-name file))
(ledger-accounts-in-buffer)))
includes)
(funcall orig))))
(advice-add #'ledger-accounts-in-buffer
:around
#'ledger-accounts-expand-includes)
(defun check-account-in-buffer (account)
(member (list account) (ledger-accounts-in-buffer)))
(advice-add #'ledger-reconcile-check-valid-account
:override
#'check-account-in-buffer)
;; TODO there has to be a better way to do this
(defun save-after-reconcile-toggle (&rest args)
(save-buffer))
;; (advice-add #'ledger-toggle-current
;; :after
;; #'save-after-reconcile-toggle)
(defun ledger-dynamic-report ()
(interactive)
(let* ((ledger-reports dynamic-reports)
(report-name (ledger-report-read-name)))
(ledger-report report-name nil)))
(setq ledger-reconcile-buffer-line-format
"%(date)s %-4(code)s %-30(payee)s %-30(account)s %15(amount)s\n")
(defun ledger-account-check-dont-include-regexp (orig account)
(when (= (aref account 0)
?^)
(setq account
(substring account 1))))
(defun ledger-report-show-monthly-average ()
(interactive)
(let ((average-string "-A -M -n"))
(unless (string-match-p average-string ledger-report-cmd)
(setq ledger-report-cmd
(--> ledger-report-cmd
(replace-regexp-in-string
(rx " -b " (+ (not " "))) "" it)
(replace-regexp-in-string
(rx " -e " (+ (not " "))) "" it)
(concat it " " average-string)))
(ledger-report-redo))))
(setq ledger-amount-regexp
(concat
"\\( \\|\t\\| \t\\)[ \t]*-?"
"\\(?:" "?-" ledger-commodity-regexp " *\\)?"
;; We either match just a number after the commodity with no
;; decimal or thousand separators or a number with thousand
;; separators. If we have a decimal part starting with `,'
;; or `.', because the match is non-greedy, it must leave at
;; least one of those symbols for the following capture
;; group, which then finishes the decimal part.
"\\(-?\\(?:[0-9]+\\|[0-9,.]+?\\)\\)"
"\\([,.][0-9)]+\\)?"
"\\(?: *" ledger-commodity-regexp "\\)?"
"\\([ \t]*[@={]@?[^\n;]+?\\)?"
"\\([ \t]+;.+?\\|[ \t]*\\)?$"))
(define-key ledger-report-mode-map (kbd "M") #'ledger-report-show-monthly-average)
(defun my/ledger-complete-xact--remove-stars ()
(interactive)
(let* ((date-regexp (rx (and line-start (= 4 digit) "/" (= 2 digit) "/" (= 2 digit))))
(start (save-excursion
(re-search-backward date-regexp)
(point)))
(end (save-excursion
(or (re-search-forward date-regexp nil t)
(end-of-buffer))
(beginning-of-line)
(point))))
(save-window-excursion
(save-restriction
(narrow-to-region start end)
(beginning-of-buffer)
(save-excursion
(replace-regexp (rx " "
(or "*" "!")
" "
(group (+ (not (any " " "\n")))))
" \\1 "))
(save-excursion
(replace-regexp (rx (and " " (+ " ")
";; [" (+ (any digit "-" "=" "/")) "]"
line-end))
""))
(save-excursion
(replace-regexp (rx line-start (group (+ (any "/" digit)) " ")
" ")
"\\1"))))))
(advice-add #'ledger-fully-complete-xact
:after
#'my/ledger-complete-xact--remove-stars)
(defun my/ledger-clean-commodity ()
(save-excursion
(beginning-of-buffer)
(replace-regexp (rx " -$") " $-")))
(advice-add #'ledger-mode-clean-buffer
:after
#'my/ledger-clean-commodity)
(defun my/ledger-convert-alias (account)
(save-excursion
(goto-char (point-min))
(let ((regexp
(rx line-start
"alias " (literal account) "="
(group (+ (or alphanumeric ":" "_")))
(* space)
line-end)))
(or (and (re-search-forward regexp nil t)
(aprog1 (match-string 1)
(set-text-properties 0 (length it) nil it)))
account))))
(advice-add #'ledger-read-account-with-prompt
:filter-return
#'my/ledger-convert-alias)
(defun my/ledger-field (orig context field)
(let ((res (funcall orig context field)))
(if (or (not (eq field 'account))
(null res)
(not (string-match (rx (group (separated-list ":" (separated-list " " (+ alphanumeric)))) " ") res)) )
res
(match-string 1 res))))
;; (advice-add #'ledger-context-field-value
;; :around
;; #'my/ledger-field)
(defun my/ledger-reconcile-switch-to-master (&rest args)
(interactive)
(switch-to-buffer (find-file-noselect ledger-master-file)))
;; (advice-add #'ledger-reconcile
;; :before
;; #'my/ledger-reconcile-switch-to-master)
)
#+end_src
** Credit Card Statement Macro
#+begin_src emacs-lisp
(fset 'credit_card_statement
[?\M-x ?o ?r ?g ?- ?m ?o ?d ?e return ?\M-x ?q backspace ?r ?e ?p ?l ?a ?c ?e ?- ?r ?e ?g ?e ?x ?p return ?^ ?\C-q tab return ? ? ? ? return ?\M-< ?\C- ?\C-f ?\C-f ?\C-f ?\C-f ?\C-c ?m ?a ?\C-w ?- ? ?\[ ? ?\] ? ?\C-e ?\C-k ?\C-c ?m ? ?\C-q tab ?\C-q tab ?\C-e ?\C-j ?y ?\C-a ?_ ?_ ?_ ?_ backspace backspace backspace backspace ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?\C-p ?\C-p ?\C-k ?\C-c ?m ? ?\C-q tab ?\C-q tab ?\C-d ?\C-d return ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n])
#+end_src
** debbugs
#+begin_src emacs-lisp
(use-package debbugs)

View file

@ -1036,8 +1036,3 @@
browse-url-browser-function 'browse-url-generic
search-web-default-browser 'browse-url-generic))
#+end_src
* Terminal compatibility
#+begin_src emacs-lisp
(when (null window-system)
(require 'term-compat))
#+end_src

View file

@ -627,131 +627,6 @@
:override
#'my/org-wiki-insert-link)
#+end_src
* org-roam
#+begin_src emacs-lisp
(require 'org-ql)
;; (defvar bootstrap-version)
;; (let ((bootstrap-file
;; (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
;; (bootstrap-version 5))
;; (unless (file-exists-p bootstrap-file)
;; (with-current-buffer
;; (url-retrieve-synchronously
;; "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
;; 'silent 'inhibit-cookies)
;; (goto-char (point-max))
;; (eval-print-last-sexp)))
;; (load bootstrap-file nil 'nomessage))
;; (setq straight-vc-git-default-protocol 'ssh)
(define-prefix-command '*org-roam-map*)
(define-key pestctrl-minor-mode-map
(kbd "C-c n")
'*org-roam-map*)
(use-package org-roam
:after org
:custom
(org-roam-directory (my/org-file "org-roam"))
(org-roam-use-completion-everywhere t)
:bind (:map *org-roam-map*
("h" . org-roam-buffer-toggle)
("f" . my/org-roam-find-file)
("F" . my/org-roam-find-daily)
("p" . my/org-roam-find-project)
("T" . org-roam-dailies-goto-today)
("t" . org-roam-dailies-capture-today)
("i" . org-roam-node-insert)
("w" . org-roam-refile)
("j" . my/org-roam-logger-capture-current)
("c" . org-roam-capture)
:map org-mode-map
("C-M-i" . completion-at-point))
:init
(setq org-roam-v2-ack t)
:config
(org-roam-setup)
(setq org-roam-dailies-directory "daily/")
(setq org-roam-dailies-capture-templates
'(("d" "Journal" entry "* %<%H:%M> %?"
:unnarrowed t
:target (file+head+olp "%<%Y-%m-%d>.org"
"#+title: %<%Y-%m-%d>\n#+filetags: %<:%Y:%B:dailies>\n"
("Journal")))
;; ("m" "Most Important Thing" entry "* TODO %? :mit:"
;; :target (file+head+olp "%<%Y-%m-%d>.org"
;; "#+title: %<%Y-%m-%d>\n#+filetags: %<:%Y:%B:>\n"
;; ("Most Important Thing(s)")))
))
(require 'my-org-roam-logger)
(setq org-roam-capture-templates
'(("d" "default" plain "%?" :target
(file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
:unnarrowed t)
("j" "Journal" entry "* %<%H:%M> %?" :target
(file+datetree "%<%Y%m%d%H%M%S>-${slug}.org" 'day)
:unnarrowed t)
("t" "tech tips" plain "%?" :target
(file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: techtips\n")
:unnarrowed t)))
(require 'org-roam-util)
(defun my/org-roam-find-file ()
(interactive)
;; Select a project file to open, creating it if necessary
(org-roam-node-find
nil nil
(lambda (node)
(let ((tags (org-roam-node-tags node)))
(not (member "project" tags))))))
(defun my/org-roam-find-project ()
(interactive)
;; Select a project file to open, creating it if necessary
(org-roam-node-find
nil nil
(lambda (node)
(let ((tags (org-roam-node-tags node)))
(and (eq (org-roam-node-level node) 0)
(member "project" tags)
(not (member "done" tags)))))
nil
:templates
'(("p" "project" plain ""
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}: %^{Description}\n#+category: ${title}\n#+filetags: project")
:unnarrowed t))))
(use-package consult-org-roam
:demand t
:commands (my/org-roam-find-daily)
:config
(require 'org-roam-util)
(defun consult-org-roam-file-find (arg)
"Find org-roam node with preview, if ARG open in other window."
(interactive "P")
(cl-letf (((symbol-function 'org-roam-node-read)
(symbol-function 'consult-org-roam-node-read)))
(let ((other-window (if arg t nil)))
(org-roam-node-find other-window nil #'consult-org-roam--node-file-p))))
(defun my/org-roam-find-daily ()
(interactive)
(cl-letf (((symbol-function 'org-roam-node-read)
(symbol-function 'consult-org-roam-node-read)))
(org-roam-node-find nil nil
(my/org-roam-filter-by-tag "dailies")
(lambda (x y)
(string-lessp (org-roam-node-file (cdr y))
(org-roam-node-file (cdr x)))))))))
;; (require 'org-roam-protocol)
#+end_src
* Variable pitch org-mode
#+begin_src emacs-lisp
;; (mapcar

View file

@ -56,7 +56,8 @@
(require 'quelpa)
(require 'quelpa-use-package)
(require 'term-compat)
(when (null window-system)
(require 'term-compat))
(when (native-comp-available-p)
(require 'comp)

284
lisp/my-ledger.el Normal file
View file

@ -0,0 +1,284 @@
;;; my-ledger.el --- -*- lexical-binding: t -*-
;; Copyright (C) 2025 Benson Chu
;; Author: Benson Chu <bensonchu457@gmail.com>
;; Created: [2025-08-10 13:52]
;; This file is not part of GNU Emacs
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(use-package ledger-mode
:mode "\\.dat\\'"
:config
(setq ledger-narrow-on-reconcile nil)
(setq ledger-reports
`(("account" "%(binary) -f %(ledger-file) reg %(account)")
("credit card" "%(binary) -f %(ledger-file) reg %(account) --aux-date --sort -d")
("bal" "%(binary) -f %(ledger-file) bal")
("reg" "%(binary) -f %(ledger-file) reg")
("equity" "%(binary) -f %(ledger-file) bal ^Exp ^RE ^Rev")
("uncleared" "%(binary) -f %(ledger-file) reg --uncleared --limit=\"payee!='Texas Instruments Income'\"")
("last-superfluous" "%(binary) -f %(ledger-file) bal --limit='account =~ /^Exp:(Food|Luxury|NewTech|People)/ && date >= [this month]'")
("superfluous" "%(binary) -f %(ledger-file) reg --limit='account =~ /^Exp:(Food|Luxury|NewTech|People)/'")
("recurring" "%(binary) -f %(ledger-file) reg --limit='has_tag(\"RECURRING\")' ^Exp")
("expmonth" "%(binary) -f %(ledger-file) -M reg Expenses")
("owedmom" "%(binary) -f %(ledger-file) reg Liabilities")
("progress" "%(binary) -f %(ledger-file) reg Assets Equity Liabilities")
("payee" "%(binary) -f %(ledger-file) reg @%(payee)")
("lia1" "%(binary) -f %(ledger-file) bal ^Lia --cleared")
("lia2" "%(binary) -f %(ledger-file) reg ^Lia --uncleared")
("Ast:AR" "%(binary) -f %(ledger-file) bal ^Ast:AR")
("earned-money" "%(binary) -f %(ledger-file) bal ^Rev:TI ^Exp:Necessary:Tax ^Exp:Necessary:Insurance ^Exp:Necessary:GroupLife")))
(setq dynamic-reports
'(("budgetcal" "%(binary) -f ~/MEGA/org/entries/food.ledger --daily --add-budget reg Expenses")))
(use-package stripes)
(add-hook 'ledger-report-after-report-hook
#'(lambda ()
(stripes-mode 2)))
(require 'parse-time)
(defun ledger-narrow-to-date-range ()
(interactive)
(goto-char (line-beginning-position))
(when (looking-at
(rx (and
(separated-list " - "
(group (= 2 digit)) "-" (group (= 3 alpha))
"-" (= 2 digit)))))
(let ((year (match-string 1))
(month-start (cdr (assoc (downcase (match-string 2)) parse-time-months))))
(setq ledger-report-cmd
(--> ledger-report-cmd
(string-replace " -M" "" it)
(string-replace " -n" "" it)
(string-replace " -A" "" it)
(concat it
" "
(format " -b 20%s-%d"
year
month-start)
(format " -e 20%s-%d" year (1+ month-start)))))
(ledger-report-redo))))
(define-key ledger-report-mode-map (kbd "n")
#'ledger-narrow-to-date-range)
(defun ledger-accounts-expand-includes (orig)
(let (includes)
(save-excursion
(goto-char (point-min))
(while (re-search-forward (rx line-start "include "
(group (+ nonl)))
nil t)
(push (match-string 1) includes)))
(append
(cl-mapcan #'(lambda (file)
(with-current-buffer (find-file-noselect
(expand-file-name file))
(ledger-accounts-in-buffer)))
includes)
(funcall orig))))
(advice-add #'ledger-accounts-in-buffer
:around
#'ledger-accounts-expand-includes)
(defun check-account-in-buffer (account)
(member (list account) (ledger-accounts-in-buffer)))
(advice-add #'ledger-reconcile-check-valid-account
:override
#'check-account-in-buffer)
;; TODO there has to be a better way to do this
(defun save-after-reconcile-toggle (&rest args)
(save-buffer))
;; (advice-add #'ledger-toggle-current
;; :after
;; #'save-after-reconcile-toggle)
(defun ledger-dynamic-report ()
(interactive)
(let* ((ledger-reports dynamic-reports)
(report-name (ledger-report-read-name)))
(ledger-report report-name nil)))
(setq ledger-reconcile-buffer-line-format
"%(date)s %-4(code)s %-30(payee)s %-30(account)s %15(amount)s\n")
(defun ledger-account-check-dont-include-regexp (orig account)
(when (= (aref account 0)
?^)
(setq account
(substring account 1))))
(defun ledger-report-show-monthly-average ()
(interactive)
(let ((average-string "-A -M -n"))
(unless (string-match-p average-string ledger-report-cmd)
(setq ledger-report-cmd
(--> ledger-report-cmd
(replace-regexp-in-string
(rx " -b " (+ (not " "))) "" it)
(replace-regexp-in-string
(rx " -e " (+ (not " "))) "" it)
(concat it " " average-string)))
(ledger-report-redo))))
(setq ledger-amount-regexp
(concat
"\\( \\|\t\\| \t\\)[ \t]*-?"
"\\(?:" "?-" ledger-commodity-regexp " *\\)?"
;; We either match just a number after the commodity with no
;; decimal or thousand separators or a number with thousand
;; separators. If we have a decimal part starting with `,'
;; or `.', because the match is non-greedy, it must leave at
;; least one of those symbols for the following capture
;; group, which then finishes the decimal part.
"\\(-?\\(?:[0-9]+\\|[0-9,.]+?\\)\\)"
"\\([,.][0-9)]+\\)?"
"\\(?: *" ledger-commodity-regexp "\\)?"
"\\([ \t]*[@={]@?[^\n;]+?\\)?"
"\\([ \t]+;.+?\\|[ \t]*\\)?$"))
(define-key ledger-report-mode-map (kbd "M") #'ledger-report-show-monthly-average)
(defun my/ledger-complete-xact--remove-stars ()
(interactive)
(let* ((date-regexp (rx (and line-start (= 4 digit) "/" (= 2 digit) "/" (= 2 digit))))
(start (save-excursion
(re-search-backward date-regexp)
(point)))
(end (save-excursion
(or (re-search-forward date-regexp nil t)
(end-of-buffer))
(beginning-of-line)
(point))))
(save-window-excursion
(save-restriction
(narrow-to-region start end)
(beginning-of-buffer)
(save-excursion
(replace-regexp (rx " "
(or "*" "!")
" "
(group (+ (not (any " " "\n")))))
" \\1 "))
(save-excursion
(replace-regexp (rx (and " " (+ " ")
";; [" (+ (any digit "-" "=" "/")) "]"
line-end))
""))
(save-excursion
(replace-regexp (rx line-start (group (+ (any "/" digit)) " ")
" ")
"\\1"))))))
(advice-add #'ledger-fully-complete-xact
:after
#'my/ledger-complete-xact--remove-stars)
(defun my/ledger-clean-commodity ()
(save-excursion
(beginning-of-buffer)
(replace-regexp (rx " -$") " $-")))
(advice-add #'ledger-mode-clean-buffer
:after
#'my/ledger-clean-commodity)
(defun my/ledger-convert-alias (account)
(save-excursion
(goto-char (point-min))
(let ((regexp
(rx line-start
"alias " (literal account) "="
(group (+ (or alphanumeric ":" "_")))
(* space)
line-end)))
(or (and (re-search-forward regexp nil t)
(aprog1 (match-string 1)
(set-text-properties 0 (length it) nil it)))
account))))
(advice-add #'ledger-read-account-with-prompt
:filter-return
#'my/ledger-convert-alias)
(defun my/ledger-field (orig context field)
(let ((res (funcall orig context field)))
(if (or (not (eq field 'account))
(null res)
(not (string-match (rx (group (separated-list ":" (separated-list " " (+ alphanumeric)))) " ") res)) )
res
(match-string 1 res))))
;; (advice-add #'ledger-context-field-value
;; :around
;; #'my/ledger-field)
(defun my/ledger-reconcile-switch-to-master (&rest args)
(interactive)
(switch-to-buffer (find-file-noselect ledger-master-file)))
;; (advice-add #'ledger-reconcile
;; :before
;; #'my/ledger-reconcile-switch-to-master)
(defface ledger-starting-monthly-face
`((t ,(list
:background "gray25"
:extend t
:inherit font-lock-comment-face
:box `(:line-width 1 :color "gray30" :style ,(if (>= emacs-major-version 30) 'released-button 'raised)))))
nil)
(defun ledger-apply-month-separator ()
(interactive)
(remove-overlays nil nil 'face 'ledger-starting-monthly-face)
(save-excursion
(beginning-of-buffer)
(while (not (eobp))
(when (looking-at-p (rx line-start
(separated " - "
(separated "-" (= 2 digit) (= 3 alpha) (= 2 digit))
(separated "-" (= 2 digit) (= 3 alpha) (= 2 digit)))
(+ nonl)))
(let ((ol (make-overlay (point) (line-end-position))))
(overlay-put ol 'face 'ledger-starting-monthly-face)
(overlay-put ol 'priority 5)))
(next-line))))
;; ;; Need some other way to do this
;; (add-hook 'ledger-report-mode-hook
;; 'ledger-apply-month-separator)
)
(fset 'credit_card_statement
[?\M-x ?o ?r ?g ?- ?m ?o ?d ?e return ?\M-x ?q backspace ?r ?e ?p ?l ?a ?c ?e ?- ?r ?e ?g ?e ?x ?p return ?^ ?\C-q tab return ? ? ? ? return ?\M-< ?\C- ?\C-f ?\C-f ?\C-f ?\C-f ?\C-c ?m ?a ?\C-w ?- ? ?\[ ? ?\] ? ?\C-e ?\C-k ?\C-c ?m ? ?\C-q tab ?\C-q tab ?\C-e ?\C-j ?y ?\C-a ?_ ?_ ?_ ?_ backspace backspace backspace backspace ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?= ?\C-p ?\C-p ?\C-k ?\C-c ?m ? ?\C-q tab ?\C-q tab ?\C-d ?\C-d return ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n ?\C-n])
(provide 'my-ledger)
;;; my-ledger.el ends here

View file

@ -26,5 +26,10 @@
(rx-define separated-list (sep &rest match) (seq match (* sep match)))
(rx-define separated (sep &rest matches)
(eval `(seq ,@(cdr (mapcon (lambda (x)
(list sep (car x)))
'(matches))))))
(provide 'my-rx-forms)
;;; my-rx-forms.el ends here

43
lisp/nix-only.el Normal file
View file

@ -0,0 +1,43 @@
;;; nix-only.el --- -*- lexical-binding: t -*-
;; Copyright (C) 2025 Benson Chu
;; Author: Benson Chu <bensonchu457@gmail.com>
;; Created: [2025-08-10 14:08]
;; This file is not part of GNU Emacs
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'anaphora)
;; TODO: Need to know if home-manager config is deployed or not
(defun nix-present-p ()
(executable-find "nix"))
(when (nix-present-p)
(aand (executable-find "ledger")
(--> (file-truename it)
(file-name-parent-directory it)
(directory-file-name it)
(file-name-parent-directory it)
(directory-file-name it)
(expand-file-name "share/info" it)
(add-to-list 'Info-directory-list it))))
(provide 'nix-only)
;;; nix-only.el ends here

View file

@ -27,36 +27,45 @@
(defun my/side-window-p (window)
(window-parameter window 'window-side))
(defun my/org-capture-shouldnt-mess-windows (fun &rest args)
(let ((buffer
(save-window-excursion
(set-window-parameter (selected-window) 'window-side nil)
(let ((window--sides-inhibit-check t))
(apply fun args)
(current-buffer)))))
(pop-to-buffer buffer)))
(defun my/org-capture-display-use-side-window (buffer alist)
;; I think the alist argument is never used
(pop-to-buffer
buffer
(advice-add #'org-capture
'(display-buffer-in-side-window
(side . left)
(window-width . 85)
(window-parameters
. ((no-delete-other-windows . t)
;;(dedicated . t)
)))))
(advice-add #'org-display-buffer-split
:override
#'my/org-capture-display-use-side-window)
(defun my/window-dedicated-p (orig &optional window)
;; Excuse me? Side windows are not dedicated.
(or (and (not org-capture-mode)
(funcall orig window))
(eq (funcall orig window)
t)))
(advice-add #'window-dedicated-p
:around
#'my/org-capture-shouldnt-mess-windows)
#'my/window-dedicated-p)
(defun my/org-capture-finalize-shouldnt-mess-windows (&rest args)
(save-window-excursion
(let ((buffer (current-buffer)))
;; This basically means that we don't change window configurations when
;; there are previous buffers to be seen. IDK if this will have unintended
;; consequences, because...
(if (zerop (length (window-prev-buffers)))
(delete-window)
(previous-buffer))
;; current-window-configuration will encode a buffer that's about to be
;; deleted. I tested it, and it does what I want, so maybe there's no
;; problem?
(with-current-buffer buffer
(org-capture-put :return-to-wconf (current-window-configuration))))))
(defun my/org-capture-finalize-shouldnt-mess-windows (orig &rest args)
(cl-letf* ((orig-set-window-configuration (symbol-function 'set-window-configuration))
((symbol-function 'set-window-configuration)
#'(lambda (&rest args)
(unless (equal (car args) (org-capture-get :return-to-wconf 'local))
(apply orig-set-window-configuration args)))))
(apply orig args)))
(advice-add #'org-capture-finalize
:before
:around
#'my/org-capture-finalize-shouldnt-mess-windows)
(defun my/org-todo-side-window-hack (fun &rest args)

View file

@ -0,0 +1,49 @@
;;; my-org-edit-special.el --- -*- lexical-binding: t -*-
;; Copyright (C) 2025 Benson Chu
;; Author: Benson Chu <bensonchu457@gmail.com>
;; Created: [2025-08-10 13:28]
;; This file is not part of GNU Emacs
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(defun my/org-edit-src-exit ()
(interactive)
(save-window-excursion
(call-interactively #'org-edit-src-exit)))
(defvar-keymap org-capture-self-chat-keymap
:parent org-src-mode-map
"C-c C-c" #'my/org-edit-src-exit
"C-c C-'" #'my/org-edit-src-exit
"C-c C-k" #'(lambda ()
(interactive)
(call-interactively #'org-edit-src-abort)
(call-interactively #'org-cut-special)
(call-interactively #'delete-window)))
(defun my/org-edit-special ()
(interactive)
(call-interactively #'org-edit-special)
(setq-local minor-mode-overriding-map-alist
`((org-src-mode . ,org-capture-self-chat-keymap))))
(provide 'my-org-edit-special)
;;; my-org-edit-special.el ends here

View file

@ -129,11 +129,6 @@
(setq org-html-table-default-attributes
'(:border "2" :cellspacing "0" :cellpadding "6")))
(when (eq window-system 'x)
(define-key org-mode-map
(kbd "C-S-b")
#'(lambda () (interactive) (org-back-to-heading))))
(define-key org-mode-map (kbd "M-g o") (lambda () (interactive) (org-back-to-heading)))
(define-key org-mode-map (kbd "C-M-a") (lambda () (interactive) (org-back-to-heading)))
@ -258,5 +253,8 @@
(my/org-agenda-save)
(close-tab-switch))
(require 'my-org-edit-special)
(define-key org-mode-map (kbd "C-c \"") #'my/org-edit-special)
(provide 'my-org-misc)
;;; my-org-misc.el ends here

View file

@ -0,0 +1,177 @@
;;; my-org-roam.el --- -*- lexical-binding: t -*-
;; Copyright (C) 2025 Benson Chu
;; Author: Benson Chu <bensonchu457@gmail.com>
;; Created: [2025-08-10 11:58]
;; This file is not part of GNU Emacs
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;; Code:
(require 'org-ql)
;; (defvar bootstrap-version)
;; (let ((bootstrap-file
;; (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
;; (bootstrap-version 5))
;; (unless (file-exists-p bootstrap-file)
;; (with-current-buffer
;; (url-retrieve-synchronously
;; "https://raw.githubusercontent.com/raxod502/straight.el/develop/install.el"
;; 'silent 'inhibit-cookies)
;; (goto-char (point-max))
;; (eval-print-last-sexp)))
;; (load bootstrap-file nil 'nomessage))
;; (setq straight-vc-git-default-protocol 'ssh)
(define-prefix-command '*org-roam-map*)
(define-key pestctrl-minor-mode-map
(kbd "C-c n")
'*org-roam-map*)
(use-package org-roam
:after org
:custom
(org-roam-directory (my/org-file "org-roam"))
(org-roam-use-completion-everywhere t)
:bind (:map *org-roam-map*
("h" . org-roam-buffer-toggle)
("f" . my/org-roam-find-file)
("F" . my/org-roam-find-daily)
("p" . my/org-roam-find-project)
("T" . org-roam-dailies-goto-today)
("t" . org-roam-dailies-capture-today)
("i" . org-roam-node-insert)
("w" . org-roam-refile)
("j" . my/org-roam-logger-capture-current)
("c" . org-roam-capture)
:map org-mode-map
("C-M-i" . completion-at-point))
:init
(setq org-roam-v2-ack t)
:config
(org-roam-setup)
(setq org-roam-dailies-directory "daily/")
(require 'my-org-edit-special)
(defun self-chat-capture-template-display-src ()
(with-current-buffer (org-capture-get :buffer)
(goto-char (org-capture-get :insertion-point))
(let ((src-buffer (save-window-excursion
(my/org-edit-special)
(current-buffer)
)))
(org-display-buffer-split src-buffer nil))))
(setq org-roam-dailies-capture-templates
'(("j" "Journal" entry "* %<%H:%M> %?"
:unnarrowed t
:target (file+head+olp "%<%Y-%m-%d>.org"
"#+title: %<%Y-%m-%d>\n#+filetags: :dailies:%<%Y:%B:>\n"
("Journal")))
("J" "Journal with source" entry "* %<%H:%M> %?\n:PROPERTIES:\n:LOCATION: %a\n:END:"
:unnarrowed t
:target
(file+head+olp "%<%Y-%m-%d>.org"
"#+title: %<%Y-%m-%d>\n#+filetags: :dailies:%<%Y:%B:>\n"
("Journal")))
("c" "self-chat" entry "* %<%H:%M> \n#+begin_src self-chat\n%?\n#+end_src"
:unnarrowed t
:target
(file+head+olp "%<%Y-%m-%d>.org"
"#+title: %<%Y-%m-%d>\n#+filetags: :dailies:%<%Y:%B:>\n"
("Journal"))
:after-finalize self-chat-capture-template-display-src
:immediate-finish)
;; ("m" "Most Important Thing" entry "* TODO %? :mit:"
;; :target (file+head+olp "%<%Y-%m-%d>.org"
;; "#+title: %<%Y-%m-%d>\n#+filetags: %<:%Y:%B:>\n"
;; ("Most Important Thing(s)")))
))
(require 'my-org-roam-logger)
(setq org-roam-capture-templates
'(("d" "default" plain "%?" :target
(file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n")
:unnarrowed t)
("j" "Journal" entry "* %<%H:%M> %?" :target
(file+datetree "%<%Y%m%d%H%M%S>-${slug}.org" 'day)
:unnarrowed t)
("t" "tech tips" plain "%?" :target
(file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n#+filetags: techtips\n")
:unnarrowed t)))
(require 'org-roam-util)
(defun my/org-roam-find-file ()
(interactive)
;; Select a project file to open, creating it if necessary
(org-roam-node-find
nil nil
(lambda (node)
(let ((tags (org-roam-node-tags node)))
(not (member "project" tags))))))
(defun my/org-roam-find-project ()
(interactive)
;; Select a project file to open, creating it if necessary
(org-roam-node-find
nil nil
(lambda (node)
(let ((tags (org-roam-node-tags node)))
(and (eq (org-roam-node-level node) 0)
(member "project" tags)
(not (member "done" tags)))))
nil
:templates
'(("p" "project" plain ""
:if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}: %^{Description}\n#+category: ${title}\n#+filetags: project")
:unnarrowed t))))
(use-package consult-org-roam
:demand t
:commands (my/org-roam-find-daily)
:config
(require 'org-roam-util)
(defun consult-org-roam-file-find (arg)
"Find org-roam node with preview, if ARG open in other window."
(interactive "P")
(cl-letf (((symbol-function 'org-roam-node-read)
(symbol-function 'consult-org-roam-node-read)))
(let ((other-window (if arg t nil)))
(org-roam-node-find other-window nil #'consult-org-roam--node-file-p))))
(defun my/org-roam-find-daily ()
(interactive)
(cl-letf (((symbol-function 'org-roam-node-read)
(symbol-function 'consult-org-roam-node-read)))
(org-roam-node-find nil nil
(my/org-roam-filter-by-tag "dailies")
(lambda (x y)
(string-lessp (org-roam-node-file (cdr y))
(org-roam-node-file (cdr x)))))))))
;; (require 'org-roam-protocol)
(provide 'my-org-roam)
;;; my-org-roam.el ends here

View file

@ -45,5 +45,7 @@
(require 'org-table-convert)
(require 'my-org-roam)
(provide 'my-org)
;;; my-org.el ends here

View file

@ -15,6 +15,16 @@
(setq font-lock-defaults '(self-chat-highlights))
(olivetti-mode 1))
(defun self-chat-buffer ()
(interactive)
(--> (let ((org-time-was-given t))
(org-read-date t nil ""))
(format "*self-chat-%s*"
it)
(get-buffer-create it)
(switch-to-buffer it))
(self-chat-mode))
(modify-syntax-entry ?\" " " self-chat-mode-syntax-table)
(define-key self-chat-mode-map (kbd "RET") #'self-chat-insert-next)
@ -137,6 +147,8 @@ shuffling is done in place."
(defface ,face-sym '((t (:foreground ,color))) ,(concat name
"'s face for self-chat-mode"))
(defvar ,face-sym ',face-sym)
(add-to-list 'self-chat-highlights
'(,(rx symbol-start (literal name) symbol-end) 0 ,face-sym prepend))
(add-to-list 'self-chat-highlights
'(,(format "^> %s:.*$" name) . ,face-sym)))
(cl-incf num))))