aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-29 04:41:40 -0400
committerCraig Jennings <c@cjennings.net>2026-06-29 04:41:40 -0400
commitca3d447bfd849b54f8f9d74568f3dee1e86c391e (patch)
tree86a1f7d79310cf57ed23b2f5b02be7675badc25e /modules
parenta56a714ce30fe91bd5afd0ba181d0d4bc508a8c0 (diff)
downloaddotemacs-ca3d447bfd849b54f8f9d74568f3dee1e86c391e.tar.gz
dotemacs-ca3d447bfd849b54f8f9d74568f3dee1e86c391e.zip
refactor: split custom-misc.el into focused modules
custom-misc.el was an incoherent grab-bag, so anything small defaulted to landing there. I split its eight commands by concern. Three moved into new modules: custom-format (region/buffer reformat), custom-counts (word and character counts), and custom-text-transform (fraction glyphs). The other three went to existing homes: the previous-buffer toggle to custom-buffer-file, the delimiter jump to custom-line-paragraph, and the align-regexp space advice with its alignment and fill bindings to custom-whitespace. The C-; bindings, which-key labels, and the six test files moved with their functions, and custom-misc.el is deleted. No behavior change: every command keeps its name and its C-; key.
Diffstat (limited to 'modules')
-rw-r--r--modules/custom-buffer-file.el9
-rw-r--r--modules/custom-counts.el63
-rw-r--r--modules/custom-format.el46
-rw-r--r--modules/custom-line-paragraph.el31
-rw-r--r--modules/custom-misc.el196
-rw-r--r--modules/custom-text-transform.el63
-rw-r--r--modules/custom-whitespace.el16
7 files changed, 228 insertions, 196 deletions
diff --git a/modules/custom-buffer-file.el b/modules/custom-buffer-file.el
index b10ecd168..261956f37 100644
--- a/modules/custom-buffer-file.el
+++ b/modules/custom-buffer-file.el
@@ -574,5 +574,14 @@ Signals an error if:
"C-; b <down>" "resize divider down"))
+;; --- previous-buffer toggle (formerly in custom-misc.el) ---
+(defun cj/switch-to-previous-buffer ()
+ "Switch to previously open buffer.
+Repeated invocations toggle between the two most recently open buffers."
+ (interactive)
+ (switch-to-buffer (other-buffer (current-buffer) 1)))
+
+(cj/register-command "SPC" #'cj/switch-to-previous-buffer "prev buffer")
+
(provide 'custom-buffer-file)
;;; custom-buffer-file.el ends here.
diff --git a/modules/custom-counts.el b/modules/custom-counts.el
new file mode 100644
index 000000000..792732a40
--- /dev/null
+++ b/modules/custom-counts.el
@@ -0,0 +1,63 @@
+;;; custom-counts.el --- Word and character counts -*- coding: utf-8; lexical-binding: t; -*-
+
+;;; Commentary:
+;;
+;; Layer: 2 (Core UX).
+;; Category: L.
+;; Load shape: eager.
+;; Eager reason: registers its C-; # w and C-; # c command bindings at load.
+;; Top-level side effects: binds the count commands under C-; # w and C-; # c.
+;; Runtime requires: keybindings.
+;; Direct test load: yes (requires keybindings explicitly).
+;;
+;; Count words or characters in the active region, or the whole buffer when no
+;; region is active, and report the total in the minibuffer. Split out of the
+;; former custom-misc.el grab-bag.
+
+;;; Code:
+
+(require 'keybindings) ;; provides cj/register-command
+
+(defun cj/--count-words (start end)
+ "Internal implementation: Count words between START and END.
+START and END define the region to count.
+Returns the word count as an integer."
+ (when (> start end)
+ (error "Invalid region: start (%d) is greater than end (%d)" start end))
+ (count-words start end))
+
+(defun cj/count-words-buffer-or-region ()
+ "Count the number of words in the buffer or region.
+Display the result in the minibuffer."
+ (interactive)
+ (let* ((use-region (use-region-p))
+ (begin (if use-region (region-beginning) (point-min)))
+ (end (if use-region (region-end) (point-max)))
+ (area-type (if use-region "the region" "the buffer"))
+ (word-count (cj/--count-words begin end)))
+ (message "There are %d words in %s." word-count area-type)))
+
+(defun cj/--count-characters (start end)
+ "Internal implementation: Count characters between START and END.
+START and END define the region to count.
+Returns the character count as an integer."
+ (when (> start end)
+ (error "Invalid region: start (%d) is greater than end (%d)" start end))
+ (- end start))
+
+(defun cj/count-characters-buffer-or-region ()
+ "Count the number of characters in the buffer or region.
+Display the result in the minibuffer."
+ (interactive)
+ (let* ((use-region (use-region-p))
+ (begin (if use-region (region-beginning) (point-min)))
+ (end (if use-region (region-end) (point-max)))
+ (area-type (if use-region "the region" "the buffer"))
+ (char-count (cj/--count-characters begin end)))
+ (message "There are %d characters in %s." char-count area-type)))
+
+(cj/register-command "# w" #'cj/count-words-buffer-or-region "count words")
+(cj/register-command "# c" #'cj/count-characters-buffer-or-region "count characters")
+
+(provide 'custom-counts)
+;;; custom-counts.el ends here
diff --git a/modules/custom-format.el b/modules/custom-format.el
new file mode 100644
index 000000000..47cd7d88d
--- /dev/null
+++ b/modules/custom-format.el
@@ -0,0 +1,46 @@
+;;; custom-format.el --- Region and buffer reformatting -*- coding: utf-8; lexical-binding: t; -*-
+
+;;; Commentary:
+;;
+;; Layer: 2 (Core UX).
+;; Category: L.
+;; Load shape: eager.
+;; Eager reason: registers its C-; f command binding at load.
+;; Top-level side effects: binds cj/format-region-or-buffer under C-; f.
+;; Runtime requires: keybindings.
+;; Direct test load: yes (requires keybindings explicitly).
+;;
+;; Reformat the active region, or the whole buffer when no region is active:
+;; untabify, reindent, and delete trailing whitespace. Split out of the
+;; former custom-misc.el grab-bag.
+
+;;; Code:
+
+(require 'keybindings) ;; provides cj/register-command
+
+(defun cj/--format-region (start end)
+ "Internal implementation: Reformat text between START and END.
+START and END define the region to operate on.
+Replaces tabs with spaces, reindents, and deletes trailing whitespace."
+ (when (> start end)
+ (error "Invalid region: start (%d) is greater than end (%d)" start end))
+ (save-excursion
+ (save-restriction
+ (narrow-to-region start end)
+ (untabify (point-min) (point-max))
+ (indent-region (point-min) (point-max))
+ (delete-trailing-whitespace (point-min) (point-max)))))
+
+(defun cj/format-region-or-buffer ()
+ "Reformat the region or the entire buffer.
+Replaces tabs with spaces, deletes trailing whitespace, and reindents."
+ (interactive)
+ (let ((start-pos (if (use-region-p) (region-beginning) (point-min)))
+ (end-pos (if (use-region-p) (region-end) (point-max))))
+ (cj/--format-region start-pos end-pos)
+ (message "Formatted %s" (if (use-region-p) "region" "buffer"))))
+
+(cj/register-command "f" #'cj/format-region-or-buffer "format buffer")
+
+(provide 'custom-format)
+;;; custom-format.el ends here
diff --git a/modules/custom-line-paragraph.el b/modules/custom-line-paragraph.el
index dd2999c4e..d29d4125b 100644
--- a/modules/custom-line-paragraph.el
+++ b/modules/custom-line-paragraph.el
@@ -166,5 +166,36 @@ If the line is empty or contains only whitespace, abort with a message."
"C-; l r" "remove matching"
"C-; l u" "underscore line"))
+;; --- delimiter jump (formerly in custom-misc.el) ---
+(defun cj/jump-to-matching-paren ()
+ "Jump to the matching delimiter if point is on (or just after) one.
+If not on a delimiter, show a message. Respects the current syntax table."
+ (interactive)
+ (let* ((ca (char-after))
+ (cb (char-before))
+ ;; Check if on opening paren
+ (open-p (and ca (eq (char-syntax ca) ?\()))
+ ;; Check if on or just after closing paren
+ (close-p (or (and ca (eq (char-syntax ca) ?\)))
+ (and cb (eq (char-syntax cb) ?\))))))
+ (cond
+ ;; Jump forward from opening
+ (open-p
+ (condition-case err
+ (forward-sexp)
+ (scan-error
+ (message "No matching delimiter: %s" (error-message-string err)))))
+ ;; Jump backward from closing
+ (close-p
+ (condition-case err
+ (backward-sexp)
+ (scan-error
+ (message "No matching delimiter: %s" (error-message-string err)))))
+ ;; Not on delimiter
+ (t
+ (message "Point is not on a delimiter.")))))
+
+(cj/register-command ")" #'cj/jump-to-matching-paren "jump to paren")
+
(provide 'custom-line-paragraph)
;;; custom-line-paragraph.el ends here.
diff --git a/modules/custom-misc.el b/modules/custom-misc.el
deleted file mode 100644
index 7e5e4f8d6..000000000
--- a/modules/custom-misc.el
+++ /dev/null
@@ -1,196 +0,0 @@
-;;; custom-misc.el --- Miscellaneous utility functions -*- coding: utf-8; lexical-binding: t; -*-
-
-;;; Commentary:
-;;
-;; Layer: 2 (Core UX).
-;; Category: L/C.
-;; Load shape: eager.
-;; Eager reason: registers its C-; command bindings and an align-regexp advice
-;; at load. Currently eager by init order; a deferral candidate for Phase 3/4.
-;; Top-level side effects: advises align-regexp; binds several commands directly
-;; under C-; (")", "f", "A", "SPC", "|", and others).
-;; Runtime requires: keybindings.
-;; Direct test load: yes (requires keybindings explicitly).
-;;
-;; This module provides various utility functions for text manipulation,
-;; formatting, and navigation. Features include:
-;; - Jump between matching delimiters
-;; - Format regions/buffers (untabify, reindent, remove trailing whitespace)
-;; - Word counting with region awareness
-;; - Fraction glyph conversion (¼ ↔ 1/4)
-;; - Force align-regexp to use spaces instead of tabs
-;;
-;; All functions are bound to the cj/custom-keymap for easy access.
-;;
-;;; Code:
-
-(require 'keybindings) ;; provides cj/custom-keymap
-
-(defun cj/jump-to-matching-paren ()
- "Jump to the matching delimiter if point is on (or just after) one.
-If not on a delimiter, show a message. Respects the current syntax table."
- (interactive)
- (let* ((ca (char-after))
- (cb (char-before))
- ;; Check if on opening paren
- (open-p (and ca (eq (char-syntax ca) ?\()))
- ;; Check if on or just after closing paren
- (close-p (or (and ca (eq (char-syntax ca) ?\)))
- (and cb (eq (char-syntax cb) ?\))))))
- (cond
- ;; Jump forward from opening
- (open-p
- (condition-case err
- (forward-sexp)
- (scan-error
- (message "No matching delimiter: %s" (error-message-string err)))))
- ;; Jump backward from closing
- (close-p
- (condition-case err
- (backward-sexp)
- (scan-error
- (message "No matching delimiter: %s" (error-message-string err)))))
- ;; Not on delimiter
- (t
- (message "Point is not on a delimiter.")))))
-
-
-(defun cj/--format-region (start end)
- "Internal implementation: Reformat text between START and END.
-START and END define the region to operate on.
-Replaces tabs with spaces, reindents, and deletes trailing whitespace."
- (when (> start end)
- (error "Invalid region: start (%d) is greater than end (%d)" start end))
- (save-excursion
- (save-restriction
- (narrow-to-region start end)
- (untabify (point-min) (point-max))
- (indent-region (point-min) (point-max))
- (delete-trailing-whitespace (point-min) (point-max)))))
-
-(defun cj/format-region-or-buffer ()
- "Reformat the region or the entire buffer.
-Replaces tabs with spaces, deletes trailing whitespace, and reindents."
- (interactive)
- (let ((start-pos (if (use-region-p) (region-beginning) (point-min)))
- (end-pos (if (use-region-p) (region-end) (point-max))))
- (cj/--format-region start-pos end-pos)
- (message "Formatted %s" (if (use-region-p) "region" "buffer"))))
-
-(defun cj/switch-to-previous-buffer ()
- "Switch to previously open buffer.
-Repeated invocations toggle between the two most recently open buffers."
- (interactive)
- (switch-to-buffer (other-buffer (current-buffer) 1)))
-
-(defun cj/--count-words (start end)
- "Internal implementation: Count words between START and END.
-START and END define the region to count.
-Returns the word count as an integer."
- (when (> start end)
- (error "Invalid region: start (%d) is greater than end (%d)" start end))
- (count-words start end))
-
-(defun cj/count-words-buffer-or-region ()
- "Count the number of words in the buffer or region.
-Display the result in the minibuffer."
- (interactive)
- (let* ((use-region (use-region-p))
- (begin (if use-region (region-beginning) (point-min)))
- (end (if use-region (region-end) (point-max)))
- (area-type (if use-region "the region" "the buffer"))
- (word-count (cj/--count-words begin end)))
- (message "There are %d words in %s." word-count area-type)))
-
-(defun cj/--count-characters (start end)
- "Internal implementation: Count characters between START and END.
-START and END define the region to count.
-Returns the character count as an integer."
- (when (> start end)
- (error "Invalid region: start (%d) is greater than end (%d)" start end))
- (- end start))
-
-(defun cj/count-characters-buffer-or-region ()
- "Count the number of characters in the buffer or region.
-Display the result in the minibuffer."
- (interactive)
- (let* ((use-region (use-region-p))
- (begin (if use-region (region-beginning) (point-min)))
- (end (if use-region (region-end) (point-max)))
- (area-type (if use-region "the region" "the buffer"))
- (char-count (cj/--count-characters begin end)))
- (message "There are %d characters in %s." char-count area-type)))
-
-
-(defun cj/--replace-fraction-glyphs (start end to-glyphs)
- "Internal implementation: Replace fraction glyphs or text between START and END.
-START and END define the region to operate on.
-TO-GLYPHS when non-nil converts text (1/4) to glyphs (¼),
-otherwise converts glyphs to text."
- (when (> start end)
- (error "Invalid region: start (%d) is greater than end (%d)" start end))
- (let ((replacements (if to-glyphs
- '(("1/4" . "¼")
- ("1/2" . "½")
- ("3/4" . "¾")
- ("1/3" . "⅓")
- ("2/3" . "⅔"))
- '(("¼" . "1/4")
- ("½" . "1/2")
- ("¾" . "3/4")
- ("⅓" . "1/3")
- ("⅔" . "2/3"))))
- (count 0)
- (end-marker (copy-marker end)))
- (save-excursion
- (dolist (r replacements)
- (goto-char start)
- (while (search-forward (car r) end-marker t)
- (replace-match (cdr r))
- (setq count (1+ count)))))
- count))
-
-(defun cj/replace-fraction-glyphs (start end)
- "Replace common fraction glyphs between START and END.
-Operate on the buffer or region designated by START and END.
-Replace the text representations with glyphs when called with a
-\\[universal-argument] prefix."
- (interactive (if (use-region-p)
- (list (region-beginning) (region-end))
- (list (point-min) (point-max))))
- (let ((count (cj/--replace-fraction-glyphs start end current-prefix-arg)))
- (message "Replaced %d fraction%s" count (if (= count 1) "" "s"))))
-
-(defun cj/align-regexp-with-spaces (orig-fun &rest args)
- "Call ORIG-FUN with ARGS while temporarily disabling tabs for alignment.
-This advice ensures =align-regexp' uses spaces by binding =indent-tabs-mode'
-to nil."
- (let ((indent-tabs-mode nil))
- (apply orig-fun args)))
-
-;; avoid double advice stacking in case the file is reloaded
-(advice-remove 'align-regexp #'cj/align-regexp-with-spaces)
-(advice-add 'align-regexp :around #'cj/align-regexp-with-spaces)
-
-(cj/register-command ")" #'cj/jump-to-matching-paren)
-(cj/register-command "f" #'cj/format-region-or-buffer)
-(cj/register-command "# w" #'cj/count-words-buffer-or-region)
-(cj/register-command "# c" #'cj/count-characters-buffer-or-region)
-(cj/register-command "/" #'cj/replace-fraction-glyphs)
-(cj/register-command "A" #'align-regexp)
-(cj/register-command "SPC" #'cj/switch-to-previous-buffer)
-(cj/register-command "|" #'display-fill-column-indicator-mode)
-
-(with-eval-after-load 'which-key
- (which-key-add-key-based-replacements
- "C-; )" "jump to paren"
- "C-; f" "format buffer"
- "C-; # w" "count words"
- "C-; # c" "count characters"
- "C-; /" "fraction glyphs"
- "C-; A" "align regexp"
- "C-; SPC" "prev buffer"
- "C-; |" "fill column"))
-
-(provide 'custom-misc)
-;;; custom-misc.el ends here
diff --git a/modules/custom-text-transform.el b/modules/custom-text-transform.el
new file mode 100644
index 000000000..537f8df21
--- /dev/null
+++ b/modules/custom-text-transform.el
@@ -0,0 +1,63 @@
+;;; custom-text-transform.el --- Text glyph transforms -*- coding: utf-8; lexical-binding: t; -*-
+
+;;; Commentary:
+;;
+;; Layer: 2 (Core UX).
+;; Category: L.
+;; Load shape: eager.
+;; Eager reason: registers its C-; / command binding at load.
+;; Top-level side effects: binds cj/replace-fraction-glyphs under C-; /.
+;; Runtime requires: keybindings.
+;; Direct test load: yes (requires keybindings explicitly).
+;;
+;; Convert between text fractions (1/4) and their Unicode glyphs (1/4 becomes
+;; the vulgar-fraction character), over the region or whole buffer. Split out
+;; of the former custom-misc.el grab-bag.
+
+;;; Code:
+
+(require 'keybindings) ;; provides cj/register-command
+
+(defun cj/--replace-fraction-glyphs (start end to-glyphs)
+ "Internal implementation: Replace fraction glyphs or text between START and END.
+START and END define the region to operate on.
+TO-GLYPHS when non-nil converts text (1/4) to glyphs (¼),
+otherwise converts glyphs to text."
+ (when (> start end)
+ (error "Invalid region: start (%d) is greater than end (%d)" start end))
+ (let ((replacements (if to-glyphs
+ '(("1/4" . "¼")
+ ("1/2" . "½")
+ ("3/4" . "¾")
+ ("1/3" . "⅓")
+ ("2/3" . "⅔"))
+ '(("¼" . "1/4")
+ ("½" . "1/2")
+ ("¾" . "3/4")
+ ("⅓" . "1/3")
+ ("⅔" . "2/3"))))
+ (count 0)
+ (end-marker (copy-marker end)))
+ (save-excursion
+ (dolist (r replacements)
+ (goto-char start)
+ (while (search-forward (car r) end-marker t)
+ (replace-match (cdr r))
+ (setq count (1+ count)))))
+ count))
+
+(defun cj/replace-fraction-glyphs (start end)
+ "Replace common fraction glyphs between START and END.
+Operate on the buffer or region designated by START and END.
+Replace the text representations with glyphs when called with a
+\\[universal-argument] prefix."
+ (interactive (if (use-region-p)
+ (list (region-beginning) (region-end))
+ (list (point-min) (point-max))))
+ (let ((count (cj/--replace-fraction-glyphs start end current-prefix-arg)))
+ (message "Replaced %d fraction%s" count (if (= count 1) "" "s"))))
+
+(cj/register-command "/" #'cj/replace-fraction-glyphs "fraction glyphs")
+
+(provide 'custom-text-transform)
+;;; custom-text-transform.el ends here
diff --git a/modules/custom-whitespace.el b/modules/custom-whitespace.el
index cbf3eff12..52cc4e54d 100644
--- a/modules/custom-whitespace.el
+++ b/modules/custom-whitespace.el
@@ -228,5 +228,21 @@ Operate on the active region designated by START and END."
"C-; w t" "untabify"
"C-; w T" "tabify"))
+;; --- align-regexp space enforcement + alignment/fill bindings ---
+;; (formerly in custom-misc.el)
+(defun cj/align-regexp-with-spaces (orig-fun &rest args)
+ "Call ORIG-FUN with ARGS while temporarily disabling tabs for alignment.
+This advice ensures =align-regexp' uses spaces by binding =indent-tabs-mode'
+to nil."
+ (let ((indent-tabs-mode nil))
+ (apply orig-fun args)))
+
+;; avoid double advice stacking in case the file is reloaded
+(advice-remove 'align-regexp #'cj/align-regexp-with-spaces)
+(advice-add 'align-regexp :around #'cj/align-regexp-with-spaces)
+
+(cj/register-command "A" #'align-regexp "align regexp")
+(cj/register-command "|" #'display-fill-column-indicator-mode "fill column")
+
(provide 'custom-whitespace)
;;; custom-whitespace.el ends here.