From 9278ddd4ea1a8b1a4c1edaa8894516e3f48d245b Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Thu, 14 Aug 2025 19:24:49 -0500 Subject: refactor(system-utils): major refactoring / adding tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- modules/file-config.el | 161 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 modules/file-config.el (limited to 'modules/file-config.el') 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 +;; +;;; 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))))) -- cgit v1.2.3