summaryrefslogtreecommitdiff
path: root/modules/custom-functions.el
diff options
context:
space:
mode:
Diffstat (limited to 'modules/custom-functions.el')
-rw-r--r--modules/custom-functions.el506
1 files changed, 506 insertions, 0 deletions
diff --git a/modules/custom-functions.el b/modules/custom-functions.el
new file mode 100644
index 00000000..fe617ed1
--- /dev/null
+++ b/modules/custom-functions.el
@@ -0,0 +1,506 @@
+;;; 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)
+ (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))))
+
+;; --------------------------- 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\nMQuote: ")
+ (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")))))
+
+;; -------------------- 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 "
+ "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))
+
+;; ------------------------------ 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-whitespace-in-region)
+ (define-key map "p" 'cj/append-to-lines-in-region-or-buffer)
+ (define-key map "1" 'cj/alphabetize-and-replace-regibon)
+ (define-key map "C" 'display-fill-column-indicator-mode)
+ (define-key map "w" 'cj/wrap-region-as-code-span)
+ (define-key map "f" 'cj/format-region-or-buffer)
+ (define-key map "h" 'cj/hyphenate-region)
+ (define-key map "j" 'cj/join-line-or-region)
+ (define-key map "J" 'cj/join-paragraph)
+ (define-key map "r" 'align-regexp)
+ (define-key map "l" 'downcase-dwim)
+ (define-key map "u" 'cj/title-case-region)
+ (define-key map "U" 'upcase-region)
+ (define-key map "#" 'cj/count-words-buffer-or-region)
+ 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)
+
+(provide 'custom-functions)
+;;; custom-functions.el ends here.