diff options
| author | Craig Jennings <c@cjennings.net> | 2025-09-06 22:46:35 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-09-06 22:46:35 -0500 |
| commit | 5cd5a9cadf5624be581d40df6e41fc32a8220c84 (patch) | |
| tree | 4f9214a269671233b02d988378bcc4e453bf0e1f | |
| parent | a5cf1161703b6fb7578655c25275ec5e8ca17795 (diff) | |
| download | dotemacs-5cd5a9cadf5624be581d40df6e41fc32a8220c84.tar.gz dotemacs-5cd5a9cadf5624be581d40df6e41fc32a8220c84.zip | |
refactor: Improve whitespace and region handling in custom functions
Refactors several custom functions to better handle regions and
improve code quality:
- Make `cj/fraction-glyph-toggle` automatically detect region or buffer
- Rewrite `cj/remove-leading-trailing-whitespace` for more consistent behavior
- Improve `cj/collapse-whitespace-line-or-region` with better tab handling
- Update `cj/join-line-or-region` and `cj/remove-duplicate-lines-region-or-buffer` to work without
explicit region parameters
- Makes minor improvements to string handling in other functions
| -rw-r--r-- | modules/custom-functions.el | 191 |
1 files changed, 96 insertions, 95 deletions
diff --git a/modules/custom-functions.el b/modules/custom-functions.el index 04be6c19..6ae4ad2f 100644 --- a/modules/custom-functions.el +++ b/modules/custom-functions.el @@ -117,23 +117,25 @@ Displays result as a message in the minibuffer and *Messasges* buffer." "Replace common fraction glyphs (½) with their text format (1/2). Operates in the buffer or region (as identified with START and END) if selected. Replaces the text versions with the glyphs if function prefaced by 'C-u'." - (interactive "r") + (interactive (if (use-region-p) + (list (region-beginning) (region-end)) + (list (point-min) (point-max)))) (let ((replacements (if current-prefix-arg - '(("1/4" . "¼") - ("1/2" . "½") - ("3/4" . "¾") - ("1/3" . "⅓") - ("2/3" . "⅔")) - '(("¼" . "1/4") - ("½" . "1/2") - ("¾" . "3/4") - ("⅓" . "1/3") - ("⅔" . "2/3"))))) - (save-excursion - (dolist (r replacements) - (goto-char start) - (while (search-forward (car r) end t) - (replace-match (cdr r))))))) + '(("1/4" . "¼") + ("1/2" . "½") + ("3/4" . "¾") + ("1/3" . "⅓") + ("2/3" . "⅔")) + '(("¼" . "1/4") + ("½" . "1/2") + ("¾" . "3/4") + ("⅓" . "1/3") + ("⅔" . "2/3"))))) + (save-excursion + (dolist (r replacements) + (goto-char start) + (while (search-forward (car r) end t) + (replace-match (cdr r))))))) (defun cj/align-regexp-with-spaces (orig-fun &rest args) "Around-advice for =align-regexp' to disable tabs during alignment. @@ -253,51 +255,50 @@ This does not save the deleted text in the kill ring." ;;; ---------------------- Whitespace Operations And Keymap --------------------- -(defun cj/remove-leading-trailing-whitespace (start end) - "Remove leading and trailing whitespace in a region or buffer. -When called interactively, if a region is active, remove leading -and trailing spaces in the region. Else, remove from the current line. -If called with a prefix argument (C-u), remove throughout the entire buffer. -START and END define region." - (interactive "r") - (let (deactivate-mark) - (if (or (use-region-p) current-prefix-arg) - (save-restriction - (if current-prefix-arg - (progn (widen) (setq start (point-min) end (point-max))) - (narrow-to-region start end)) - (goto-char (point-min)) - (while (re-search-forward "^[ \t]+" nil t) (replace-match "")) - (goto-char (point-min)) - (while (re-search-forward "[ \t]+$" nil t) (replace-match ""))) - (beginning-of-line) - (while (looking-at "^[ \t]+") (replace-match "")) - (end-of-line) - (while (re-search-backward "[ \t]+$" (line-beginning-position) t) - (replace-match ""))))) - -(defun cj/collapse-whitespace-line-or-region (&optional region) - "Collapse whitespace to one space in the current line, or region if selected. -Ensure there is exactly one space between words, and remove leading and trailing -whitespace. When called with a prefix argument, it operates on the current -REGION." - (interactive "P") - (save-excursion - (let* ((beg (if region (region-beginning) (line-beginning-position))) - (end (if region (region-end) (line-end-position)))) +(defun cj/remove-leading-trailing-whitespace () + "Remove leading and trailing whitespace in a region, line, or buffer. +When called interactively: +- If a region is active, operate on the region +- If called with C-u prefix, operate on entire buffer +- Otherwise, operate on current line." + (interactive) + (let ((start (cond (current-prefix-arg (point-min)) + ((use-region-p) (region-beginning)) + (t (line-beginning-position)))) + (end (cond (current-prefix-arg (point-max)) + ((use-region-p) (region-end)) + (t (line-end-position))))) + (save-excursion (save-restriction - (narrow-to-region beg end) - ;; Replace all tabs with space - (goto-char (point-min)) - (replace-string "\t" " " nil beg end) - ;; Remove leading and trailing spaces + (narrow-to-region start end) (goto-char (point-min)) - (while (re-search-forward "^\\s-+\\|\\s-+$" nil t) - (replace-match "" nil nil)) - ;; Ensure only one space between words/symbols. + (while (re-search-forward "^[ \t]+" nil t) (replace-match "")) (goto-char (point-min)) - (while (re-search-forward "\\s-\\{2,\\}" nil t) - (replace-match " " nil nil)))))) + (while (re-search-forward "[ \t]+$" nil t) (replace-match "")))))) + +(defun cj/collapse-whitespace-line-or-region () + "Collapse whitespace to one space in the current line, or region if selected. +Ensure there is exactly one space between words, and remove leading and trailing +whitespace." + (interactive) + (save-excursion + (let* ((region-active (use-region-p)) + (beg (if region-active (region-beginning) (line-beginning-position))) + (end (if region-active (region-end) (line-end-position)))) + (save-restriction + (narrow-to-region beg end) + ;; Replace all tabs with space + (goto-char (point-min)) + (while (search-forward "\t" nil t) + (replace-match " " nil t)) + ;; Remove leading and trailing spaces + (goto-char (point-min)) + (while (re-search-forward "^\\s-+\\|\\s-+$" nil t) + (replace-match "" nil nil)) + ;; Ensure only one space between words/symbols + (goto-char (point-min)) + (while (re-search-forward "\\s-\\{2,\\}" nil t) + (replace-match " " nil nil)))))) (defun cj/delete-blank-lines-region-or-buffer (start end) "Delete all blank lines in the region between START and END. @@ -483,22 +484,21 @@ Uses `readable-time-format' for the formatting the date/time." ;;; ----------------------- Line And Paragraph Operations ----------------------- -(defun cj/join-line-or-region (beg end) - "Apply \='join-line\=' over the marked region or join with previous line. -Region indicated with BEG and END." - (interactive "r") - ;; in region - (if mark-active +(defun cj/join-line-or-region () + "Apply 'join-line' over the marked region or join with previous line." + (interactive) + (if (use-region-p) (let ((beg (region-beginning)) (end (copy-marker (region-end)))) (goto-char beg) - ;; apply join lines until point => end (while (< (point) end) (join-line 1)) (goto-char end) - (newline))) - ;; outside region - (join-line)(newline)) + (newline)) + ;; No region - just join with previous line + (join-line) + (newline))) + (defun cj/join-paragraph () "Mark all text in a paragraph then run cj/join-line-or-region." @@ -524,19 +524,19 @@ Comment the duplicated line if prefix argument COMMENT is passed." (when comment (comment-region (line-beginning-position) (line-end-position))))))) -(defun cj/remove-duplicate-lines-region-or-buffer (start end) - "Find duplicate lines in region START to END keeping the first occurrence. -If no region is selected, operate on the whole buffer." - (interactive "*r\nP") - (save-excursion - (unless (region-active-p) - (setq start (point-min) end (point-max))) - (setq end (copy-marker end)) - (while - (progn - (goto-char start) - (re-search-forward "^\\(.*\\)\n\\(\\(.*\n\\)*\\)\\1\n" end t)) - (replace-match "\\1\n\\2")))) +(defun cj/remove-duplicate-lines-region-or-buffer () + "Find duplicate lines in region or buffer keeping the first occurrence. +If a region is selected, operate on the region. Otherwise, operate on the whole buffer." + (interactive) + (let ((start (if (use-region-p) (region-beginning) (point-min))) + (end (if (use-region-p) (region-end) (point-max)))) + (save-excursion + (let ((end-marker (copy-marker end))) + (while + (progn + (goto-char start) + (re-search-forward "^\\(.*\\)\n\\(\\(.*\n\\)*\\)\\1\n" end-marker t)) + (replace-match "\\1\n\\2")))))) (defun cj/underscore-line () "Underline the current line by inserting a row of characters below it. @@ -635,10 +635,10 @@ mode's comment syntax at both the beginning and end of each line. The box respects the current indentation level and avoids trailing whitespace." (interactive) (let* ((comment-char (if (equal comment-start ";") ";;" - (string-trim comment-start))) + (string-trim comment-start))) (comment-end-char (if (string-empty-p comment-end) - comment-char - (string-trim comment-end))) + comment-char + (string-trim comment-end))) (line-char (if (equal comment-char ";;") "-" "#")) (comment (capitalize (string-trim (read-from-minibuffer "Comment: ")))) (comment-length (length comment)) @@ -646,17 +646,17 @@ respects the current indentation level and avoids trailing whitespace." (max-width (min fill-column 80)) ;; Calculate available width between comment markers (available-width (- max-width - current-column-pos - (length comment-char) - (length comment-end-char))) + current-column-pos + (length comment-char) + (length comment-end-char))) ;; Inner width is the width without the spaces after comment start and before comment end (inner-width (- available-width 2)) ;; Calculate padding for each side of the centered text (padding-each-side (max 1 (/ (- inner-width comment-length) 2))) ;; Adjust for odd-length comments (right-padding (if (= (% (- inner-width comment-length) 2) 0) - padding-each-side - (1+ padding-each-side)))) + padding-each-side + (1+ padding-each-side)))) ;; Check if we have enough space (if (< inner-width (+ comment-length 4)) ; minimum sensible width @@ -664,8 +664,8 @@ respects the current indentation level and avoids trailing whitespace." (progn ;; Top line - fill entirely with line characters except for space after comment start (insert comment-char) - (insert " ") - (dotimes (_ inner-width) (insert line-char)) + (insert " ") + (insert (make-string inner-width (string-to-char line-char))) (insert " ") (insert comment-end-char) (newline) @@ -748,9 +748,11 @@ START and END indicate the region selected." "Alphabetize strings (words/tokens) in region replacing the original region. The result will be comma separated." (interactive) + (unless (use-region-p) + (user-error "No region selected")) (let ((start (region-beginning)) - (end (region-end)) - (string (buffer-substring-no-properties (region-beginning) (region-end)))) + (end (region-end)) + (string (buffer-substring-no-properties (region-beginning) (region-end)))) (delete-region start end) (goto-char start) (insert @@ -827,8 +829,7 @@ and all articles are considered minor words." (setq end (line-end-position)))) (save-excursion ;; work on uppercased text (e.g., headlines) by downcasing first - (downcase-region beg end) - + (downcase-region beg end) (goto-char beg) (while (< (point) end) |
