blob: 2af9c2444613fe005236983b1be8bddda3cee48a (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
|
;;; custom-misc.el --- Miscellaneous utility functions -*- coding: utf-8; lexical-binding: t; -*-
;;; Commentary:
;;
;; 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:
;; cj/custom-keymap defined in keybindings.el
(eval-when-compile (defvar 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/--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)
(keymap-set cj/custom-keymap ")" #'cj/jump-to-matching-paren)
(keymap-set cj/custom-keymap "f" #'cj/format-region-or-buffer)
(keymap-set cj/custom-keymap "W" #'cj/count-words-buffer-or-region)
(keymap-set cj/custom-keymap "/" #'cj/replace-fraction-glyphs)
(keymap-set cj/custom-keymap "A" #'align-regexp)
(keymap-set cj/custom-keymap "SPC" #'cj/switch-to-previous-buffer)
(keymap-set cj/custom-keymap "|" #'display-fill-column-indicator-mode)
(provide 'custom-misc)
;;; custom-misc.el ends here
|