summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-08-31 16:57:26 -0500
committerCraig Jennings <c@cjennings.net>2025-08-31 16:57:26 -0500
commite50e1e8c3c198dc75f3be913f6b4b1047880eeb6 (patch)
treef1eecaa2abf9b09249bda5629f1a0451208755f1 /modules
parent921cfcd8ce348046e67969e4c10c263f890ce3e2 (diff)
downloaddotemacs-e50e1e8c3c198dc75f3be913f6b4b1047880eeb6.tar.gz
dotemacs-e50e1e8c3c198dc75f3be913f6b4b1047880eeb6.zip
(refactor) custom functions
- Pull in custom commenting functions from prog-comments.el - categorize similar functions and add to proper key maps - better comments to explain functions - wrapped upcase and downcase functions to actually dwim - modified readable time format - removed strip-ctrl-m -- unused and didn't function correctly - removed wrap-region-as-code-span -- unused & redundant with other wrap funcs - renamed some functions to clarify purpose
Diffstat (limited to 'modules')
-rw-r--r--modules/custom-functions.el1178
-rw-r--r--modules/prog-comments.el135
2 files changed, 657 insertions, 656 deletions
diff --git a/modules/custom-functions.el b/modules/custom-functions.el
index 16b68c51..59a31ec2 100644
--- a/modules/custom-functions.el
+++ b/modules/custom-functions.el
@@ -3,142 +3,237 @@
;;; 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.
+;; These are custom utility functions I use frequently.
+;; For convenience, they are bound to a custom keymap with a prefix of "C-;".
+;; Additional keymaps are created on top of this prefix to collect similar operations.
+;;
+;; C-; --- Custom Key Map
+;; C-; ) → jump to matching parenthesis
+;; C-; f → re-formats region or buffer (delete trailing whitespace, reindent, and untabify).
+;; C-; c → copy the entire buffer to the kill ring
+;; C-; W → counts words in region or buffer displaying results in echo area.
+;; C-; / → replace common glyph fractions (½) to text (1/2) (text to glyph with C-u).
+;; C-; r → align text by regexp with spaces
+;; C-; | → toggle visibility of the fill-column indicator
+;;
+;; C-; b --- Buffer & File Operations
+;; C-; b m → move buffer and file to another directory
+;; C-; b r → rename buffer and its file simultaneously
+;; C-; b d → delete buffer and its file simultaneously
+;; C-; b l → copy file:// link of buffer’s source file
+;;
+;; C-; w --- Whitespace Operations
+;; C-; w r → remove leading/trailing whitespace from line or region (buffer with C-u).
+;; C-; w c → collapses runs of whitespace to one space.
+;; C-; w l → delete all blank lines in region or buffer
+;; C-; w h → hyphenate all whitespace in region
+;;
+;; C-; s --- Surround, Append & Prepend
+;; C-; s s → surround word or region with string
+;; C-; s a → append a string to each line
+;; C-; s p → prepend a string to each line
+;;
+;; C-; d --- Date/Time Insertion
+;; C-; d r → readable date and time : Sunday, August 31, 2025 at 04:07:02 PM CDT
+;; C-; d s → sortable date and time : 2025-08-31 Sun @ 16:07:30 -0500
+;; C-; d t → sortable time only : 04:07:50 PM CDT
+;; C-; d D → readable time only : 4:08 PM
+;; C-; d T → readable date only : Sunday, August 31, 2025
+;; C-; d d → sortable date only : 2025-08-31 Sun
+;;
+;; C-; l --- Line & Paragraph Operations
+;; C-; l j → join lines (or selected region of lines)
+;; C-; l J → join entire paragraph. guesses at the lines that constitute paragraph.
+;; C-; l d → duplicates the line or region
+;; C-; l r → remove duplicate lines from the buffer, keepinf the first occurrence.
+;; C-; l u → "underline" current line: repeat a chosen character to same length on line below.
+;;
+;; C-; m --- Comment Styling & Removal
+;; C-; m r → reformats selecton into a commented paragraph re-wrapping at fill column width.
+;; C-; m c → insert centered comment
+;; C-; m - → insert hyphen-style comment
+;; C-; m b → draw a comment box
+;; C-; m D → delete all comments in buffer
+;;
+;; C-; o --- Ordering & Sorting
+;; C-; o a → arrayify lines into quoted list
+;; C-; o u → unarrayify list into lines
+;; C-; o A → alphabetize items in region
+;; C-; o l → split comma-separated text onto lines
+;;
+;; C-; c --- Case-Change Operations
+;; C-; c t → Change selected text to Title Case : This is the Title of a Movie
+;; C-; c u → Change word or region to Upper Case : THIS IS THE TITLE OF A MOVIE
+;; C-; c d → Change word or region to Lower Case : this is the title of a movie
;;; Code:
-(use-package subr-x
- :ensure nil) ;; built-in
+(require 'subr-x)
+
(use-package expand-region
- :demand t)
+ :demand t) ;; used w/in join paragraph
+
+;;; ----------------- Miscellaneous Functions And Custom Keymap -----------------
+
+(defun cj/jump-to-matching-paren ()
+ "If point is 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."))))
+(defun cj/format-region-or-buffer ()
+ "Reformat the region or the entire buffer.
+Deletes all trailing whitespace, reindents the region, and replaces tabs with spaces."
+ (interactive)
+ (let ((start-pos (if (use-region-p) (region-beginning) (point-min)))
+ (end-pos (if (use-region-p) (region-end) (point-max))))
+ (save-excursion
+ (save-restriction
+ (narrow-to-region start-pos end-pos)
+ (delete-trailing-whitespace)
+ (indent-region (point-min) (point-max))
+ (untabify (point-min) (point-max))))))
-;; -------------------------------- Copy Buffer --------------------------------
-;; copies the contents of the buffer to the kill ring
+(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))))
-(defun cj/copy-buffer ()
+(defun cj/replace-fraction-glyphs (start end)
+ "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")
+ (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)))))))
+
+(defun cj/copy-whole-buffer ()
"Copy the entire contents of the current buffer to the kill ring.
Point and mark are left exactly where they were. No transient region
is created. A message is displayed when done."
(interactive)
(let ((contents (buffer-substring-no-properties (point-min) (point-max))))
(kill-new contents)
- (message "Buffer contents copied to kill ring")))
+ (message "Buffer contents copied to kill ring")))
-;; ------------------------ Jump To Matching Parentheses -----------------------
-;; shows you the other matching parenthesis by jumping to it.
+(defun cj/align-regexp-with-spaces (orig-fun &rest args)
+ "Around-advice for =align-regexp' to disable tabs during alignment.
+ORIG-FUN is the original =align-regexp'; ARGS are its arguments."
+ (let ((indent-tabs-mode nil))
+ (apply orig-fun args)))
-(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."))))
+(advice-remove 'align-regexp #'align-regexp-with-spaces) ; in case this is reloaded
+(advice-add 'align-regexp :around #'cj/align-regexp-with-spaces)
-;; ---------------------------- Join Line Or Region ----------------------------
-;; joins all selected lines and fixes up the whitespace.
+;; Must unbind Flyspell's 'C-;' keybinding before it's assigned to cj/custom-keymap
+(global-unset-key (kbd "C-;"))
+(eval-after-load "flyspell"
+ '(define-key flyspell-mode-map (kbd "C-;") nil))
-(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))
+(defvar cj/custom-keymap
+ (let ((map (make-sparse-keymap)))
+ (define-key map ")" 'cj/jump-to-matching-paren)
+ (define-key map "f" 'cj/format-region-or-buffer)
+ (define-key map "c" 'cj/copy-whole-buffer)
+ (define-key map "W" 'cj/count-words-buffer-or-region)
+ (define-key map "/" 'cj/replace-fraction-glyphs)
+ (define-key map "r" 'align-regexp)
+ (define-key map "|" 'display-fill-column-indicator-mode)
+ map)
+ "The base key map for custom elisp functions holding miscellaneous functions.
+Other key maps extend from this key map to hold categorized functions.")
+(global-set-key (kbd "C-;") cj/custom-keymap)
-;; ------------------------------- Join Paragraph ------------------------------
-;; expands the region to the paragraph, then joins lines and fixes whitespace.
+;;; ------------------- Buffer And File Operations And Keymap -------------------
-(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))
+(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))))
-;; ---------------------- Count Words In Buffer Or Region ----------------------
-;; minibuffer messages the number of words in the buffer (or region if selected).
+(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))))))
-(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."
+(defun cj/delete-buffer-and-file ()
+ "Kill the current buffer and delete the file it visits."
(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
+ (let ((filename (buffer-file-name)))
+ (when filename
+ (if (vc-backend filename)
+ (vc-delete-file filename)
(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
+ (delete-file filename t)
+ (message "Deleted file %s" filename)
+ (kill-buffer))))))
-(defun cj/format-region-or-buffer ()
- "Reformat the region or the entire buffer."
+(defun cj/copy-link-to-buffer-file ()
+ "Copy the full file:// path of the current buffer's source file to the kill ring."
(interactive)
- (let ((start-pos (if (use-region-p) (region-beginning) (point-min)))
- (end-pos (if (use-region-p) (region-end) (point-max))))
- (save-excursion
- (save-restriction
- (narrow-to-region start-pos end-pos)
- (delete-trailing-whitespace)
- (indent-region (point-min) (point-max))
- (untabify (point-min) (point-max))))))
+ (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))))
-;; ------------------- Remove Leading And Trailing Whitespace ------------------
-;; removes leading and trailing whitespace on line, region, or buffer.
+;; Buffer & file operations prefix and keymap
+(define-prefix-command 'cj/buffer-and-file-map nil
+ "Keymap for buffer-and-file operations.")
+(define-key cj/custom-keymap "b" 'cj/buffer-and-file-map)
+(define-key cj/buffer-and-file-map "m" 'cj/move-buffer-and-file)
+(define-key cj/buffer-and-file-map "r" 'cj/rename-buffer-and-file)
+(define-key cj/buffer-and-file-map "d" 'cj/delete-buffer-and-file)
+(define-key cj/buffer-and-file-map "l" 'cj/copy-link-to-buffer-file)
+
+;;; ---------------------- Whitespace Operations And Keymap ---------------------
(defun cj/remove-leading-trailing-whitespace (start end)
"Remove leading and trailing whitespace in a region or buffer.
@@ -163,96 +258,81 @@ START and END define region."
(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/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))))
+ (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
+ (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/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)))
+(defun cj/delete-blank-lines-region-or-buffer (start end)
+ "Delete all blank lines in the region between START and END.
+Blank lines contain nothing or only whitespace (spaces or tabs).
+If called with an active region, operate on that region.
+If no region is selected, prompt before operating on the whole buffer.
+Otherwise signal a user-error and do nothing. The point is restored
+to its original position after deletion."
+ (interactive
+ (if (use-region-p)
+ ;; grab its boundaries if there's a region
+ (list (region-beginning) (region-end))
+ ;; or ask if user intended operating on whole buffer
+ (if (yes-or-no-p "Delete blank lines in entire buffer? ")
+ (list (point-min) (point-max))
+ (user-error "Aborted"))))
+ (save-excursion
+ (save-restriction
+ (widen)
+ ;; Regexp "^[[:space:]]*$" matches lines of zero or more spaces/tabs.
+ (flush-lines "^[[:space:]]*$" start end)))
+ ;; Return nil (Emacs conventions). Point is already restored.
+ nil)
-;; ----------------------- Comma Separated Text To Lines -----------------------
-;; like arrayify, just without the quotes
+(defun cj/hyphenate-whitespace-in-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.")))
-(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")))))
+;; Whitespace operations prefix and keymap
+(define-prefix-command 'cj/whitespace-map nil
+ "Keymap for whitespace operations.")
+(define-key cj/custom-keymap "w" 'cj/whitespace-map)
+(define-key cj/whitespace-map "r" 'cj/remove-leading-trailing-whitespace)
+(define-key cj/whitespace-map "c" 'cj/collapse-whitespace-line-or-region)
+(define-key cj/whitespace-map "l" 'cj/delete-blank-lines-region-or-buffer)
+(define-key cj/whitespace-map "h" 'cj/hyphenate-whitespace-in-region)
-;; ------------------------ Insert Around Word Or Region -----------------------
+;;; ------------------------- Surround, Append, Prepend -------------------------
-(defun cj/insert-around-word-or-region ()
+(defun cj/surround-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: "))
+ (let ((str (read-string "Surround with: "))
(regionp (use-region-p)))
(save-excursion
(if regionp
@@ -270,34 +350,6 @@ User is prompted for the optional descriptor."
(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: ")
@@ -314,10 +366,6 @@ User is prompted for the optional descriptor."
(insert str)
(forward-line 1)))))
-;; -------------------- Prepend To Lines In Region Or Buffer --------------------
-;; prepend characters to the beginning of all lines in the region or the buffer.
-;; should probably be collapsed into the append lines function. .
-
(defun cj/prepend-to-lines-in-region-or-buffer (str)
"Prompt for STR and prepend it to the start of each line in region or buffer."
(interactive "sEnter string to prepend: ")
@@ -334,178 +382,15 @@ User is prompted for the optional descriptor."
(insert str)
(forward-line 1)))))
-;; ------------------------------ Hyphenate Region -----------------------------
-;; hyphenates any empty space in a region; complains if no region selected
-
-(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 cj/buffer-strip-ctrl-m ()
- "Remove ^M from the current buffer."
- (interactive)
- (save-excursion
- (goto-char (point-min))
- (while (search-forward "" nil t)
- (replace-match "" nil t))))
-
-;; -------------------- Remove Blank Lines Region Or Buffer --------------------
-;; removes lines contiaining whitespace from region or buffer.
-
-(defun cj/delete-blank-lines-region-or-buffer (start end)
- "Delete all blank lines in the region between START and END.
-Blank lines are lines that contain only whitespace (spaces or tabs).
-If called interactively with an active region, operate on that region.
-If no active region, prompt user before operating on the whole buffer.
-Otherwise signal a user-error and do nothing. The point is restored
-to its original position after deletion."
- (interactive
- (if (use-region-p)
- ;; grab its boundaries if there's a region
- (list (region-beginning) (region-end))
- ;; or ask if user intended operating on whole buffer
- (if (yes-or-no-p "Delete blank lines in entire buffer? ")
- (list (point-min) (point-max))
- (user-error "Aborted"))))
- (save-excursion
- (save-restriction
- (widen)
- ;; Regexp "^[[:space:]]*$" matches lines of zero or more spaces/tabs.
- (flush-lines "^[[:space:]]*$" start end)))
- ;; Return nil (Emacs conventions). Point is already restored.
- nil)
-
-;; ---------------------- Fixup Whitespace Line Or Region ----------------------
-
-(defun cj/fixup-whitespace-line-or-region (&optional region)
- "Fix up whitespace 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))))
- (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
- (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))))))
-
-;; -------------------------- Replace Fraction Glyphs --------------------------
+;; Surround, append, prepend prefix keymap
+(define-prefix-command 'cj/surround-map nil
+ "Keymap for surrounding, appending, and prepending operations.")
+(define-key cj/custom-keymap "s" 'cj/surround-map)
+(define-key cj/surround-map "s" 'cj/surround-word-or-region)
+(define-key cj/surround-map "a" 'cj/append-to-lines-in-region-or-buffer)
+(define-key cj/surround-map "p" 'cj/prepend-to-lines-in-region-or-buffer)
-(defun cj/replace-fraction-glyphs (start end)
- "Replace common fraction glyphs with their spelled out format.
-Operates in the buffer or region (as identified with START and END) if selected.
-Replaces the text with the glyphs if called with C-u."
- (interactive "r")
- (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)))))))
-
-;; ------------------------------ Insert Date Time -----------------------------
-;; insert a sortable or a readable datestamp or timestamp
+;;; -------------------------- Date And Time Insertion --------------------------
(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.
@@ -537,6 +422,16 @@ Uses `sortable-time-format' for the formatting the date/time."
(interactive)
(insert (format-time-string sortable-time-format (current-time))))
+(defvar readable-time-format "%-I:%M %p "
+ "Time format to insert with `insert-readable-time' func.
+See help of `format-time-string' for possible replacements")
+
+(defun cj/insert-readable-time ()
+ "Insert the current time into current buffer.
+Uses `readable-time-format' for the formatting the date/time."
+ (interactive)
+ (insert (format-time-string readable-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")
@@ -547,149 +442,390 @@ 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
+(defvar readable-date-format "%A, %B %d, %Y"
+ "Time format to insert with `insert-readable-time' func.
+See help of `format-time-string' for possible replacements")
-(defun cj/copy-link-to-source-file ()
- "Copy the full file:// path of the underlying source file to the kill ring."
+(defun cj/insert-readable-date ()
+ "Insert the current date into current buffer.
+Uses `readable-time-format' for the formatting the date/time."
(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))))
+ (insert (format-time-string readable-date-format (current-time))))
-;; ------------------------- Buffer And File Operations ------------------------
-;; move, rename, or delete the underlying source file for the current buffer.
+;; Date/time insertion prefix and keymap
+(define-prefix-command 'cj/datetime-map nil
+ "Keymap for inserting various date/time formats.")
+(define-key cj/custom-keymap "d" 'cj/datetime-map)
+(define-key cj/datetime-map "r" 'cj/insert-readable-date-time)
+(define-key cj/datetime-map "s" 'cj/insert-sortable-date-time)
+(define-key cj/datetime-map "t" 'cj/insert-sortable-time)
+(define-key cj/datetime-map "T" 'cj/insert-readable-time)
+(define-key cj/datetime-map "d" 'cj/insert-sortable-date)
+(define-key cj/datetime-map "D" 'cj/insert-readable-date)
-;; 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))))
+;;; ----------------------- Line And Paragraph Operations -----------------------
-;; 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))))))
+(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))
-;; DELETE BUFFER + FILE
-(defun cj/delete-buffer-and-file ()
- "Kill the current buffer and delete the file it visits."
+(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))
+
+(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)))))))
+
+(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/underscore-line ()
+ "Underline the current line by inserting a row of characters below it.
+If the line is empty or contains only whitespace, abort with a message."
+ (interactive)
+ (let ((line (buffer-substring-no-properties
+ (line-beginning-position)
+ (line-end-position))))
+ (if (string-match-p "^[[:space:]]*$" line)
+ (message "Line empty or only whitespace. Aborting.")
+ (let* ((char (read-char "Enter character for underlining: "))
+ (len (save-excursion
+ (goto-char (line-end-position))
+ (current-column))))
+ (save-excursion
+ (end-of-line)
+ (insert "\n" (make-string len char)))))))
+
+
+;; Line & paragraph operations prefix and keymap
+(define-prefix-command 'cj/line-and-paragraph-map nil
+ "Keymap for line and paragraph manipulation.")
+(define-key cj/custom-keymap "l" 'cj/line-and-paragraph-map)
+(define-key cj/line-and-paragraph-map "j" 'cj/join-line-or-region)
+(define-key cj/line-and-paragraph-map "J" 'cj/join-paragraph)
+(define-key cj/line-and-paragraph-map "d" 'cj/duplicate-line-or-region)
+(define-key cj/line-and-paragraph-map "r" 'cj/remove-duplicate-lines-region-or-buffer)
+(define-key cj/line-and-paragraph-map "u" 'cj/underscore-line)
+
+;;; ---------------------------------- Comments ---------------------------------
+
+(defun cj/comment-reformat ()
+ "Reformats commented text into a single paragraph."
(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))))))
-;; ------------------------------- Ordinal Suffix ------------------------------
-;; add the proper ordinal to a number (e.g., 1st, 2nd, 3rd, 4th).
-;; Stolen from `diary.el' (`diary-ordinal-suffix').
+ (if mark-active
+ (let ((beg (region-beginning))
+ (end (copy-marker (region-end)))
+ (orig-fill-column fill-column))
+ (uncomment-region beg end)
+ (setq fill-column (- fill-column 3))
+ (cj/join-line-or-region beg end)
+ (comment-region beg end)
+ (setq fill-column orig-fill-column )))
+ ;; if no region
+ (message "No region was selected. Select the comment lines to reformat."))
+
+(defun cj/comment-centered (&optional comment-char)
+ "Insert comment text centered around the COMMENT-CHAR character.
+Will default to the '#' character if called with no arguments. Uses the value of
+fill-column or 80 (whichever is less) to calculate the comment length. Will
+begin and end the line with the appropriate comment symbols based on programming mode."
+ (interactive)
+ (if (not (char-or-string-p comment-char))
+ (setq comment-char "#"))
+ (let* ((comment (capitalize (string-trim (read-from-minibuffer "Comment: "))))
+ (fill-column (min fill-column 80))
+ (comment-length (length comment))
+ (comment-start-length (length comment-start))
+ (comment-end-length (length comment-end))
+ (current-column-pos (current-column))
+ (space-on-each-side (/ (- fill-column
+ current-column-pos
+ comment-length
+ (length comment-start)
+ (length comment-end)
+ ;; Single space on each side of comment
+ (if (> comment-length 0) 2 0)
+ ;; Single space after comment syntax sting
+ 1)
+ 2)))
+ (if (< space-on-each-side 2)
+ (message "Comment string is too big to fit in one line")
+ (progn
+ (insert comment-start)
+ (when (equal comment-start ";") ; emacs-lisp line comments are ;;
+ (insert comment-start)) ; so insert comment-char again
+ (insert " ")
+ (dotimes (_ space-on-each-side) (insert comment-char))
+ (when (> comment-length 0) (insert " "))
+ (insert comment)
+ (when (> comment-length 0) (insert " "))
+ (dotimes (_ (if (= (% comment-length 2) 0)
+ (- space-on-each-side 1)
+ space-on-each-side))
+ (insert comment-char))
+ (insert " ")
+ (insert comment-end)))))
+
+(defun cj/comment-box ()
+ "Insert a comment with '#' drawn around a string the user inputs.
+The box extends to the fill column. Places the point on the line after the
+comment box."
+ (interactive)
+ (let* ((comment-char "#")
+ (comment-pad 4) ; 4 = 2 comment chars & 2 spaces
+ (comment (capitalize (string-trim (read-from-minibuffer "Comment: "))))
+ (comment-length (length comment)))
+
+ ;; message if the comment doesn't fit on a single line
+ (if (> comment-length (- fill-column comment-pad))
+ (message "Comment string is too big to fit in one line")
+ (progn
+ (dotimes (_ (- fill-column 1)) (insert comment-char))
+ (newline)
+ (insert comment-char)
+ (insert " ")
+ (insert comment)
+ (dotimes(_ (- fill-column comment-length comment-pad)) (insert " ")))
+ (insert comment-char)
+ (newline)
+ (dotimes (_ (- fill-column 1)) (insert comment-char)))))
+
+(defun cj/comment-hyphen()
+ "Insert a centered comment with '-' (hyphens) on each side."
+ (interactive)
+ (cj/comment-centered "-"))
-(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))))
+(defun cj/delete-buffer-comments ()
+ "Delete all comments within the current buffer."
+ (interactive)
+ (goto-char (point-min))
+ (let (kill-ring)
+ (comment-kill (count-lines (point-min) (point-max)))))
+
+;; Comment styles & removal prefix and keymap
+(define-prefix-command 'cj/comment-map nil
+ "Keymap for comment styling and removal.")
+(define-key cj/custom-keymap "m" 'cj/comment-map)
+(define-key cj/comment-map "r" 'cj/comment-reformat)
+(define-key cj/comment-map "c" 'cj/comment-centered)
+(define-key cj/comment-map "-" 'cj/comment-hyphen)
+(define-key cj/comment-map "b" 'cj/comment-box)
+(define-key cj/comment-map "D" 'cj/delete-buffer-comments)
+
+;;; ---------------------- Ordering And Sorting Operations ----------------------
-;; -------------------------------- Align-Regexp -------------------------------
-;; the built-in align regexp shouldn't use tabs
+(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)))
-(defadvice align-regexp (around align-regexp-with-spaces activate)
- "Avoid tabs when aligning text."
- (let ((indent-tabs-mode nil))
- ad-do-it))
+(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)))
-;; ----------------------------- 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/alphabetize-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)
+ ", "))))
-(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)))
+(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"))
-;; ------------------------------ Personal Keymap ------------------------------
-;; a keymap to use the above functions. prefix key: "C-;"
+ (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)))
+
+
+;; Ordering & sorting prefix and keymap
+(define-prefix-command 'cj/ordering-map nil
+ "Keymap for text ordering and sorting operations.")
+(define-key cj/custom-keymap "o" 'cj/ordering-map)
+(define-key cj/ordering-map "a" 'cj/arrayify)
+(define-key cj/ordering-map "u" 'cj/unarrayify)
+(define-key cj/ordering-map "A" 'cj/alphabetize-region)
+(define-key cj/ordering-map "l" 'cj/comma-separated-text-to-lines)
+
+;;; --------------------------- Case Change Operations --------------------------
-;; Unset Flyspell's greedy keybinding C-; so it can be used for personal-keymap
-(global-unset-key (kbd "C-;"))
-(eval-after-load "flyspell"
- '(define-key flyspell-mode-map (kbd "C-;") nil))
+(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))))))
-(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 "D" 'cj/remove-duplicate-lines-from-region-or-buffer)
- (define-key map ")" #'cj/jump-to-matching-paren)
- (define-key map "/" #'cj/replace-fraction-glyphs)
- (define-key map "L" #'cj/clear-blank-lines)
- (define-key map "-" #'cj/hyphenate-region)
- (define-key map "U" 'upcase-region)
- (define-key map "w" 'cj/fixup-whitespace-line-or-region)
- (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 "P" 'cj/prepend-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)
-(global-set-key (kbd "C-; b c") 'cj/copy-buffer)
-;; 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)
+;; replace the capitalize-region keybinding to call title-case
+(global-set-key [remap capitalize-region] 'cj/title-case-region)
+(defun cj/upcase-dwim ()
+ "Upcase the active region, or upcase the symbol at point if no region."
+ (interactive)
+ (if (use-region-p)
+ (upcase-region (region-beginning) (region-end))
+ (let ((bounds (bounds-of-thing-at-point 'symbol)))
+ (if bounds
+ (upcase-region (car bounds) (cdr bounds))
+ (user-error "No symbol at point")))))
+
+(defun cj/downcase-dwim ()
+ "Downcase the active region, or downcase the symbol at point if no region."
+ (interactive)
+ (if (use-region-p)
+ (downcase-region (region-beginning) (region-end))
+ (let ((bounds (bounds-of-thing-at-point 'symbol)))
+ (if bounds
+ (downcase-region (car bounds) (cdr bounds))
+ (user-error "No symbol at point")))))
+
+;; Case-change operations prefix and keymap
+(define-prefix-command 'cj/case-map nil
+ "Keymap for case-change operations.")
+(define-key cj/custom-keymap "c" 'cj/case-map)
+(define-key cj/case-map "t" 'cj/title-case-region)
+(define-key cj/case-map "u" 'cj/upcase-dwim)
+(define-key cj/case-map "l" 'cj/downcase-dwim) ;; for "lower" case
(provide 'custom-functions)
;;; custom-functions.el ends here.
diff --git a/modules/prog-comments.el b/modules/prog-comments.el
deleted file mode 100644
index 0e022c4b..00000000
--- a/modules/prog-comments.el
+++ /dev/null
@@ -1,135 +0,0 @@
-;;; prog-comments.el --- Comments and Underscores -*- lexical-binding: t; -*-
-;; author: Craig Jennings <c@cjennings.net>
-;;; Commentary:
-;; Simple utility functions for creating and managing comments.
-
-;;; Code:
-
-;; ------------------------------ Comment Reformat -----------------------------
-;; uncomments the selected text,joins into one paragraph,reapplies comments
-
-(defun cj/comment-reformat ()
- "Reformats commented text into a single paragraph."
- (interactive)
-
- (if mark-active
- (let ((beg (region-beginning))
- (end (copy-marker (region-end)))
- (orig-fill-column fill-column))
- (uncomment-region beg end)
- (setq fill-column (- fill-column 3))
- (cj/join-line-or-region beg end)
- (comment-region beg end)
- (setq fill-column orig-fill-column )))
- ;; if no region
- (message "No region was selected. Select the comment lines to reformat."))
-(global-set-key (kbd "C-z c r") 'cj/comment-reformat)
-
-;; ------------------------------ Comment Centered -----------------------------
-;; Horizontal comment char with centered text. Defaults to appropriate comments
-;; per major mode.
-
-(defun cj/comment-centered (&optional comment-char)
- "Insert comment text centered around the COMMENT-CHAR character.
-Will default to the '#' character if called with no arguments. Uses the value of
-fill-column or 80 (whichever is less) to calculate the comment length. Will
-begin and end the line with the appropriate comment symbols based on programming mode."
- (interactive)
- (if (not (char-or-string-p comment-char))
- (setq comment-char "#"))
- (let* ((comment (capitalize (string-trim (read-from-minibuffer "Comment: "))))
- (fill-column (min fill-column 80))
- (comment-length (length comment))
- (comment-start-length (length comment-start))
- (comment-end-length (length comment-end))
- (current-column-pos (current-column))
- (space-on-each-side (/ (- fill-column
- current-column-pos
- comment-length
- (length comment-start)
- (length comment-end)
- ;; Single space on each side of comment
- (if (> comment-length 0) 2 0)
- ;; Single space after comment syntax sting
- 1)
- 2)))
- (if (< space-on-each-side 2)
- (message "Comment string is too big to fit in one line")
- (progn
- (insert comment-start)
- (when (equal comment-start ";") ; emacs-lisp line comments are ;;
- (insert comment-start)) ; so insert comment-char again
- (insert " ")
- (dotimes (_ space-on-each-side) (insert comment-char))
- (when (> comment-length 0) (insert " "))
- (insert comment)
- (when (> comment-length 0) (insert " "))
- (dotimes (_ (if (= (% comment-length 2) 0)
- (- space-on-each-side 1)
- space-on-each-side))
- (insert comment-char))
- (insert " ")
- (insert comment-end)))))
-(global-set-key (kbd "C-z c l") 'cj/comment-line)
-
-;; ------------------------------- Comment Hyphen ------------------------------
-;; Horizontal dashes with centered text, typically used to indicating sections
-
-(defun cj/comment-hyphen()
- "Insert a centered comment with '-' (hyphens) on each side."
- (interactive)
- (cj/comment-centered "-"))
-(global-set-key (kbd "C-z c -") 'cj/comment-hyphen)
-
-;; -------------------------------- Comment Box --------------------------------
-;; Traditional comment boxes
-
-(defun cj/comment-box ()
- "Insert a comment with '#' drawn around a string the user inputs.
-The box extends to the fill column. Places the point on the line after the
-comment box."
- (interactive)
- (let* ((comment-char "#")
- (comment-pad 4) ; 4 = 2 comment chars & 2 spaces
- (comment (capitalize (string-trim (read-from-minibuffer "Comment: "))))
- (comment-length (length comment)))
-
- ;; message if the comment doesn't fit on a single line
- (if (> comment-length (- fill-column comment-pad))
- (message "Comment string is too big to fit in one line")
- (progn
- (dotimes (_ (- fill-column 1)) (insert comment-char))
- (newline)
- (insert comment-char)
- (insert " ")
- (insert comment)
- (dotimes(_ (- fill-column comment-length comment-pad)) (insert " ")))
- (insert comment-char)
- (newline)
- (dotimes (_ (- fill-column 1)) (insert comment-char)))))
-
-;; ------------------------------ Underscore Line ------------------------------
-;; Underlines the current line with the character of your choosing
-
-(defun cj/underscore-line (char)
- "Insert the number of 'CHAR' underneath the current line to mimic an underscore."
- (interactive "cEnter the character for underlining: ")
- (save-excursion
- (let ((length (- (point-at-eol) (point-at-bol))))
- (end-of-line)
- (insert "\n")
- (insert (make-string length char)))))
-
-;; --------------------------- Remove Buffer Comments --------------------------
-;; another nice suggestion from malabarba.
-;; https://emacs.stackexchange.com/questions/5441/function-to-delete-all-comments-from-a-buffer-without-moving-them-to-kill-ring
-
-(defun cj/remove-buffer-comments ()
- (interactive)
- (goto-char (point-min))
- (let (kill-ring)
- (comment-kill (count-lines (point-min) (point-max)))))
-
-
-(provide 'prog-comments)
-;;; prog-comments.el ends here.