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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
|
;;; custom-functions.el --- My Custom Functions and Keymaps -*- lexical-binding: t; -*-
;;; Commentary:
;;
;;These are custom utility functions which I use frequently. They are bound to a
;;personal keymap with a prefix of "C-;" created at the end of this file.
;;; Code:
(use-package subr-x
:ensure nil) ;; built-in
(use-package expand-region
:demand t)
;; ------------------------ Jump To Matching Parentheses -----------------------
;; shows you the other matching parenthesis by jumping to it.
(defun cj/jump-to-matching-paren ()
"If on a parenthesis, jump to it's match. Otherwise, complain."
(interactive)
(cond ((looking-at "\\s\(\\|\\s\{\\|\\s\[")
(forward-list))
((looking-back "\\s\)\\|\\s\}\\|\\s\\]")
(backward-list))
(t (message "Cursor doesn't follow parenthesis, so there's no match."))))
;; ---------------------------- Join Line Or Region ----------------------------
;; joins all selected lines and fixes up the whitespace.
(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
(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))
;; ------------------------------- Join Paragraph ------------------------------
;; expands the region to the paragraph, then joins lines and fixes whitespace.
(defun cj/join-paragraph ()
"Mark all text in a paragraph then run cj/join-line-or-region."
(interactive)
(er/mark-paragraph) ;; from package expand region
(cj/join-line-or-region (region-beginning)(region-end))
(forward-line))
;; ---------------------- Count Words In Buffer Or Region ----------------------
;; minibuffer messages the number of words in the buffer (or region if selected).
(defun cj/count-words-buffer-or-region ()
"Count the number of words in buffer or region.
Displays result as a message in the minibuffer and *Messasges* buffer."
(interactive)
(let ((begin (point-min))
(end (point-max))
(area_type "the buffer"))
(when mark-active
(setq begin (region-beginning)
end (region-end)
area_type "the region"))
(message (format "There are %d words in %s." (count-words begin end) area_type))))
;; -------------------------- Duplicate Line Or Region -------------------------
;; duplicates the current line on a new line below. With "C-u" the new line's
;; commented. when a region is selected, the whole region is duplicated.
(defun cj/duplicate-line-or-region (&optional comment)
"Duplicate the line or region below.
Comment the duplicated line if prefix argument COMMENT is passed."
(interactive "P")
(let* ((b (if (region-active-p) (region-beginning) (line-beginning-position)))
(e (if (region-active-p) (region-end) (line-end-position)))
(lines (split-string (buffer-substring-no-properties b e) "\n")))
(save-excursion
(goto-char e)
(dolist (line lines)
(open-line 1)
(forward-line 1)
(insert line)
;; If the COMMENT prefix argument is non-nil, comment the inserted text
(when comment
(comment-region (line-beginning-position) (line-end-position)))))))
;; ---------------- Remove Duplicate Lines From Region Or Buffer ---------------
;; removes all duplicate lines from the region or buffer
(defun cj/remove-duplicate-lines-from-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"))))
;; -------------------------- Format Region Or Buffer --------------------------
;; reindent, untabify, and delete trailing whitespace across region or buffer
(defun cj/format-region-or-buffer ()
"Reformat the region or the entire buffer.
If a region is selected, delete trailing whitespace, then indent and untabify
the region. If no region is selected, perform the same actions across the
buffer."
(interactive)
(let (start-pos end-pos)
(if (use-region-p)
(progn
(setq start-pos (region-beginning))
(setq end-pos (region-end)))
(setq start-pos (point-min))
(setq end-pos (point-max)))
(save-excursion
(delete-trailing-whitespace start-pos end-pos)
(indent-region start-pos end-pos nil)
(untabify start-pos end-pos))))
;; ------------------- Remove Leading And Trailing Whitespace ------------------
;; removes leading and trailing whitespace on line, region, or buffer.
(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 "")))))
;; --------------------------- Arrayify / Unarrayify ---------------------------
;; unquoted text on newlines to quoted comma separated strings (and vice-versa).
(defun cj/arrayify (start end quote)
"Turn unquoted text on newlines into quoted comma-separated strings.
START and END indicate the region selected.
QUOTE is the characters used for quotations (i.e, \=' or \")"
(interactive "r\nMQuotation character to use for array element: ")
(let ((insertion
(mapconcat
(lambda (x) (format "%s%s%s" quote x quote))
(split-string (buffer-substring start end)) ", ")))
(delete-region start end)
(insert insertion)))
(defun cj/unarrayify (start end)
"Turn quoted comma-separated strings into unquoted text on newlines.
START and END indicate the region selected."
(interactive "r")
(let ((insertion
(mapconcat
(lambda (x) (replace-regexp-in-string "[\"']" "" x))
(split-string (buffer-substring start end) ", ") "\n")))
(delete-region start end)
(insert insertion)))
;; ----------------------- Comma Separated Text To Lines -----------------------
;; like arrayify, just without the quotes
(defun cj/comma-separated-text-to-lines ()
"Breaks up text between commas in a region and places each text on its own line."
(interactive)
(if (not (region-active-p))
(error "No region selected"))
(let ((beg (region-beginning))
(end (region-end))
(text (buffer-substring-no-properties (region-beginning) (region-end))))
(with-temp-buffer
(insert text)
(goto-char (point-min))
(while (search-forward "," nil t)
(replace-match "\n" nil t))
(delete-trailing-whitespace)
(setq text (buffer-string)))
(delete-region beg end)
(goto-char beg)
(insert text)))
;; ----------------------- Alphabetize And Replace Region ----------------------
;; sorts selected words into alphabetical order, then replaces the region.
(defun cj/alphabetize-and-replace-region ()
"Alphabetize strings (words/tokens) in region replacing the original region.
The result will be comma separated."
(interactive)
(let ((start (region-beginning))
(end (region-end))
(string (buffer-substring-no-properties (region-beginning) (region-end))))
(delete-region start end)
(goto-char start)
(insert
(mapconcat #'identity
(sort (split-string string "[[:space:],]+" t)
#'string-lessp)
", "))))
;; --------------------- Wrap Region As Markdown Code Block --------------------
;; wrap the selection in triple backslash and indicate the language for markdown
(defun cj/wrap-region-as-code-span (start end)
"Wraps the region between START and END with triple backticks and descriptor.
Triple backicks are often used to indicate a code-span block in markdown.
User is prompted for the optional descriptor."
(interactive "r")
(let ((lang (read-string "Descriptor (e.g., code, bash, python): ")))
(save-excursion
(goto-char end)
(unless (bolp) (insert "\n"))
(insert "```\n")
(goto-char start)
(insert (concat "```" lang "\n")))))
;; ------------------------ Insert Around Word Or Region -----------------------
(defun cj/insert-around-word-or-region ()
"Prompt for a string, insert it before and after the word at point or selected region."
(interactive)
(let ((str (read-string "Enter a string: "))
(regionp (use-region-p)))
(save-excursion
(if regionp
(let ((beg (region-beginning))
(end (region-end)))
(goto-char end)
(insert str)
(goto-char beg)
(insert str))
(if (thing-at-point 'word)
(let ((bounds (bounds-of-thing-at-point 'word)))
(goto-char (cdr bounds))
(insert str)
(goto-char (car bounds))
(insert str))
(message "Can't insert around. No word at point and no region selected."))))))
(global-set-key (kbd "C-; i a") 'cj/insert-around-word-or-region)
;; ------------------------ Insert Around Word Or Region -----------------------
(defun cj/insert-around-word-or-region ()
"Prompt for a string, insert it before and after the word at point or selected region."
(interactive)
(let ((str (read-string "Enter a string: "))
(regionp (use-region-p)))
(save-excursion
(if regionp
(let ((beg (region-beginning))
(end (region-end)))
(goto-char end)
(insert str)
(goto-char beg)
(insert str))
(if (thing-at-point 'word)
(let ((bounds (bounds-of-thing-at-point 'word)))
(goto-char (cdr bounds))
(insert str)
(goto-char (car bounds))
(insert str))
(message "Can't insert around. No word at point and no region selected."))))))
;; -------------------- Append To Lines In Region Or Buffer --------------------
;; append characters to the end of all lines in the region or the whole buffer.
(defun cj/append-to-lines-in-region-or-buffer (str)
"Prompt for STR and append it to the end of each line in region or buffer."
(interactive "sEnter string to append: ")
(let ((start-pos (if (use-region-p)
(region-beginning)
(point-min)))
(end-pos (if (use-region-p)
(region-end)
(point-max))))
(save-excursion
(goto-char start-pos)
(while (< (point) end-pos)
(move-end-of-line 1)
(insert str)
(forward-line 1)))))
;; ------------------------------ Hyphenate Region -----------------------------
;; hyphenates any empty space in a region; complains if there's no Region
(defun cj/hyphenate-region (start end)
"Hyphenate all continuous whitespace in the region.
START and END represent the region selected."
(interactive "*r")
(if (use-region-p)
(save-excursion
(save-restriction
(narrow-to-region start end)
(goto-char (point-min))
(while (re-search-forward "[ \t\n\r]+" nil t)
(replace-match "-"))))
(message "No region; nothing to hyphenate.")))
;; ----------------------------- Title Case Region -----------------------------
;; a literate version of capitalize region for prose text.
(defun cj/title-case-region ()
"Capitalize the region in title case format.
Title case is a capitalization convention where major words
are capitalized,and most minor words are lowercase. Nouns,
verbs (including linking verbs), adjectives, adverbs,pronouns,
and all words of four letters or more are considered major words.
Short (i.e., three letters or fewer) conjunctions, short prepositions,
and all articles are considered minor words."
(interactive)
(let ((beg nil)
(end nil)
(prev-word-end nil)
;; Allow capitals for skip characters after this, so:
;; Warning: An Example
;; Capitalizes the `An'.
(chars-skip-reset '(?: ?! ??))
;; Don't capitalize characters directly after these. e.g.
;; "Foo-bar" or "Foo\bar" or "Foo's".
(chars-separator '(?\\ ?- ?' ?.))
(word-chars "[:alnum:]")
(word-skip
(list "a" "an" "and" "as" "at" "but" "by"
"for" "if" "in" "is" "nor" "of"
"on" "or" "so" "the" "to" "yet"))
(is-first t))
(cond
((region-active-p)
(setq beg (region-beginning))
(setq end (region-end)))
(t
(setq beg (line-beginning-position))
(setq end (line-end-position))))
(save-excursion
;; work on uppercased text (e.g., headlines) by downcasing first
(downcase-region beg end)
(goto-char beg)
(while (< (point) end)
(setq prev-word-end (point))
(skip-chars-forward (concat "^" word-chars) end)
(let ((word-end
(save-excursion
(skip-chars-forward word-chars end)
(point))))
(unless (memq (char-before (point)) chars-separator)
(let* ((c-orig (char-to-string (char-after (point))))
(c-up (capitalize c-orig)))
(unless (string-equal c-orig c-up)
(let ((word (buffer-substring-no-properties (point) word-end)))
(when
(or
;; Always allow capitalization.
is-first
;; If it's not a skip word, allow.
(not (member word word-skip))
;; Check the beginning of the previous word doesn't reset first.
(save-excursion
(and
(not (zerop (skip-chars-backward "[:blank:]" prev-word-end)))
(memq (char-before (point)) chars-skip-reset))))
(delete-region (point) (1+ (point)))
(insert c-up))))))
(goto-char word-end)
(setq is-first nil))))))
;; replace the capitalize-region keybinding to call title-case
(global-set-key [remap capitalize-region] 'cj/title-case-region)
;; --------------------------- Buffer Strip Control M --------------------------
;; remove windows carriage return control characters from the buffer
(defun buffer-strip-ctrl-m ()
"Remove ^M from the current buffer."
(interactive)
(save-excursion
(goto-char (point-min))
(while (search-forward "^M" nil t)
(replace-match "" nil t))))
;; ------------------------------ Insert Date Time -----------------------------
;; insert a sortable or a readable datestamp or timestamp
(defvar readable-date-time-format "%A, %B %d, %Y at %I:%M:%S %p %Z "
"Format of date to insert with `insert-readable-date-time' func.
See help of `format-time-string' for possible replacements")
(defun cj/insert-readable-date-time ()
"Insert the current date and time into current buffer.
Uses `readable-date-time-format' for the formatting the date/time."
(interactive)
(insert (format-time-string readable-date-time-format (current-time))))
(defvar sortable-date-time-format "%Y-%m-%d %a @ %H:%M:%S %z "
"Format of date to insert with `insert-current-date-time' func.
See help of `format-time-string' for possible replacements")
(defun cj/insert-sortable-date-time ()
"Insert the current date and time into current buffer.
Uses `sortable-date-time-format' for the formatting the date/time."
(interactive)
(insert (format-time-string sortable-date-time-format (current-time))))
(defvar sortable-time-format "%I:%M:%S %p %Z "
"Time format to insert with `insert-current-time' func.
See help of `format-time-string' for possible replacements")
(defun cj/insert-sortable-time ()
"Insert the current time into current buffer.
Uses `sortable-time-format' for the formatting the date/time."
(interactive)
(insert (format-time-string sortable-time-format (current-time))))
(defvar sortable-date-format "%Y-%m-%d %a"
"Time format to insert with `insert-current-time' func.
See help of `format-time-string' for possible replacements")
(defun cj/insert-sortable-date ()
"Insert the current time into current buffer.
Uses `sortable-time-format' for the formatting the date/time."
(interactive)
(insert (format-time-string sortable-date-format (current-time))))
;; -------------------------- Copy Link To Source File -------------------------
;; find the source file for the current buffer and place it's URL in the clipboard
(defun cj/copy-link-to-source-file ()
"Copy the full file:// path of the underlying source file to the kill ring."
(interactive)
(let ((file-path (buffer-file-name)))
(when file-path
(setq file-path (concat "file://" file-path))
(kill-new file-path)
(message "Copied file link to kill ring: %s" file-path))))
;; ------------------------- Buffer And File Operations ------------------------
;; move, rename, or delete the underlying source file for the current buffer.
;; MOVE BUFFER + FILE
(defun cj/move-buffer-and-file (dir)
"Move both current buffer and the file it visits to DIR."
(interactive "DMove buffer and file (to new directory): ")
(let* ((name (buffer-name))
(filename (buffer-file-name))
(dir
(if (string-match dir "\\(?:/\\|\\\\)$")
(substring dir 0 -1) dir))
(newname (concat dir "/" name)))
(if (not filename)
(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)
"Rename both current buffer and the file it visits to NEW-NAME."
(interactive
(list (read-string "Rename buffer and file (to new name): "
(file-name-nondirectory (buffer-file-name)))))
(let ((name (buffer-name))
(filename (buffer-file-name)))
(if (not filename)
(message "Buffer '%s' is not visiting a file!" name)
(if (get-buffer new-name)
(message "A buffer named '%s' already exists!" new-name)
(progn
(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)
;; DELETE BUFFER + FILE
(defun cj/delete-buffer-and-file ()
"Kill the current buffer and delete the file it visits."
(interactive)
(let ((filename (buffer-file-name)))
(when filename
(if (vc-backend filename)
(vc-delete-file filename)
(progn
(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).
;; Stolen from `diary.el' (`diary-ordinal-suffix').
(defun ordinal-suffix (n)
"Ordinal suffix for N. That is, `st', `nd', `rd', or `th', as appropriate."
(if (or (memq (% n 100) '(11 12 13)) (< 3 (% n 10)))
"th"
(aref ["th" "st" "nd" "rd"] (% n 10))))
;; -------------------------------- Align-Regexp -------------------------------
;; the built-in align regexp shouldn't use tabs
(defadvice align-regexp (around align-regexp-with-spaces activate)
"Avoid tabs when aligning text."
(let ((indent-tabs-mode nil))
ad-do-it))
;; ----------------------------- Merge List To List ----------------------------
;; Convenience method for merging two lists together
;; https://emacs.stackexchange.com/questions/38008/adding-many-items-to-a-list/68048#68048
(defun cj/merge-list-to-list (dst src)
"Merge content of the 2nd list SRC with the 1st one DST."
(set dst
(append (eval dst) src)))
;; ------------------------------ Personal Keymap ------------------------------
;; a keymap to use the above functions. prefix key: "C-;"
(global-unset-key (kbd "C-;"))
(defvar personal-keymap
(let ((map (make-sparse-keymap)))
;; un/arrayify
(define-key map "a" 'cj/arrayify)
(define-key map "A" 'cj/unarrayify)
;; de/duplicate lines
(define-key map "d" 'cj/duplicate-line-or-region)
(define-key map "D" 'cj/remove-duplicate-lines-from-region-or-buffer)
(define-key map ")" #'cj/jump-to-matching-paren)
(define-key map "-" #'cj/hyphenate-region)
(define-key map "U" 'upcase-region)
(define-key map "w" 'cj/remove-leading-trailing-whitespace)
(define-key map "W" 'fixup-whitespace)
(define-key map "#" 'cj/count-words-buffer-or-region)
(define-key map "1" 'cj/alphabetize-and-replace-region)
(define-key map "C" 'display-fill-column-indicator-mode)
(define-key map "J" 'cj/join-paragraph)
(define-key map "f" 'cj/format-region-or-buffer)
(define-key map "j" 'cj/join-line-or-region)
(define-key map "l" 'downcase-dwim)
(define-key map "p" 'cj/append-to-lines-in-region-or-buffer)
(define-key map "r" 'align-regexp)
(define-key map "u" 'cj/title-case-region)
(define-key map "c" 'cj/wrap-region-as-code-span)
map)
"My personal key map.")
(global-set-key (kbd "C-;") personal-keymap)
;; timestamp insertion
(global-set-key (kbd "C-; i h") 'cj/insert-readable-date-time)
(global-set-key (kbd "C-; i s") 'cj/insert-sortable-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/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
(global-set-key (kbd "C-; b l") 'cj/copy-link-to-source-file)
;; insert around
(global-set-key (kbd "C-; i a") 'cj/insert-around-word-or-region)
(provide 'custom-functions)
;;; custom-functions.el ends here.
|