summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-08-14 19:24:49 -0500
committerCraig Jennings <c@cjennings.net>2025-08-14 19:24:49 -0500
commit9278ddd4ea1a8b1a4c1edaa8894516e3f48d245b (patch)
tree1105519cd55a4ebbb1e91609e6aae7cc3929ddaf /modules
parenta878e5ae99f750ecbbb723f98ef91d3404189a32 (diff)
downloaddotemacs-9278ddd4ea1a8b1a4c1edaa8894516e3f48d245b.tar.gz
dotemacs-9278ddd4ea1a8b1a4c1edaa8894516e3f48d245b.zip
refactor(system-utils): major refactoring / adding tests
Theme: Modularize system-utilities into separate modules. Clean up any typos, buts, and unused variables. Add some initial ERT tests for new modules created. Changes: - Extract file handling into its own module (file-config) - Extract keyboard macro management into its own module (keyboard-macros) - Extract buffer burying (instead of killing) into its own module (undead-buffers) - Extract all date/time config into its own module (chrono-tools) - Moved keybinding discovery functionality and help into keybindings module - Combine flyspell and abbrev (spell-check and autocorrect) to flyspell-and-abbrev.el - Rename epa-config.el to auth-config.el for auth-source and epa settings. - Refactor `cj/kill-other-window` for more accurate buffer handling. - Include "*ert*" in the default bury (don't kill) list as killing it kills test runs. - Bind C-c M-m to inhibit-mouse-mode - Remove the unused ledger-file variable in user-constants.el. - Removed obsolete C-x x m, C-x x r, and C-x x d key mappings. - C-; b r to call cj/rename-buffer-and-file instead of typo’d function - Other purely cosmetic comment changes to system-utils.el ERT tests: - Rename ERT test definitions to include module scopes (file-config, keyboard-macros) - Add an ERT test for the timer bell's existence. - Add ERT tests to cover `cj/kill-buffer-or-bury-alive`, prefix-arg behavior, window-killing commands, and bulk operations. - Add test `authinfo-file` exists Missing authinfo triggers a debug message - Add test that `gpg2` executable is on the user’s PATH - Remove outdated authinfo test. - Add “Run these tests” note where missing.
Diffstat (limited to 'modules')
-rw-r--r--modules/auth-config.el57
-rw-r--r--modules/chrono-tools.el62
-rw-r--r--modules/custom-functions.el7
-rw-r--r--modules/epa-config.el31
-rw-r--r--modules/file-config.el161
-rw-r--r--modules/flyspell-and-abbrev.el (renamed from modules/flyspell-config.el)41
-rw-r--r--modules/keybindings.el15
-rw-r--r--modules/keyboard-macros.el111
-rw-r--r--modules/system-defaults.el7
-rw-r--r--modules/system-utils.el382
-rw-r--r--modules/undead-buffers.el167
-rw-r--r--modules/user-constants.el12
12 files changed, 656 insertions, 397 deletions
diff --git a/modules/auth-config.el b/modules/auth-config.el
new file mode 100644
index 00000000..43849f20
--- /dev/null
+++ b/modules/auth-config.el
@@ -0,0 +1,57 @@
+;; auth-config.el --- Configuration for Authentication Utilities -*- lexical-binding: t; -*-
+;; author Craig Jennings <c@cjennings.net>
+
+;;; Commentary:
+;;
+;; Configuration for Emacs authentication and GPG integration:
+
+;; • auth-source
+;; – Forces use of your default authinfo file
+;; – Disable external GPG agent in favor of Emacs’s own prompt
+;; – Enable auth-source debug messages
+
+;; • Easy PG Assistant (epa)
+;; – Force using the ‘gpg2’ executable for encryption/decryption operations
+
+;;; Code:
+
+(require 'user-constants) ;; defines authinfo-file location
+
+;; -------------------------------- Auth Sources -------------------------------
+;; auth sources settings
+
+(use-package auth-source
+ :ensure nil ;; built in
+ :demand t ;; load this package immediately
+ :config
+ (setq auth-sources `(,authinfo-file))
+ (setenv "GPG_AGENT_INFO" nil) ;; emacs use internal prompt, not gpg agent
+ (setq auth-source-debug t)) ;; echo debug info to Messages
+
+;; ----------------------------- Easy PG Assistant -----------------------------
+;; Key management, cryptographic operations on regions and files, dired
+;; integration, and automatic encryption/decryption of *.gpg files.
+
+(use-package epa
+ :ensure nil ;; built-in
+ :defer .5
+ :config
+ (setq epg-gpg-program "gpg2")) ;; force use gpg2 (not gpg v.1)
+
+(provide 'auth-config)
+;;; auth-config.el ends here.
+
+;; --------------------------------- ERT Tests ---------------------------------
+;; Run these tests with M-x ert RET t RET
+
+(require 'ert)
+(require 'cl-lib)
+
+(ert-deftest auth-config/authinfo-file-exists ()
+ "Verify that `authinfo-file` actually exists on disk."
+ (should (and (stringp authinfo-file)
+ (file-exists-p authinfo-file))))
+
+(ert-deftest auth-config/gpg2-is-on-path ()
+ "Verify that the `gpg2` executable is on the user’s PATH."
+ (should (executable-find "gpg2")))
diff --git a/modules/chrono-tools.el b/modules/chrono-tools.el
new file mode 100644
index 00000000..ae0592f8
--- /dev/null
+++ b/modules/chrono-tools.el
@@ -0,0 +1,62 @@
+;;; chrono-tools.el --- Config for Date and Time-Related Utils -*- lexical-binding: t; -*-
+;; author Craig Jennings <c@cjennings.net>
+;;; Commentary:
+;;
+;; This module centralizes configuration for Emacs time-related tools:
+;;
+;; – world-clock: predefined city list and custom time format
+;; – calendar: quick navigation keybindings by day, month, and year
+;; – tmr: lightweight timer setup with sounds, notifications, and history
+;;
+;;; Code:
+
+(use-package time
+ :ensure nil ;; built-in
+ :defer 0.5
+ :bind ("C-x c" . world-clock)
+ :config
+ (setq world-clock-list
+ '(("Pacific/Honolulu" " Honolulu")
+ ("America/Los_Angeles" " San Francisco, LA")
+ ("America/Chicago" " Chicago, New Orleans")
+ ("America/New_York" " New York, Boston")
+ ("Etc/UTC" " UTC =================")
+ ("Europe/London" " London, Lisbon")
+ ("Europe/Paris" " Paris, Berlin, Rome")
+ ("Europe/Athens" " Athens, Istanbul, Moscow")
+ ("Asia/Kolkata" " India")
+ ("Asia/Shanghai" " Shanghai, Singapore")
+ ("Asia/Tokyo" " Tokyo, Seoul")))
+ (setq world-clock-time-format " %a, %d %b @ %I:%M %p %Z"))
+
+(use-package calendar
+ :ensure nil ;; built-in
+ :defer 0.5
+ :bind (("M-#" . calendar)
+ :map calendar-mode-map
+ ("," . calendar-backward-day)
+ ("." . calendar-forward-day)
+ ("<" . calendar-backward-month)
+ (">" . calendar-forward-month)
+ ("M-," . calendar-backward-year)
+ ("M-." . calendar-forward-year)))
+
+(use-package tmr
+ :defer 0.5
+ :bind ("M-t" . tmr-prefix-map)
+ :config
+ (setq tmr-sound-file "/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga"
+ tmr-notification-urgency 'normal
+ tmr-descriptions-list 'tmr-description-history))
+
+
+(provide 'chrono-tools)
+;;; chrono-tools.el ends here.
+
+;; --------------------------------- ERT Tests ---------------------------------
+;; Run these tests with M-x ert RET t RET
+
+(ert-deftest chrono-tools/tmr-sound-file-exists ()
+ "Test that `tmr-sound-file` points to an existing file."
+ (require 'tmr)
+ (should (file-exists-p tmr-sound-file)))
diff --git a/modules/custom-functions.el b/modules/custom-functions.el
index efc2d4e2..846475cb 100644
--- a/modules/custom-functions.el
+++ b/modules/custom-functions.el
@@ -548,7 +548,6 @@ Uses `sortable-time-format' for the formatting the date/time."
(message "Buffer '%s' is not visiting a file!" name)
(progn (copy-file filename newname 1) (delete-file filename)
(set-visited-file-name newname) (set-buffer-modified-p nil) t))))
-(global-set-key (kbd "C-x x m") 'cj/move-buffer-and-file)
;; RENAME BUFFER + FILE
(defun cj/rename-buffer-and-file (new-name)
@@ -566,8 +565,7 @@ Uses `sortable-time-format' for the formatting the date/time."
(rename-file filename new-name 1)
(rename-buffer new-name)
(set-visited-file-name new-name)
- (set-buffer-modified-p nil))))))
-(global-set-key (kbd "C-x x r") 'cj/rename-buffer-and-file)
+ (set-buffer-modified-p nil))))))
;; DELETE BUFFER + FILE
(defun cj/delete-buffer-and-file ()
@@ -581,7 +579,6 @@ Uses `sortable-time-format' for the formatting the date/time."
(delete-file filename t)
(message "Deleted file %s" filename)
(kill-buffer))))))
-(global-set-key (kbd "C-x x d") 'cj/delete-buffer-and-file)
;; ------------------------------- Ordinal Suffix ------------------------------
;; add the proper ordinal to a number (e.g., 1st, 2nd, 3rd, 4th).
@@ -656,7 +653,7 @@ Uses `sortable-time-format' for the formatting the date/time."
(global-set-key (kbd "C-; i t") 'cj/insert-sortable-time)
(global-set-key (kbd "C-; i d") 'cj/insert-sortable-date)
;; buffer and file operations
-(global-set-key (kbd "C-; b r") 'cj/renameq-buffer-and-file)
+(global-set-key (kbd "C-; b r") 'cj/rename-buffer-and-file)
(global-set-key (kbd "C-; b d") 'cj/delete-buffer-and-file)
(global-set-key (kbd "C-; b m") 'cj/move-buffer-and-file)
;; copy link to source file
diff --git a/modules/epa-config.el b/modules/epa-config.el
deleted file mode 100644
index 1b7ae21b..00000000
--- a/modules/epa-config.el
+++ /dev/null
@@ -1,31 +0,0 @@
-;;; epa-config.el --- EasyPG Configuration -*- lexical-binding: t; -*-
-;; author Craig Jennings <c@cjennings.net>
-
-;;; Commentary:
-;;
-
-;;; Code:
-
-;; -------------------------------- Auth Sources -------------------------------
-;; auth sources settings
-
-(use-package auth-source
- :ensure nil ;; built in
- :demand t ;; load this package early
- :config
- (setq auth-sources `(,authinfo-file))
- (setenv "GPG_AGENT_INFO" nil) ;; emacs use internal prompt, not gpg agent
- (setq auth-source-debug t)) ;; echo debug info to Messages
-
-;; ----------------------------- Easy PG Assistant -----------------------------
-;; Key management, cryptographic operations on regions and files, dired
-;; integration, and automatic encryption/decryption of *.gpg files.
-
-(use-package epa
- :ensure nil ;; built-in
- :defer .5
- :config
- (setq epg-gpg-program "gpg2")) ;; force use gpg2 (not gpg v.1)
-
-(provide 'epa-config)
-;;; epa-config.el ends here.
diff --git a/modules/file-config.el b/modules/file-config.el
new file mode 100644
index 00000000..cf5ef715
--- /dev/null
+++ b/modules/file-config.el
@@ -0,0 +1,161 @@
+;;; file-config.el --- Open Files Using Default OS Handler -*- lexical-binding: t; -*-
+;; author Craig Jennings <c@cjennings.net>
+;;
+;;; Commentary:
+;;
+;; This library provides a simple mechanism for opening files with specific
+;; extensions using your operating system’s default application rather than
+;; visiting them in an Emacs buffer. It offers:
+
+;; • A simple method to run a command on the current buffer's file
+;; "C-c x o" bound to cj/open-this-file-with
+;; • A customizable list =default-open-extensions= of file‐type suffixes
+;; (e.g. “pdf”, “docx”, “png”) that should be handled externally.
+;; • A function =default-open-file= (and its helper commands) which will
+;; launch the matching file in the OS’s default MIME handler.
+;; • Integration with =find-file-hook= so that any file whose extension
+;; appears in =default-open-extensions= is automatically opened externally
+;; upon visit.
+;; • Optional interactive commands for manually invoking an external open on
+;; point or on a user-chosen file.
+
+;;; Code:
+
+(require 'host-environment) ;; environment information functions
+
+;; ------------------------------- Open File With ------------------------------
+
+(defun cj/open-this-file-with (command)
+ "Asynchronously run COMMAND on the current buffer's file."
+ (interactive "MOpen with program: ")
+ (let ((display-buffer-alist
+ '(("\\*Async Shell Command\\*" display-buffer-no-window))))
+ (async-shell-command (format "%s \"%s\"" command buffer-file-name))))
+(global-set-key (kbd "C-c x o") #'cj/open-this-file-with)
+
+;; ------------------------- Use Default File Handlers -------------------------
+
+(defun cj/xdg-open-command ()
+ "Return the OS-default \"open\" command for this host.
+Signals an error if the host is unsupported."
+ (cond
+ ((env-linux-p) "xdg-open")
+ ((env-macos-p) "open")
+ ((env-windows-p) "start")
+ (t (error "cj/xdg-open: unsupported host environment"))))
+
+(defun cj/xdg-open (&optional filename)
+ "Open FILENAME (or the file at point) with the OS default handler.
+Logs output and exit code to buffer *cj-xdg-open.log*."
+ (interactive)
+ (let* ((file (expand-file-name
+ (or filename
+ (dired-file-name-at-point))))
+ (cmd (cj/xdg-open-command))
+ (logbuf (get-buffer-create "*cj-xdg-open.log*"))
+ exit-code)
+ (with-current-buffer logbuf
+ (goto-char (point-max))
+ (insert (format-time-string "[%Y-%m-%d %H:%M:%S] " (current-time)))
+ (insert (format "Running `%s %s`\n" cmd file))
+ (setq exit-code
+ (call-process cmd nil logbuf t file))
+ (insert (format "Exit code: %d\n\n" exit-code)))
+ exit-code))
+
+(defun cj/find-file-auto (orig-fun &rest args)
+ "If file is media or Office, open via xdg-open, else call ORIG-FUN with ARGS."
+ (let ((file (car args))
+ (exts '("\\.avi\\'"
+ "\\.mp4\\'"
+ "\\.mkv\\'"
+ "\\.mov\\'"
+ "\\.mp3\\'"
+ "\\.ogg\\'"
+ "\\.docx?\\'"
+ "\\.pptx?\\'"
+ "\\.xlsx?\\'")))
+ (if (cl-find-if (lambda (re) (string-match re file)) exts)
+ (cj/xdg-open file)
+ (apply orig-fun args))))
+(advice-add 'find-file :around #'cj/find-file-auto)
+
+(provide 'file-config)
+;;; file-config.el ends here.
+
+;; --------------------------------- ERT Tests ---------------------------------
+;; Run these tests with M-x ert RET t RET
+
+(require 'ert)
+(require 'cl-lib)
+
+
+(ert-deftest file-config/open-this-file-with-invokes-async-shell-command ()
+ "Ensure `cj/open-this-file-with` calls `async-shell-command` with the
+program name and the buffer's file name, properly quoted."
+ (let* ((test-file (expand-file-name "space in name.txt"
+ temporary-file-directory))
+ called-cmd)
+ ;; Create a temp buffer and pretend its file is TEST-FILE
+ (with-temp-buffer
+ (set (make-local-variable 'buffer-file-name) test-file)
+ ;; Override `async-shell-command` to capture its argument
+ (cl-letf (((symbol-function 'async-shell-command)
+ (lambda (cmd &rest _)
+ (setq called-cmd cmd))))
+ (cj/open-this-file-with "myprog")))
+ ;; Now assert it was called with: myprog "full/path/to/space in name.txt"
+ (should (string= called-cmd
+ (format "myprog \"%s\"" test-file)))))
+
+(ert-deftest file-config/find-file-auto-opens-matching-extension ()
+ "cj/find-file-auto should invoke `cj/xdg-open' when the filename has a media/Office extension."
+ (let* ((called-file nil)
+ (test-file "/tmp/video.mp4")
+ ;; stash the real function so we can restore it later
+ (orig-xdg-open (symbol-function 'cj/xdg-open)))
+ (cl-letf (((symbol-function 'cj/xdg-open)
+ (lambda (file)
+ (setq called-file file)
+ ;; pretend we returned an exit code
+ 0)))
+ (unwind-protect
+ (progn
+ ;; call the advice wrapper as if Emacs were doing (find-file test-file)
+ (cj/find-file-auto
+ (lambda (_file) (error "Should not call original find-file"))
+ test-file)
+ (should (equal called-file test-file)))
+ ;; restore
+ (fset 'cj/xdg-open orig-xdg-open)))))
+
+(ert-deftest file-config/find-file-auto-falls-back-for-nonmatching ()
+ "cj/find-file-auto should fall back to the original FIND-FILE when the extension does not match."
+ (let ((called-file nil)
+ (test-file "/tmp/document.txt"))
+ (cj/find-file-auto
+ (lambda (file)
+ (setq called-file file)
+ ;; pretend that visiting succeeded
+ t)
+ test-file)
+ (should (equal called-file test-file))))
+
+(ert-deftest file-config/xdg-open-logs-and-returns-exit-code ()
+ "cj/xdg-open should call the OS command, log its invocation+exit code, and return that code."
+ (let* ((dummy-file (make-temp-file "cj-test" nil ".foo"))
+ (logbuf (get-buffer-create "*cj-xdg-open.log*"))
+ ;; override the command to something harmless
+ (orig-cmd-fn (symbol-function 'cj/xdg-open-command)))
+ (cl-letf (((symbol-function 'cj/xdg-open-command)
+ (lambda () "true")))
+ (unwind-protect
+ (let ((code (cj/xdg-open dummy-file)))
+ (should (= code 0))
+ (with-current-buffer logbuf
+ (goto-char (point-min))
+ (should (search-forward (format "`true %s`" dummy-file) nil t))
+ (should (search-forward "Exit code: 0" nil t))))
+ ;; cleanup
+ (kill-buffer logbuf)
+ (fset 'cj/xdg-open-command orig-cmd-fn)))))
diff --git a/modules/flyspell-config.el b/modules/flyspell-and-abbrev.el
index 89fd25a1..bcc8be69 100644
--- a/modules/flyspell-config.el
+++ b/modules/flyspell-and-abbrev.el
@@ -1,40 +1,40 @@
-;;; flyspell-config.el --- Spell Check Configuration -*- lexical-binding: t; -*-
+;;; flyspell-and-abbrev.el --- Spell Check Configuration -*- lexical-binding: t; -*-
;; author Craig Jennings <c@cjennings.net>
;;; Commentary:
-;; Spell-Checking: Flyspell
-
+;; WORKFLOW:
;; C-' is now my main interface for all spell checking.
-
+;;
;; The workflow is that it finds the nearest misspelled word above where the
;; cursor is, allows for saving or correcting, then stops. You may proceed to
;; the next misspelling by selecting C-' again.
-
+;;
+;; Use M-o to get to 'other options', like saving to your personal dictionary.
+;;
;; Flyspell will automatically run in a mode appropriate for the buffer type
;; - if it's a programming mode, it will only check comments
;; - if in text mode, it will check everything
;; - otherwise it will turn off.
;; This check happens on every mode switch.
-
+;;
;; If you want flyspell on in another mode (say fundamental mode), or you want
;; to turn it off, you can toggle flyspell's state with 'C-c f'
-
+;;
;; The nicest thing is that each spell correction creates an abbrev. This
;; essentially is a shortcut that expands that same misspelling to the correct
;; spelling the next time it's typed. That idea comes courtesy Artur Malabarba,
;; and it's increased my overall typing speed.
-
+;;
;; Original idea here:
;; http://endlessparentheses.com/ispell-and-abbrev-the-perfect-auto-correct.html
-
+;;
;; The code below is my refactoring of Artur Malabarba's code, and using
;; flyspell rather than ispell.
-
;;
-;; Use M-o to get to 'other options', like saving to your personal dictionary.
-
-;; Note that the keybinding typically taken for the flyspell-mode-map "C-;" has
+;; NOTES:
+;;
+;; FYI, the keybinding typically taken for the flyspell-mode-map "C-;" has
;; been deliberately hijacked in custom-functions.el for my personal-keymap.
;; This is the code run there:
@@ -43,8 +43,17 @@
;;; Code:
-;; ---------------------------- Ispell And Flyspell ----------------------------
+;; ----------------------------------- Abbrev ----------------------------------
+(use-package abbrev-mode
+ :ensure nil
+ :defer 0.5
+ :custom
+ (abbrev-file-name (concat user-emacs-directory "assets/abbrev_defs"))
+ :config
+ (abbrev-mode 1))
+
+;; ---------------------------- Ispell And Flyspell ----------------------------
(use-package ispell
:defer .5
@@ -198,5 +207,5 @@ argument is provided, when it's created in the global dictionary."
(define-key global-map (kbd "C-'") 'cj/flyspell-then-abbrev)
-(provide 'flyspell-config)
-;;; flyspell-config.el ends here.
+(provide 'flyspell-and-abbrev)
+;;; flyspell-and-abbrev.el ends here.
diff --git a/modules/keybindings.el b/modules/keybindings.el
index af600999..a8b3eb41 100644
--- a/modules/keybindings.el
+++ b/modules/keybindings.el
@@ -101,6 +101,21 @@
(define-key jump-to-keymap (kbd "I")
#'(lambda () (interactive) (find-file emacs-init-file)))
+
+;; ---------------------------- Keybinding Discovery ---------------------------
+
+(use-package free-keys
+ :defer 1
+ :bind ("C-h C-k" . free-keys))
+
+(use-package which-key
+ :defer 1
+ :config
+ (setq which-key-idle-delay 3.0
+ which-key-popup-type 'side-window)
+ (which-key-setup-side-window-right-bottom)
+ (which-key-mode 1))
+
;; ---------------------------- General Keybindings ----------------------------
;; Avoid hostile bindings
diff --git a/modules/keyboard-macros.el b/modules/keyboard-macros.el
new file mode 100644
index 00000000..cff25b39
--- /dev/null
+++ b/modules/keyboard-macros.el
@@ -0,0 +1,111 @@
+;;; keyboard-macros.el --- Keyboard Macro Management -*- lexical-binding: t; -*-
+;; author Craig Jennings <c@cjennings.net>
+
+;;; Commentary:
+;;
+;; This library provides a simple, end-user–focused interface for
+;; creating, naming, saving, and replaying keyboard macros in Emacs.
+;; All commands are built on top of the built-in =kmacro= machinery, but
+;; add a lightweight workflow and persistence across sessions.
+;;
+;; User Workflow:
+;;
+;; 1. Start recording with C-F3 (or M-x cj/kbd-macro-start-or-end)
+;; This toggles macro recording on.
+;; Now you can perform all the edits you want recorded in the macro.
+;;
+;; 2. Stop recording with C-F3 (or M-x cj/kbd-macro-start-or-end)
+;; This stops recording and the macro becomes the “last keyboard macro.”
+;;
+;; 3. Replay your macro <f3> (or M-x call-last-kbd-macro)
+;;
+;; 4. Name your macro with M-<F3>
+;; You will be prompted for a short name (e.g. =align-comments=,
+;; =cleanup-trail-spaces=). This name is how you’ll refer to it later.
+;;
+;;
+;; 5. Recall that macro later with M-x [the name you gave the macro]
+;;
+;; 6. View all your saved macros with s-<f3> (super-f3)
+;;
+;; 7. All macros reload at startup automatically.
+;; When this library is loaded, it will look for the save file and
+;; re-establish all your named macros in your current session.
+;;
+;;; Code:
+
+(require 'user-constants) ;; definition of sync-dir constant is here.
+
+(defvar macros-file (concat sync-dir "macros.el")
+ "The location of the macros file for recorded saved macros via M-f3.")
+
+(defun ensure-macros-file (file)
+ "Ensure FILE exists and its first line enables lexical-binding."
+ (unless (file-exists-p file)
+ (with-temp-file file
+ (insert ";;; -*- lexical-binding: t -*-\n"))))
+
+(when (file-exists-p macros-file)
+ (load macros-file))
+
+(defun cj/kbd-macro-start-or-end ()
+ "Toggle start/end of keyboard macro definition."
+ (interactive)
+ (if defining-kbd-macro
+ (end-kbd-macro)
+ (start-kbd-macro nil)))
+(global-set-key (kbd "C-<f3>") #'cj/kbd-macro-start-or-end)
+(global-set-key (kbd "<f3>") #'call-last-kbd-macro)
+
+(defun cj/save-maybe-edit-macro (name)
+ "Save last macro as NAME in `macros-file'; edit if prefix arg."
+ (interactive "SName of macro: ")
+ (kmacro-name-last-macro name)
+ (find-file macros-file)
+ (goto-char (point-max))
+ (newline)
+ (insert-kbd-macro name)
+ (newline)
+ (save-buffer)
+ (switch-to-buffer (other-buffer (current-buffer) 1))
+ (when current-prefix-arg
+ (find-file macros-file)
+ (goto-char (point-max)))
+ name)
+(global-set-key (kbd "M-<f3>") #'cj/save-maybe-edit-macro)
+
+(global-set-key (kbd "s-<f3>") (lambda () (interactive) (find-file macros-file)))
+
+(defun ensure-macros-file (file)
+ "Ensure FILE exists and its first line enables lexical-binding."
+ (unless (file-exists-p file)
+ (with-temp-file file
+ (insert ";;; -*- lexical-binding: t -*-\n"))))
+
+(when (file-exists-p macros-file)
+ (load macros-file))
+
+(provide 'keyboard-macros)
+;;; keyboard-macros.el ends here.
+
+
+;; --------------------------------- ERT Tests ---------------------------------
+;; Run these tests with M-x ert RET t RET
+
+(require 'ert)
+
+(ert-deftest keyboard-macros/ensure-macros-file-creates-header ()
+ "ensure-macros-file creates FILE with the right header."
+ (let ((file (make-temp-file "macros-test" nil ".el")))
+ (unwind-protect
+ (progn
+ (delete-file file)
+ (should-not (file-exists-p file))
+ (ensure-macros-file file)
+ (should (file-exists-p file))
+ (let ((contents (with-temp-buffer
+ (insert-file-contents file)
+ (buffer-string))))
+ (should (string= contents ";;; -*- lexical-binding: t -*-\n"))))
+ (when (file-exists-p file)
+ (delete-file file)))))
diff --git a/modules/system-defaults.el b/modules/system-defaults.el
index b984b78c..c52b3e04 100644
--- a/modules/system-defaults.el
+++ b/modules/system-defaults.el
@@ -142,15 +142,16 @@ Return non-nil to indicate the warning was handled."
;; disabling mouse prevents accidental mouse moves modifying text
(use-package inhibit-mouse
- :commands inhibit-mouse-mode
:hook (after-init . inhibit-mouse-mode)
:custom
(inhibit-mouse-adjust-mouse-highlight t)
(inhibit-mouse-adjust-show-help-function t)
+ :bind
+ ("C-c M-m" . inhibit-mouse-mode) ;; toggle with C-c M-m
:config
(if (daemonp)
- (add-hook 'server-after-make-frame-hook #'inhibit-mouse-mode)
- (inhibit-mouse-mode 1)))
+ (add-hook 'server-after-make-frame-hook #'inhibit-mouse-mode)
+ (inhibit-mouse-mode 1)))
;; ------------------------------- Be Quiet(er)! -------------------------------
;; reduces "helpful" instructions that distract Emacs power users.
diff --git a/modules/system-utils.el b/modules/system-utils.el
index e0121f0f..53992481 100644
--- a/modules/system-utils.el
+++ b/modules/system-utils.el
@@ -4,371 +4,89 @@
;;; Code:
+(require 'cl-lib)
+(require 'host-environment)
-;; ---------------------------------- Xdg-Open ---------------------------------
-;; open specific file extensions with the system's default mime-type handler
-
-(defun cj/xdg-open (&optional filename)
- (interactive)
- (let ((command (cond ((eq system-type 'gnu/linux) "xdg-open")
- ((eq system-type 'darwin) "open")
- ((eq system-type 'windows-nt) "start")
- (t ""))))
- (with-current-buffer (get-buffer-create "xdg-open.log")
- (goto-char (point-max))
- (insert (format "Running command %s\n" command))
- (let ((exit-code (call-process command nil t t (expand-file-name
- (or filename (dired-file-name-at-point))))))
- (insert (format "Exit code: %s\n" exit-code))))))
-
-(defun cj/find-file-auto (orig-fun &rest args)
- (let ((filename (car args)))
- (if (cl-find-if
- (lambda (regexp) (string-match regexp filename))
- '("\\.avi\\'"
- "\\.mp4\\'"
- "\\.divx\\'"
- "\\.flv\\'"
- "\\.mkv\\'"
- "\\.mpeg\\'"
- "\\.mov\\'"
- "\\.wav\\'"
- "\\.webm\\'"
- "\\.mp3\\'"
- "\\.opus\\'"
- "\\.ogg\\'"
- "\\.flac\\'"
- "\\.docx?\\'"
- "\\.pptx?\\'"
- "\\.xlsx?\\'"
- ))
- (progn
- (cj/xdg-open filename))
- (progn
- (apply orig-fun args)))))
-
-(advice-add 'find-file :around 'cj/find-file-auto)
-
-;; ---------------------------------- Ibuffer ----------------------------------
-
-(global-set-key [remap list-buffers] 'ibuffer) ;; use ibuffer, not list-buffers
-
-(use-package nerd-icons-ibuffer
- :defer .5
- :after nerd-icons
- :hook (ibuffer-mode . nerd-icons-ibuffer-mode)
- :config
- (setq nerd-icons-ibuffer-icon t)
- (setq nerd-icons-ibuffer-color-icon t)
- (setq nerd-icons-ibuffer-human-readable-size t))
-
-;; ------------------------ Killing Buffers And Windows ------------------------
-;; Accidentally killing buffers can lose data. these functions override common
-;; buffer killing functions and buries buffers on the
-;; 'cj/buffer-bury-alive-list' list rather than killing them. Allows for
-;; interactive adding to the 'cj/buffer-bury-alive-list' via 'C-u C-x k'
-
-;; BUFFER BURY ALIVE LIST
-(defvar cj/buffer-bury-alive-list '("*dashboard*"
- "*scratch*"
- "*Messages*"
- "*ChatGPT*")
- "Buffers that shouldn't be killed, but buried instead.")
-
-;; KILL BUFFER AND WINDOW
-(defun cj/kill-buffer-and-window ()
- "Kill current buffer and window.
-Buries buffers instead if they are on the cj/buffer-bury-alive-list."
- (interactive)
- (let ((target-buffer (current-buffer)))
- (delete-window)
- (cj/kill-buffer-or-bury-alive target-buffer)))
-(global-set-key (kbd "M-C") 'cj/kill-buffer-and-window)
-
-;; KILL OTHER WINDOW
-(defun cj/kill-other-window ()
- "Close the next window and kill any buffer in it.
-Buries buffers instead if they are on the cj/buffer-bury-alive-list."
- (interactive)
- (other-window 1)
- (let ((target-buffer (current-buffer)))
- (if (not (one-window-p)) (delete-window))
- (cj/kill-buffer-or-bury-alive target-buffer)))
-(global-set-key (kbd "M-O") 'cj/kill-other-window)
-
-;; KILL ALL OTHER BUFFERS AND WINDOWS
-(defun cj/kill-all-other-buffers-and-windows ()
- "Save buffers, then kill all other buffers and windows.
-Buries buffers instead if they are on the cj/buffer-bury-alive-list."
- (interactive)
- (save-some-buffers)
- (delete-other-windows)
- (mapc 'cj/kill-buffer-or-bury-alive (delq (current-buffer) (buffer-list))))
-(global-set-key (kbd "M-M") 'cj/kill-all-other-buffers-and-windows)
-
-;; KILL BUFFER OR BURY ALIVE
-(defun cj/kill-buffer-or-bury-alive (target-buffer)
- "Bury buffers on the bury-instead-list rather than killing them.
-With a prefix, add the TARGET-BUFFER to \='cj/buffer-bury-alive-list\='."
- (interactive "bKill or Add to bury (don't kill) buffer list: ")
- (with-current-buffer target-buffer
- (if current-prefix-arg
- (progn
- (add-to-list 'cj/buffer-bury-alive-list (buffer-name (current-buffer)))
- (message "Added %s to bury-alive-list" (buffer-name (current-buffer))))
- (if (member (buffer-name (current-buffer)) cj/buffer-bury-alive-list)
- (bury-buffer)
- (kill-buffer (current-buffer))))))
-(global-set-key [remap kill-buffer] #'cj/kill-buffer-or-bury-alive)
-
-;; --------------------------- Emacs Server Shutdown ---------------------------
-;; shuts down the Emacs server. useful with emacsclient.
-
-(defun server-shutdown ()
- "Save buffers, quit, and shutdown (kill) server."
- (interactive)
- (save-some-buffers)
- (kill-emacs))
-(global-set-key (kbd "C-<f10>") 'server-shutdown)
-(global-set-key (kbd "<f10>") 'save-buffers-kill-terminal)
-
-;; --------------------------------- Free Keys ---------------------------------
-;; Displays free keybindings. Allows indicating a specific key prefix.
-
-(use-package free-keys
- :defer 1
- :bind ("C-h C-k" . free-keys))
-
-;; --------------------------------- Sudo Edit ---------------------------------
-;; Edit a file the current buffer is visiting as sudo user.
+;; ---------------------------- Edit A File With Sudo ----------------------------
(use-package sudo-edit
:defer 1
:bind ("C-x M-f" . sudo-edit))
-;; --------------------------- Open File With Command --------------------------
-;; opens the current buffer's file with a command. Prompts if interactive.
-
-(defun cj/open-file-with-command (command)
- "Asynchronously open the file assocated with the current buffer with COMMAND.
-Don't automatically display output buffers, but keep them in buffer list."
+(defun cj/open-file-with (command)
+ "Asynchronously run COMMAND on the current buffer's file."
(interactive "MOpen with program: ")
- (let ((display-buffer-keywords
- '(("*Async Shell Command*" display-buffer-no-window (nil)))))
- (add-to-list 'display-buffer-alist display-buffer-keywords))
- (async-shell-command (format "%s \"%s\"" command buffer-file-name)))
+ (let ((display-buffer-alist
+ '(("\\*Async Shell Command\\*" display-buffer-no-window))))
+ (async-shell-command (format "%s \"%s\"" command buffer-file-name))))
-;; --------------------------------- Open With ---------------------------------
-;; automatically opens files with specific programs using file extensions.
+;; ------------------------------ Server Shutdown ------------------------------
-(use-package openwith
- :defer 1
- :config
- (setq openwith-associations
- (list
- (list (openwith-make-extension-regexp
- '("mpg" "mpeg" "mp3" "mp4" "webm" "avi" "wmv" "wav" "mov" "flv" "ogm" "ogg" "mkv"))
- "mpv"
- '(file))
- ;; removed jpg from list below as dashboard was opening nxiv
- (list (openwith-make-extension-regexp
- '("xbm" "pbm" "pgm" "ppm" "pnm" "png" "gif" "bmp" "tif"))
- "nsxiv"
- '(file))
- (list (openwith-make-extension-regexp
- '("odt" "odf" "xls" "xlsx" "doc" "docx"))
- "libreoffice"
- '(file))
- (list (openwith-make-extension-regexp
- '("cbr" "cbz"))
- "zathura"
- '(file)))))
+(defun server-shutdown ()
+ "Save buffers, kill Emacs and shutdown the server."
+ (interactive)
+ (save-some-buffers)
+ (kill-emacs))
+(global-set-key (kbd "C-<f10>") #'server-shutdown)
+(global-set-key (kbd "<f10>") #'save-buffers-kill-terminal)
-;; --------------------------------- Which Key ---------------------------------
-;; displays key bindings following your currently entered incomplete command
+;; ------------------------ List Buffers With Nerd Icons -----------------------
-(use-package which-key
- :defer 1
+(global-set-key [remap list-buffers] #'ibuffer)
+(use-package nerd-icons-ibuffer
+ :defer 0.5
+ :after nerd-icons
+ :hook (ibuffer-mode . nerd-icons-ibuffer-mode)
:config
- (setq which-key-idle-delay 3.0)
- (setq which-key-popup-type 'side-window)
- (add-to-list 'which-key-replacement-alist '((nil . "digit-argument") . t))
- (which-key-setup-side-window-right-bottom)
- (which-key-mode 1))
-
-;; ------------------------------- Scratch Buffer ------------------------------
-;; make the scratch buffer joyful, org-mode by default, and persistent.
-;; coding tip: org babel + code blocks allows for more flexibility and comments.
-
-(defvar scratch-emacs-version-and-system (concat ";; Welcome to Emacs "
- emacs-version " running on "
- system-configuration ".\n"))
-(defvar scratch-greet (concat ";; Emacs ♥ you, " user-login-name ". "
- "Happy Hacking!\n\n"))
-
-
-(setq initial-scratch-message (concat scratch-emacs-version-and-system
- scratch-greet))
-
-;; make scratch buffer an org-mode buffer
+ (setq nerd-icons-ibuffer-icon t
+ nerd-icons-ibuffer-color-icon t
+ nerd-icons-ibuffer-human-readable-size t))
+
+;; -------------------------- Scratch Buffer Happiness -------------------------
+
+;; Customize the scratch buffer
+(defvar scratch-emacs-version-and-system
+ (concat ";; Welcome to Emacs " emacs-version
+ " running on " system-configuration ".\n"))
+(defvar scratch-greet
+ (concat ";; Emacs ♥ you, " user-login-name ". Happy Hacking!\n\n"))
+(setq initial-scratch-message
+ (concat scratch-emacs-version-and-system scratch-greet))
(setq initial-major-mode 'org-mode)
-;; -------------------------------- World Clock --------------------------------
-;; displays current time in various timezones
-
-(use-package time
- :ensure nil ;; built-in
- :defer .5
- :bind ("C-x c" . world-clock)
- :config
- (setq world-clock-list
- '(("Pacific/Honolulu" " Honolulu")
- ("America/Los_Angeles" " San Francisco, Los Angeles")
- ("America/Chicago" " New Orleans, Chicago")
- ("America/New_York" " New York, Boston")
- ("Etc/UTC" " UTC ====================================")
- ("Europe/London" " London, Lisbon")
- ("Europe/Paris" " Paris, Berlin, Rome, Barcelona")
- ("Europe/Athens" " Athens, Istanbul, Kyiv, Moscow, Tel Aviv")
- ("Asia/Yerevan" " Yerevan")
- ("Asia/Kolkata" " India")
- ("Asia/Shanghai" " Shanghai, Singapore")
- ("Asia/Tokyo" " Tokyo, Seoul")))
- (setq world-clock-time-format " %a, %d %b @ %I:%M %p %Z"))
-
-;; ---------------------------------- Calendar ---------------------------------
-;; providing simple shortcuts
-;; backward and forward day are ',' and '.'
-;; shift & meta moves by week or year
-;; C-. jumps to today
-;; consistent with scheduling in org-mode
-
-(use-package calendar
- :defer .5
- :ensure nil ;; built-in
- :bind
- ("M-#" . calendar)
- (:map calendar-mode-map
- ("," . calendar-backward-day)
- ("." . calendar-forward-day)
- ("<" . calendar-backward-month)
- (">" . calendar-forward-month)
- ("M-," . calendar-backward-year)
- ("M-." . calendar-forward-year)))
-
;; --------------------------------- Dictionary --------------------------------
-;; install Webster's dictionary in StarDict format
-;; http://jsomers.net/blog/dictionary
(use-package sdcv
:defer 1
- :bind ("C-h d" . 'sdcv-search-input))
-
-;; ------------------------------ -Keyboard Macros -----------------------------
-;; note that this leverages simple, easy to remember shortcuts
-;;
-;; start a macro with C-f3, perform your actions, then finish with C-f3 (same key)
-;; run your macro by pressing f3
-;; if you wish to save and edit it (provide a keybinding), use C-u M-f3
-;; otherwise, jsut save it with M-f3 and call it with M-x (name you provided)
-
-(defun kbd-macro-start-or-end ()
- "Begins a keyboard macro definition, or if one's in progress, finish it."
- (interactive)
- (if defining-kbd-macro
- (end-kbd-macro)
- (start-kbd-macro nil)))
-(global-set-key (kbd "C-<f3>") 'kbd-macro-start-or-end)
-(global-set-key (kbd"<f3>") 'call-last-kbd-macro)
-(global-set-key (kbd"M-<f3>") 'cj/save-maybe-edit-macro)
-
-(defun cj/save-maybe-edit-macro (name)
- "Save a macro in `macros-file'.
-Save the last defined macro as NAME at the end of your `macros-file'
-The `macros-file' is defined in the constants section of the `init.el').
-The function offers the option to open the `macros-file' for editing when called
-with a prefix argument."
- (interactive "SName of the macro (w/o spaces): ")
- (kmacro-name-last-macro name)
- (find-file macros-file)
- (goto-char (point-max))
- (newline)
- (insert-kbd-macro name)
- (newline)
- ;; Save changes and switch back to previous buffer
- (save-buffer)
- (switch-to-buffer (other-buffer (current-buffer) 1))
- ;; Check for the prefix argument and open the macros-file to edit
- (if current-prefix-arg
- (progn
- (find-file macros-file)
- (goto-char (point-max))))
- ;; Return name for convenience
- name)
-
-;; now load all saved macros, creating an empty macro-file if it doesn't exist.
-(if (file-exists-p macros-file)
- (load macros-file)
- (progn
- (write-region ";;; -*- lexical-binding: t -*-\n" nil macros-file)
- (message "Saved macros file not found, so created: %s" macros-file)))
-
-;; -------------------------------- Abbrev Mode --------------------------------
-;; word abbreviations mode. used to auto-correct spelling (see flyspell-config)
-
-(use-package abbrev-mode
- :ensure nil ;; built-in
- :defer .5
- :custom
- (abbrev-file-name (concat user-emacs-directory "assets/abbrev_defs"))
- :config
- (abbrev-mode)) ;; use abbrev mode everywhere
+ :bind ("C-h d" . sdcv-search-input))
;; -------------------------------- Log Silently -------------------------------
-;; utility to silently log to the Messages buffer (for debugging/warning)
(defun cj/log-silently (text)
- "Send TEXT to the Messages buffer bypassing the echo area."
- (let ((inhibit-read-only t)
- (messages-buffer (get-buffer "*Messages*")))
- (with-current-buffer messages-buffer
- (goto-char (point-max))
- (unless (bolp)
- (insert "\n"))
- (insert text)
- (unless (bolp)
- (insert "\n")))))
+ "Append TEXT to *Messages* buffer without echoing in the minibuffer."
+ (let ((inhibit-read-only t))
+ (with-current-buffer (get-buffer-create "*Messages*")
+ (goto-char (point-max))
+ (unless (bolp) (insert "\n"))
+ (insert text)
+ (unless (bolp) (insert "\n")))))
-;; ----------------------------------- Proced ----------------------------------
-;; yes, there's a process monitor built-into Emacs. It's just not configured.
+;; ------------------------------ Process Monitor ------------------------------
(use-package proced
- :defer .5
- :ensure nil ;;built-in
+ :ensure nil ;; built-in
+ :defer 0.5
:commands proced
:bind ("C-M-p" . proced)
:custom
(proced-auto-update-flag t)
- (proced-goal-attribute nil)
(proced-show-remote-processes t)
(proced-enable-color-flag t)
(proced-format 'custom)
:config
- (add-to-list
- 'proced-format-alist
- '(custom user pid ppid sess tree pcpu pmem rss start time state (args
- comm))))
-
-;; ------------------------------------ TMR ------------------------------------
-
-(use-package tmr
- :defer .5
- :bind ("M-t" . tmr-prefix-map)
- :config
- (setq tmr-sound-file
- "/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga")
- (setq tmr-notification-urgency 'normal)
- (setq tmr-descriptions-list 'tmr-description-history))
+ (add-to-list 'proced-format-alist
+ '(custom user pid ppid sess tree pcpu pmem rss start time
+ state (args comm))))
(provide 'system-utils)
;;; system-utils.el ends here
diff --git a/modules/undead-buffers.el b/modules/undead-buffers.el
new file mode 100644
index 00000000..fbfc2782
--- /dev/null
+++ b/modules/undead-buffers.el
@@ -0,0 +1,167 @@
+;;; undead-buffers.el --- Bury Rather Than Kill These Buffers -*- lexical-binding: t; -*-
+
+;;; Commentary:
+;;
+;; This library allows for “burying” selected buffers instead of killing them.
+;; Since they won't be killed, I'm calling them "undead buffers".
+;;
+;; The main function cj/kill-buffer-or-bury-alive replaces kill-buffer.
+;;
+;; Additional helper commands and key bindings:
+;; - M-C (=cj/kill-buffer-and-window=): delete this window and bury/kill its buffer.
+;; - M-O (=cj/kill-other-window=): delete the next window and bury/kill its buffer.
+;; - M-M (=cj/kill-all-other-buffers-and-windows=): kill or bury all buffers except
+;; the current one and delete all other windows.
+;;
+;; Add to the list of "undead buffers" by adding to the cj/buffer-bury-alive-list
+;; variable.
+;;
+;;; Code:
+
+(defvar cj/buffer-bury-alive-list
+ '("*dashboard*" "*scratch*" "*Messages*" "*ert*" "*AI-Assistant*")
+ "Buffers to bury instead of killing.")
+
+(defun cj/kill-buffer-or-bury-alive (buffer)
+ "Kill BUFFER or bury it if it's in `cj/buffer-bury-alive-list'."
+ (interactive "bBuffer to kill or bury: ")
+ (with-current-buffer buffer
+ (if current-prefix-arg
+ (progn
+ (add-to-list 'cj/buffer-bury-alive-list (buffer-name))
+ (message "Added %s to bury-alive-list" (buffer-name)))
+ (if (member (buffer-name) cj/buffer-bury-alive-list)
+ (bury-buffer)
+ (kill-buffer)))))
+(global-set-key [remap kill-buffer] #'cj/kill-buffer-or-bury-alive)
+
+(defun cj/kill-buffer-and-window ()
+ "Delete window and kill or bury its buffer."
+ (interactive)
+ (let ((buf (current-buffer)))
+ (delete-window)
+ (cj/kill-buffer-or-bury-alive buf)))
+(global-set-key (kbd "M-C") #'cj/kill-buffer-and-window)
+
+(defun cj/kill-other-window ()
+ "Delete the next window and kill or bury its buffer."
+ (interactive)
+ (other-window 1)
+ (let ((buf (current-buffer)))
+ (unless (one-window-p)
+ (delete-window))
+ (cj/kill-buffer-or-bury-alive buf)))
+(global-set-key (kbd "M-O") #'cj/kill-other-window)
+
+(defun cj/kill-all-other-buffers-and-windows ()
+ "Kill or bury all other buffers, then delete other windows."
+ (interactive)
+ (save-some-buffers)
+ (delete-other-windows)
+ (mapc #'cj/kill-buffer-or-bury-alive
+ (delq (current-buffer) (buffer-list))))
+(global-set-key (kbd "M-M") #'cj/kill-all-other-buffers-and-windows)
+
+(provide 'undead-buffers)
+;;; undead-buffers.el ends here.
+
+;; --------------------------------- ERT Tests ---------------------------------
+;; Run these tests with M-x ert RET t RET
+
+(require 'ert)
+(require 'cl-lib)
+
+(ert-deftest undead-buffers/kill-or-bury-when-not-in-list-kills ()
+ "cj/kill-buffer-or-bury-alive should kill a buffer not in `cj/buffer-bury-alive-list'."
+ (let* ((buf (generate-new-buffer "test-not-in-list"))
+ (orig (copy-sequence cj/buffer-bury-alive-list)))
+ (unwind-protect
+ (progn
+ (should (buffer-live-p buf))
+ (cj/kill-buffer-or-bury-alive (buffer-name buf))
+ (should-not (buffer-live-p buf)))
+ (setq cj/buffer-bury-alive-list orig)
+ (when (buffer-live-p buf) (kill-buffer buf)))))
+
+(ert-deftest undead-buffers/kill-or-bury-when-in-list-buries ()
+ "cj/kill-buffer-or-bury-alive should bury (not kill) a buffer in the list."
+ (let* ((name "*dashboard*") ; an element already in the default list
+ (buf (generate-new-buffer name))
+ (orig (copy-sequence cj/buffer-bury-alive-list))
+ win-was)
+ (unwind-protect
+ (progn
+ (add-to-list 'cj/buffer-bury-alive-list name)
+ ;; show it in a temporary window so we can detect bury
+ (setq win-was (display-buffer buf))
+ (cj/kill-buffer-or-bury-alive name)
+ ;; bury should leave it alive
+ (should (buffer-live-p buf))
+ ;; note: Emacs’s `bury-buffer` does not delete windows by default,
+ ;; so we no longer assert that no window shows it.
+ )
+ ;; cleanup
+ (setq cj/buffer-bury-alive-list orig)
+ (delete-windows-on buf)
+ (kill-buffer buf))))
+
+(ert-deftest undead-buffers/kill-or-bury-adds-to-list-with-prefix ()
+ "Calling `cj/kill-buffer-or-bury-alive' with a prefix arg should add the buffer to the list."
+ (let* ((buf (generate-new-buffer "test-add-prefix"))
+ (orig (copy-sequence cj/buffer-bury-alive-list)))
+ (unwind-protect
+ (progn
+ (let ((current-prefix-arg '(4)))
+ (cj/kill-buffer-or-bury-alive (buffer-name buf)))
+ (should (member (buffer-name buf) cj/buffer-bury-alive-list)))
+ (setq cj/buffer-bury-alive-list orig)
+ (kill-buffer buf))))
+
+(ert-deftest undead-buffers/kill-buffer-and-window-removes-window ()
+ "cj/kill-buffer-and-window should delete the current window and kill/bury its buffer."
+ (let* ((buf (generate-new-buffer "test-kill-and-win"))
+ (orig (copy-sequence cj/buffer-bury-alive-list)))
+ (split-window) ; now two windows
+ (let ((win (next-window)))
+ (set-window-buffer win buf)
+ (select-window win)
+ (cj/kill-buffer-and-window)
+ (should-not (window-live-p win))
+ (unless (member (buffer-name buf) orig)
+ (should-not (buffer-live-p buf))))
+ (setq cj/buffer-bury-alive-list orig)))
+
+(ert-deftest undead-buffers/kill-other-window-deletes-that-window ()
+ "cj/kill-other-window should delete the *other* window and kill/bury its buffer."
+ (let* ((buf1 (current-buffer))
+ (buf2 (generate-new-buffer "test-other-window"))
+ (orig (copy-sequence cj/buffer-bury-alive-list)))
+ (split-window)
+ (let* ((win1 (selected-window))
+ (win2 (next-window win1)))
+ (set-window-buffer win2 buf2)
+ ;; stay on the original window
+ (select-window win1)
+ (cj/kill-other-window)
+ (should-not (window-live-p win2))
+ (unless (member (buffer-name buf2) orig)
+ (should-not (buffer-live-p buf2))))
+ (setq cj/buffer-bury-alive-list orig)))
+
+(ert-deftest undead-buffers/kill-all-other-buffers-and-windows-keeps-only-current ()
+ "cj/kill-all-other-buffers-and-windows should delete other windows and kill/bury all other buffers."
+ (let* ((main (current-buffer))
+ (extra (generate-new-buffer "test-all-others"))
+ (orig (copy-sequence cj/buffer-bury-alive-list)))
+ (split-window)
+ (set-window-buffer (next-window) extra)
+ (cj/kill-all-other-buffers-and-windows)
+ (should (one-window-p))
+ ;; main buffer still exists
+ (should (buffer-live-p main))
+ ;; extra buffer either buried or killed
+ (unless (member (buffer-name extra) orig)
+ (should-not (buffer-live-p extra)))
+ ;; cleanup
+ (setq cj/buffer-bury-alive-list orig)
+ (when (buffer-live-p extra) (kill-buffer extra))))
diff --git a/modules/user-constants.el b/modules/user-constants.el
index 9c401775..f0fec620 100644
--- a/modules/user-constants.el
+++ b/modules/user-constants.el
@@ -77,13 +77,7 @@
(defvar article-archive (concat sync-dir "article-archive.org")
"The location of the org file that stores saved articples to keep.")
- ;
-(defvar ledger-file (concat sync-dir "main.ledger")
- "The location of the user's ledger file.")
-
-(defvar macros-file (concat sync-dir "macros.el")
- "The location of the macros file for recorded saved macros via M-f3.")
-
+ ;
(defvar authinfo-file (concat user-home-dir "/.authinfo.gpg")
"The location of the encrypted .authinfo or .netrc file.")
@@ -113,9 +107,7 @@
inbox-file
contacts-file
article-file
- article-archive
- ledger-file
- macros-file))
+ article-archive))
(provide 'user-constants)
;;; user-constants.el ends here.