From 754bbf7a25a8dda49b5d08ef0d0443bbf5af0e36 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 7 Apr 2024 13:41:34 -0500 Subject: new repository --- modules/ai-config.el | 51 ++++ modules/calibredb-epub-config.el | 103 ++++++++ modules/config-utilities.el | 112 +++++++++ modules/custom-functions.el | 506 +++++++++++++++++++++++++++++++++++++++ modules/dashboard-config.el | 141 +++++++++++ modules/diff-config.el | 53 ++++ modules/dirvish-config.el | 183 ++++++++++++++ modules/elfeed-config.el | 150 ++++++++++++ modules/epa-config.el | 30 +++ modules/eradio-config.el | 36 +++ modules/erc-config.el | 109 +++++++++ modules/eshell-vterm-config.el | 152 ++++++++++++ modules/eww-config.el | 28 +++ modules/flycheck-config.el | 47 ++++ modules/flyspell-config.el | 191 +++++++++++++++ modules/font-config.el | 200 ++++++++++++++++ modules/games-config.el | 69 ++++++ modules/graphviz-config.el | 17 ++ modules/help-utils.el | 89 +++++++ modules/host-environment.el | 48 ++++ modules/httpd-config.el | 26 ++ modules/keybindings.el | 132 ++++++++++ modules/latex-config.el | 53 ++++ modules/ledger-config.el | 74 ++++++ modules/local-repository.el | 50 ++++ modules/mail-config.el | 210 ++++++++++++++++ modules/markdown-config.el | 47 ++++ modules/modeline-config.el | 36 +++ modules/org-agenda-config.el | 254 ++++++++++++++++++++ modules/org-appearance-config.el | 64 +++++ modules/org-babel-config.el | 88 +++++++ modules/org-capture-config.el | 107 +++++++++ modules/org-config.el | 154 ++++++++++++ modules/org-contacts-config.el | 30 +++ modules/org-drill-config.el | 88 +++++++ modules/org-export-config.el | 39 +++ modules/org-refile-config.el | 62 +++++ modules/org-roam-config.el | 192 +++++++++++++++ modules/pdf-config.el | 51 ++++ modules/prog-c.el | 32 +++ modules/prog-comments.el | 136 +++++++++++ modules/prog-general.el | 203 ++++++++++++++++ modules/prog-go.el | 85 +++++++ modules/prog-lisp.el | 128 ++++++++++ modules/prog-lsp.el | 55 +++++ modules/prog-python.el | 74 ++++++ modules/prog-shell.el | 14 ++ modules/prog-training.el | 30 +++ modules/prog-webdev.el | 79 ++++++ modules/prog-yaml.el | 18 ++ modules/reconcile-open-repos.el | 72 ++++++ modules/selection-framework.el | 155 ++++++++++++ modules/show-kill-ring.el | 106 ++++++++ modules/system-defaults.el | 195 +++++++++++++++ modules/system-utils.el | 311 ++++++++++++++++++++++++ modules/telegram-config.el | 48 ++++ modules/test-code.el | 170 +++++++++++++ modules/text-config.el | 102 ++++++++ modules/tramp-config.el | 52 ++++ modules/treesitter-config.el | 38 +++ modules/ui-config.el | 76 ++++++ modules/ui-navigation.el | 165 +++++++++++++ modules/ui-theme.el | 110 +++++++++ modules/vc-config.el | 97 ++++++++ modules/wrap-up.el | 30 +++ 65 files changed, 6653 insertions(+) create mode 100644 modules/ai-config.el create mode 100644 modules/calibredb-epub-config.el create mode 100644 modules/config-utilities.el create mode 100644 modules/custom-functions.el create mode 100644 modules/dashboard-config.el create mode 100644 modules/diff-config.el create mode 100644 modules/dirvish-config.el create mode 100644 modules/elfeed-config.el create mode 100644 modules/epa-config.el create mode 100644 modules/eradio-config.el create mode 100644 modules/erc-config.el create mode 100644 modules/eshell-vterm-config.el create mode 100644 modules/eww-config.el create mode 100644 modules/flycheck-config.el create mode 100644 modules/flyspell-config.el create mode 100644 modules/font-config.el create mode 100644 modules/games-config.el create mode 100644 modules/graphviz-config.el create mode 100644 modules/help-utils.el create mode 100644 modules/host-environment.el create mode 100644 modules/httpd-config.el create mode 100644 modules/keybindings.el create mode 100644 modules/latex-config.el create mode 100644 modules/ledger-config.el create mode 100644 modules/local-repository.el create mode 100644 modules/mail-config.el create mode 100644 modules/markdown-config.el create mode 100644 modules/modeline-config.el create mode 100644 modules/org-agenda-config.el create mode 100644 modules/org-appearance-config.el create mode 100644 modules/org-babel-config.el create mode 100644 modules/org-capture-config.el create mode 100644 modules/org-config.el create mode 100644 modules/org-contacts-config.el create mode 100644 modules/org-drill-config.el create mode 100644 modules/org-export-config.el create mode 100644 modules/org-refile-config.el create mode 100644 modules/org-roam-config.el create mode 100644 modules/pdf-config.el create mode 100644 modules/prog-c.el create mode 100644 modules/prog-comments.el create mode 100644 modules/prog-general.el create mode 100644 modules/prog-go.el create mode 100644 modules/prog-lisp.el create mode 100644 modules/prog-lsp.el create mode 100644 modules/prog-python.el create mode 100644 modules/prog-shell.el create mode 100644 modules/prog-training.el create mode 100644 modules/prog-webdev.el create mode 100644 modules/prog-yaml.el create mode 100644 modules/reconcile-open-repos.el create mode 100644 modules/selection-framework.el create mode 100644 modules/show-kill-ring.el create mode 100644 modules/system-defaults.el create mode 100644 modules/system-utils.el create mode 100644 modules/telegram-config.el create mode 100644 modules/test-code.el create mode 100644 modules/text-config.el create mode 100644 modules/tramp-config.el create mode 100644 modules/treesitter-config.el create mode 100644 modules/ui-config.el create mode 100644 modules/ui-navigation.el create mode 100644 modules/ui-theme.el create mode 100644 modules/vc-config.el create mode 100644 modules/wrap-up.el (limited to 'modules') diff --git a/modules/ai-config.el b/modules/ai-config.el new file mode 100644 index 00000000..14380a2b --- /dev/null +++ b/modules/ai-config.el @@ -0,0 +1,51 @@ +;;; ai-config.el --- Configuration for AI Integrations -*- lexical-binding: t; -*- + +;;; Commentary: + +;; There are several workflows available. Here are the ones I use most. + +;; - Launch GPTel and chat with the AI in a separate buffer. +;; Chatting is fine, but it can mean cutting and pasting code back and forth +;; between buffers. + +;; - Select a region and launch GPTel. +;; The region is automatically inserted into the buffer making it easy to +;; simply ask a question after it's read the code. + +;; Note that you can save a file, then turn on gptel-mode to resume your +;; conversation. + +;; Remember that sending the message requires C-. + +;;; Code: + +;; ----------------------------------- GPTel ----------------------------------- +;; integration with ChatGPT and other large language models. + +(use-package gptel + :defer .5 + :bind + ("C-h G" . gptel) + (:map gptel-mode-map + ("C-" . gptel-send)) + :custom + ;; (gptel-model "gpt-3.5-turbo-16k") ;; next best alternative + (gptel-model "gpt-4") + (gptel-default-mode 'org-mode) + :config + (setq gptel-directives + '((default + . "You are a large language model living in Emacs and a careful and + knowledgeable emacs-lisp programmer. Respond accurately and concisely.") + (programming + . "You are a large language model and a careful programmer. Provide code + and only code as output without any additional text, prompt or note.") + (writing + . "You are a large language model and a writing assistant. Respond concisely.") + (chat + . "You are a large language model and a conversation partner. Respond concisely."))) + + (setq gptel-api-key (auth-source-pick-first-password :host "api.openai.com"))) + +(provide 'ai-config) +;;; ai-config.el ends here. diff --git a/modules/calibredb-epub-config.el b/modules/calibredb-epub-config.el new file mode 100644 index 00000000..92e65e2a --- /dev/null +++ b/modules/calibredb-epub-config.el @@ -0,0 +1,103 @@ +;;; calibredb-epub-config --- Functionality for Ebook Management and Display -*- lexical-binding: t; -*- +;; author Craig Jennings + +;;; Commentary: + +;; Note: Calibre virtual library functionality works as designed, but not as I +;; want. I had hoped to simply view a virtual library defined by it's tag. +;; Instead, it searches for the library keywords within the description as well, +;; turning up many books that aren't remotely related. I've overwritten the +;; virtual-library functionality to simply filter by tag, and given that the +;; "l" keybinding in the calibredb-search-mode-map. + +;;; Code: + +;; -------------------------- CalibreDB Ebook Manager -------------------------- + +(use-package calibredb + :defer 1 + :commands calibredb + :bind + ("M-B" . calibredb) + ;; override virtual libraries to filter-by-tag + (:map calibredb-search-mode-map + ("l" . calibredb-filter-by-tag)) + :config + ;; basic config + (setq calibredb-root-dir "~/sync/books/") + (setq calibredb-library-alist '(("~/sync/books/"))) + (setq calibredb-db-dir (expand-file-name "metadata.db" calibredb-root-dir)) + (setq calibredb-program "/usr/bin/calibredb") + (setq calibredb-preferred-format "epub") + + ;; search window display + (setq calibredb-size-show nil) + (setq calibredb-order "asc") + (setq calibredb-id-width 7)) + +;; ------------------------------ Nov Epub Reader ------------------------------ + +(use-package nov + :defer .5 + :after (visual-fill-column) + :mode ("\\.epub\\'" . nov-mode) +;; :hook (nov-mode . cj/nov-apply-preferences) + :bind + (:map nov-mode-map + ("m" . cj/bookmark-set-and-save) + ("b" . bookmark-bmenu-list) + ("r" . nov-render-document) + ("l" . recenter-top-bottom) + ("d" . sdcv-search) + ("." . cj/forward-paragraph-and-center) + ("<" . nov-history-back) + (">" . nov-history-forward) + ("," . backward-paragraph) + ("z" . (lambda () (interactive) (cj/open-file-with-command "zathura"))) + ("e" . (lambda () (interactive) (cj/open-file-with-command "evince"))) + ("t" . nov-goto-toc))) + +(defun cj/forward-paragraph-and-center() + "Forward one paragraph and center the page." + (interactive) + (forward-paragraph) + (recenter)) + +(defun cj/nov-apply-preferences () + "Apply preferences after nove-mode has launched. +Meant to be called via the nov-mode hook to apply font and display preferences + when displaying epub files." + (interactive) + (face-remap-add-relative 'variable-pitch :height 180) ;; increase the size for both variable... + (face-remap-add-relative 'fixed-pitch :height 180) ;; ...and fixed-pitch fonts for readability + (setq nov-text-width 115) ;; narrow text width + (when (require 'visual-fill-column nil t) ;; if visual-fill-column isn't already loaded, do it now. + (setq-local visual-fill-column-center-text t ;; center the text + visual-fill-column-width (+ nov-text-width 10)) ;; helps avoid truncation of long word + (hl-line-mode) + (visual-fill-column-mode 1) ;; wrap lines according to fill-column + (nov-render-document))) ;; re-render the epub + +(defun cj/nov-center-images () + "Center the images in an nov document. +To be called immediately after nov renders the html via +the nov-post-html-render-hook." + (let* ((pixel-buffer-width (shr-pixel-buffer-width)) + match) + (save-excursion + (goto-char (point-min)) + (while (setq match (text-property-search-forward + 'display nil + (lambda (_ p) (eq (car-safe p) 'image)))) + (when-let ((size (car (image-size + (prop-match-value match) 'pixels))) + ((> size 150)) + (center-pixel (floor (- pixel-buffer-width size) 2)) + (center-pos (floor center-pixel (frame-char-width)))) + (beginning-of-line) + (indent-to center-pos) + (end-of-line)))))) +(add-hook 'nov-post-html-render-hook 'cj/nov-center-images) + +(provide 'calibredb-epub-config) +;;; calibredb-epub-config.el ends here diff --git a/modules/config-utilities.el b/modules/config-utilities.el new file mode 100644 index 00000000..4e0fd923 --- /dev/null +++ b/modules/config-utilities.el @@ -0,0 +1,112 @@ +;;; config-utilities --- Config Hacking Utilities -*- lexical-binding: t; -*- +;; author Craig Jennings + +;;; Commentary: +;; Convenience utilities for working on Emacs configuration. + +;;; Code: + +;; ------------------------------ Reload Init File ----------------------------- +;; it does what it says it does. + +(defun cj/reload-init-file () + "Reload the init file. Useful when modifying Emacs config." + (interactive) + (load-file user-init-file)) + +;; ---------------------------- Recompile Emacs Home --------------------------- +;; deletes all .elc and .eln files in user-emacs-directory, then compiles +;; all emacs-lisp files natively if supported, or byte-compiles them if not. + +(defun cj/recompile-emacs-home() + "Delete all compiled files in Emacs home recursively before recompilation. +Will recompile natively if supported, or byte-compiled if not." + (interactive) + (let* ((native-comp-supported (boundp 'native-compile-async)) + (elt-dir (expand-file-name (if native-comp-supported "eln" "elc") user-emacs-directory)) + (message-format (format "Please confirm recursive %s recompilation of %%s: " (if native-comp-supported "native" "byte"))) + (compile-message (format "%scompiling all emacs-lisp files in %%s" (if native-comp-supported "Natively " "Byte-")))) + (if (yes-or-no-p (format message-format user-emacs-directory)) + (progn + (message "Deleting all compiled files in %s" user-emacs-directory) + (dolist (file (directory-files-recursively user-emacs-directory "\\(\\.elc\\|\\.eln\\)$")) + (delete-file file)) + (when (file-directory-p elt-dir) + (delete-directory elt-dir t t)) + (message compile-message user-emacs-directory) + (if native-comp-supported + (let ((comp-async-report-warnings-errors nil)) + (native-compile-async user-emacs-directory 'recursively)) + (byte-recompile-directory user-emacs-directory 0))) + (message "Cancelled recompilation of %s" user-emacs-directory)))) + + +;; ---------------------- Delete Emacs Home Compiled Files --------------------- +;; removes all compiled files and deletes the eln directory + +(defun cj/delete-emacs-home-compiled-files () + "Delete all compiled files recursively in \\='user-emacs-directory\\='." + (interactive) + (message "Deleting compiled files under %s. This may take a while." user-emacs-directory) + (require 'find-lisp) ;; make sure the package is required + (mapc (lambda (path) + (when (or (string-suffix-p ".elc" path) + (string-suffix-p ".eln" path)) + (delete-file path))) + (find-lisp-find-files user-emacs-directory "")) + (message "Done. Compiled files removed under %s" user-emacs-directory)) + + +;; ---------------------- List Loaded Packages --------------------- +;; you don't really need an explanation for this function, do you? + +(defvar cj--loaded-file-paths nil + "All file paths that are loaded.") +(defvar cj--loaded-packages-buffer "*loaded-packages*" + "Buffer name for data about loaded packages.") +(defvar cj--loaded-features-buffer "*loaded-features*" + "Buffer name for data about loaded features.") + +(defun cj/list-loaded-packages() + "List all currently loaded packages." + (interactive) + (with-current-buffer (get-buffer-create cj--loaded-packages-buffer) + (erase-buffer) + (pop-to-buffer (current-buffer)) + + (insert "* Live Packages Exploration\n\n") + (insert (format "%s total packages currently loaded\n" + (length cj--loaded-file-paths))) + + ;; Extract data from builtin variable `load-history'. + (setq cj--loaded-file-paths + (seq-filter #'stringp + (mapcar #'car load-history))) + (cl-sort cj--loaded-file-paths 'string-lessp) + (cl-loop for file in cj--loaded-file-paths + do (insert "\n" file)) + + (goto-char (point-min)))) + +;; ---------------------------- List Loaded Features --------------------------- +;; this function's also self-explanatory + +(defun cj/list-loaded-features() + "List all currently loaded features." + (interactive) + (with-current-buffer (get-buffer-create cj--loaded-features-buffer) + (erase-buffer) + (pop-to-buffer (current-buffer)) + + (insert (format "\n** %d features currently loaded\n" + (length features))) + + (let ((features-vec (apply 'vector features))) + (cl-sort features-vec 'string-lessp) + (cl-loop for x across features-vec + do (insert (format " - %-25s: %s\n" x + (locate-library (symbol-name x)))))) + (goto-char (point-min)))) + +(provide 'config-utilities) +;;; config-utilities.el ends here 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. diff --git a/modules/dashboard-config.el b/modules/dashboard-config.el new file mode 100644 index 00000000..6340c32b --- /dev/null +++ b/modules/dashboard-config.el @@ -0,0 +1,141 @@ +;;; dashboard-config.el --- Dashboard Configuration -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Note: +;; Nerd-Icons Cheat Sheet: https://www.nerdfonts.com/cheat-sheet + +;;; Code: + +;; ------------------------ Dashboard Bookmarks Override ----------------------- +;; overrides the bookmark insertion from the dashboard package to provide an +;; option that only shows the bookmark name, avoiding the path. Paths are often +;; too long and the truncation options aren't aesthetically pleasing. Should be +;; accompanied by the setting (setq dashboard-bookmarks-show-path nil) in +;; config. + +(defcustom dashboard-bookmarks-item-format "%s" + "Format to use when showing the base of the file name." + :type 'string + :group 'dashboard) + +(defun dashboard-insert-bookmarks (list-size) + "Add the list of LIST-SIZE items of bookmarks." + (require 'bookmark) + (dashboard-insert-section + "Bookmarks:" + (dashboard-subseq (bookmark-all-names) list-size) + list-size + 'bookmarks + (dashboard-get-shortcut 'bookmarks) + `(lambda (&rest _) (bookmark-jump ,el)) + (if-let* ((filename el) + (path (bookmark-get-filename el)) + (path-shorten (dashboard-shorten-path path 'bookmarks))) + (cl-case dashboard-bookmarks-show-path + (`align + (unless dashboard--bookmarks-cache-item-format + (let* ((len-align (dashboard--align-length-by-type 'bookmarks)) + (new-fmt (dashboard--generate-align-format + dashboard-bookmarks-item-format len-align))) + (setq dashboard--bookmarks-cache-item-format new-fmt))) + (format dashboard--bookmarks-cache-item-format filename path-shorten)) + (`nil filename) + (t (format dashboard-bookmarks-item-format filename path-shorten))) + el))) + +;; ----------------------------- Display Dashboard ----------------------------- +;; convenience function to redisplay dashboard + +(defun cj/display-dashboard () + "Display dashboard, create if it doesn't exist." + (interactive) + (dired-sidebar-hide-sidebar) ;; hide dired-sidebar if displaying + (get-buffer-create "*dashboard*") + (pop-to-buffer "*dashboard*") + (delete-other-windows)) +(global-set-key (kbd "") 'cj/display-dashboard) + +;; --------------------------------- Dashboard --------------------------------- +;; a useful startup screen for Emacs + +(use-package dashboard + :demand t ;; needed to startup quickly + :custom + (dashboard-projects-backend 'projectile) + + (dashboard-item-generators + '((projects . dashboard-insert-projects) + (bookmarks . dashboard-insert-bookmarks))) + + (dashboard-items '((projects . 5) + (bookmarks . 7))) + + (dashboard-startupify-list + '(dashboard-insert-banner + dashboard-insert-banner-title + dashboard-insert-newline + dashboard-insert-newline + dashboard-insert-navigator + dashboard-insert-init-info + dashboard-insert-newline + dashboard-insert-newline + dashboard-insert-items + dashboard-insert-newline)) + :config + + ;; == general + (dashboard-setup-startup-hook) ;; run dashboard post emacs init + (setq initial-buffer-choice (lambda () (get-buffer "*dashboard*"))) ;; display dashboard on startup + (setq dashboard-display-icons-p t) ;; display icons on both GUI and terminal + (setq dashboard-icon-type 'nerd-icons) ;; use `nerd-icons' package + (setq dashboard-center-content t) ;; horizontally center dashboard content + (setq dashboard-bookmarks-show-path nil) ;; don't show paths in bookmarks + (setq dashboard-set-footer nil) ;; don't show footer and quotes + + ;; == banner + (custom-set-faces '(dashboard-banner-logo-title + ((t(:slant oblique + :height 170 + :family "Merriweather"))))) + + (setq dashboard-startup-banner (concat user-emacs-directory "assets/M-x_butterfly.png")) + (setq dashboard-banner-logo-title "Emacs: The Editor That Saves Your Soul") + + ;; == navigation + (setq dashboard-set-navigator t) + (setq dashboard-navigator-buttons + `(((,(nerd-icons-faicon "nf-fa-envelope") + "Email" "Mu4e Email Client" + (lambda (&rest _) (mu4e))) + + (,(nerd-icons-faicon "nf-fae-book_open_o") + "Ebooks" "Calibre Ebook Reader" + (lambda (&rest _) (calibredb))) + + (,(nerd-icons-mdicon "nf-md-school") + "Flashcards" "Org-Drill" + (lambda (&rest _) (cj/drill-start))) + + (,(nerd-icons-faicon "nf-fa-rss_square") + "Feeds" "Elfeed Feed Reader" + (lambda (&rest _) (elfeed-dashboard))) + + (,(nerd-icons-faicon "nf-fa-comments") + "IRC" "Emacs Relay Chat" + (lambda (&rest _) (cj/erc-start-or-switch))) + + (,(nerd-icons-faicon "nf-fae-telegram") + "Telegram" "Telega Chat Client" + (lambda (&rest _) (telega))) + + (,(nerd-icons-faicon "nf-fa-folder_o") + "Files" "Dirvish File Manager" + (lambda (&rest _) (dirvish user-home-dir)))))) + + ;; == content + (setq dashboard-show-shortcuts nil) ;; don't show dashboard item abbreviations + ) ;; end use-package dashboard + +(provide 'dashboard-config) +;;; dashboard-config.el ends here. diff --git a/modules/diff-config.el b/modules/diff-config.el new file mode 100644 index 00000000..aaef8dbf --- /dev/null +++ b/modules/diff-config.el @@ -0,0 +1,53 @@ +;;; ediff-config.el --- diff Configuration -*- lexical-binding: t; -*- + +;;; Commentary: +;; highly useful setup for configuring ediff +;; https://oremacs.com/2015/01/17/setting-up-ediff/ + +;;; Code: + +;; -------------------------------- Csetq Macro -------------------------------- +;; a macro that allows a setq for custom variables. uses the variable's set +;; property without writing customizations to emacs init. + +(defmacro csetq (variable value) + `(funcall (or (get ',variable 'custom-set) + 'set-default) + ',variable ,value)) + +;; ------------------------------- Ediff Settings ------------------------------ +;; ediff configuration. note the dired-ediff-files function in dirvish. + +;; lose the control panel +(csetq ediff-window-setup-function 'ediff-setup-windows-plain) + +;; only split horizontally +(csetq ediff-split-window-function 'split-window-horizontally) + +;; ignore whitespace in diffs +(csetq ediff-diff-options "-w") + +;; only highlight the current diff: +(setq-default ediff-highlight-all-diffs 'nil) + +;; use j and k for next and previous diffs +(defun cj/ediff-hook () + (ediff-setup-keymap) + (define-key ediff-mode-map "j" 'ediff-next-difference) + (define-key ediff-mode-map "k" 'ediff-previous-difference)) +(add-hook 'ediff-mode-hook 'cj/ediff-hook) + +;; restore the window setup after quitting +(winner-mode) +(add-hook 'ediff-after-quit-hook-internal 'winner-undo) + +;; ----------------------------------- Ztree ----------------------------------- +;; diff two directories + +(use-package ztree + :defer .5 + :bind + ("C-c D" . ztree-diff)) + +(provide 'diff-config) +;;; diff-config.el ends here. diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el new file mode 100644 index 00000000..e089f010 --- /dev/null +++ b/modules/dirvish-config.el @@ -0,0 +1,183 @@ +;;; dirvish-config.el --- Configuration for Tramp and the Dired File Manager -*- lexical-binding: t; -*- +;; author Craig Jennings + +;;; Commentary: + +;; DIRVISH NOTES: +;; access the quick access directories by pressing 'g' (for "go") + +;;; Code: + +;; ----------------------------------- Dired ----------------------------------- + +(use-package dired + :ensure nil ;; built-in + :defer .5 + :bind + (:map dired-mode-map + ([remap dired-summary] . which-key-show-major-mode) + ("E" . wdired-change-to-wdired-mode) ;; edit names/properties in buffer + ("e" . cj/dired-ediff-files)) ;; ediff files + :custom + (dired-use-ls-dired nil) ;; non GNU FreeBSD doesn't support a "--dired" switch + :config + (setq dired-listing-switches "-hl --almost-all --group-directories-first") + (setq dired-dwim-target t) + (setq dired-clean-up-buffers-too t) ;; offer to kill buffers associated deleted files and dirs + (setq dired-clean-confirm-killing-deleted-buffers t) ;; don't ask whether to kill buffers associated with deleted files. + (setq dired-kill-when-opening-new-dired-buffer t) ;; stop littering buffers while navigating a directory tree + (setq dired-recursive-copies (quote always)) ;; “always” means no asking + (setq dired-recursive-deletes (quote top))) ;; “top” means ask once + +;; (add-hook 'dired-mode-hook 'auto-revert-mode) ;; auto revert dired when files change + + +;; ------------------------ Dired Mark All Visible Files ----------------------- +;; convenience function to mark all visible files in dired + +(defun cj/dired-mark-all-visible-files () + "Mark all visible files for deletion in Dired mode." + (interactive) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (if (not (looking-at "^. d")) + (dired-mark 1)) + (forward-line 1)))) + +;; ---------------------------------- Dirvish ---------------------------------- + +(use-package dirvish + :defer .5 + :after dired + :custom + (dirvish-quick-access-entries + '(("h" "~/" "home") + ("rsb" "/sshx:cjennings@wolf.usbx.me:/home/cjennings/" "seedbox") + ("rcj" "/sshx:cjennings@cjennings.net:~" "cjennings.net") + ("dn" "~/downloads/" "downloads") + ("lt" "~/.local/share/Trash" "trash") + ("cj" "~/code/cjennings.net" "cjennings.net") + ("co" "~/code" "code") + ("df" "~/.dotfiles/" "dotfiles") + ("dr" "~/sync/org/drill/" "org drill files") + ("dt" "~/downloads/torrents/complete/" "torrents") + ("dx" "~/documents/" "documents") + ("gc" "~/code/golangcourse" "golang course") + ("mp" "~/sync/playlists/" "playlists") + ("mv" "~/magic/video/" "magic/video") + ("mx" "~/music/" "music") + ("my" "~/magic/youtube/" "magic/youtube") + ("or" "~/sync/org/" "sync") + ("pr" "~/projects/" "projects") + ("ps" "~/pictures/screenshots/" "screenshots") + ("pw" "~/pictures/wallpaper" "wallpaper") + ("px" "~/pictures/" "pictures") + ("tg" "~/sync/org/text.games" "text games") + ("vx" "~/videos/" "videos"))) + (dirvish-attributes '(vscode-icon file-size)) + (dirvish-override-dired-mode t) + (dirvish-preview-dispatchers '(image gif video audio epub pdf archive)) + :hook (dirvish-setup . dirvish-emerge-mode) + :config + (setq dirvish-use-mode-line nil) + (setq dirvish-use-header-line nil) + :bind + (("C-x d" . dirvish) + ("C-x C-d" . dirvish) + ("C-x D" . dirvish-override-dired-mode) + ("" . dirvish-side) + + :map dirvish-mode-map ; note: Dirvish inherits `dired-mode-map' + ("g" . dirvish-quick-access) + ("G" . revert-buffer) + ("bg" . (lambda () (interactive) ; set background image + (shell-command (concat "nitrogen --save --set-zoom-fill " + (dired-file-name-at-point) " >>/dev/null 2>&1" )))) + ("Z" . (lambda () (interactive) (cj/open-file-with-command "zathura"))) + ("p" . (lambda () (interactive) (cj/open-file-with-command "gimp"))) + ("" . dired-up-directory) + ("" . dired-find-file) + ("f" . dirvish-file-info-menu) + ("y" . dirvish-yank-menu) + ("N" . dirvish-narrow) + ("M" . cj/dired-mark-all-visible-files) + ("s" . dirvish-quicksort) ; remapped `dired-sort-toggle-or-edit' + ("v" . dirvish-vc-menu) ; remapped `dired-view-file' + ("TAB" . dirvish-subtree-toggle) + ("C-." . dirvish-history-go-forward) + ("C-," . dirvish-history-go-backward) + ("M-l" . dirvish-ls-switches-menu) + ("M-m" . dirvish-mark-menu) + ("M-t" . dirvish-layout-toggle) + ("M-s" . dirvish-setup-menu) + ("M-e" . dirvish-emerge-menu))) + +;; -------------------------------- VSCode-Icons ------------------------------- + +(use-package vscode-icon + :defer .5 + :commands (vscode-icon-for-file)) + +;; -------------------------------- Dired Rsync -------------------------------- + +(use-package dired-rsync + :after dired + :bind (:map dired-mode-map + ("r" . dired-rsync))) + +;; ---------------------------- Dired Hide Dotfiles ---------------------------- + +(use-package dired-hide-dotfiles + :after dired + :bind + (:map dired-mode-map + ("h" . dired-hide-dotfiles-mode))) + +;; ------------------------------- Dired Sidebar ------------------------------- + +(use-package dired-sidebar + :after (dired projectile) + :bind (("" . dired-sidebar-toggle-sidebar)) + :commands (dired-sidebar-toggle-sidebar) + :init + (add-hook 'dired-sidebar-mode-hook + (lambda () + (unless (file-remote-p default-directory) + (auto-revert-mode)))) + :config + (push 'toggle-window-split dired-sidebar-toggle-hidden-commands) ; don't allow splitting dired window when it's showing + (push 'rotate-windows dired-sidebar-toggle-hidden-commands) ; don't allow rotating windows when sidebar is showing + (setq dired-sidebar-subtree-line-prefix " ") ; two spaces give simple and aesthetic indentation + (setq dired-sidebar-no-delete-other-windows t) ; don't close when calling 'delete other windows' + (setq dired-sidebar-theme 'vscode) ; fancy icons, please + (setq dired-sidebar-use-custom-font 'nil) ; keep the same font as the rest of Emacs + (setq dired-sidebar-delay-auto-revert-updates 'nil) ; don't delay auto-reverting + (setq dired-sidebar-pop-to-sidebar-on-toggle-open 'nil)) ; don't jump to sidebar when it's toggled on + +;; ----------------------------- Dired Ediff Files ----------------------------- +;; mark two files within dired, then ediff them within Emacs + +(defun cj/dired-ediff-files () + "Ediff two selected files within Dired." + (interactive) + (let ((files (dired-get-marked-files)) + (wnd (current-window-configuration))) + (if (<= (length files) 2) + (let ((file1 (car files)) + (file2 (if (cdr files) + (cadr files) + (read-file-name + "file: " + (dired-dwim-target-directory))))) + (if (file-newer-than-file-p file1 file2) + (ediff-files file2 file1) + (ediff-files file1 file2)) + (add-hook 'ediff-after-quit-hook-internal + (lambda () + (setq ediff-after-quit-hook-internal nil) + (set-window-configuration wnd)))) + (error "No more than 2 files should be marked")))) + +(provide 'dirvish-config) +;;; dirvish-config.el ends here diff --git a/modules/elfeed-config.el b/modules/elfeed-config.el new file mode 100644 index 00000000..37a0e12f --- /dev/null +++ b/modules/elfeed-config.el @@ -0,0 +1,150 @@ +;;; elfeed-config --- Settings and Enhancements to the Elfeed RSS Feed Reader -*- lexical-binding: t; -*- +;; author Craig Jennings + +;;; Commentary: + +;;; Code: + +;; ------------------------------- Elfeed Config ------------------------------- + +(use-package elfeed + :bind + (:map elfeed-show-mode-map + ("w" . eww-open-in-new-buffer)) + (:map elfeed-search-mode-map + ("w" . cj/elfeed-eww-open) ;; opens in eww + ("b" . cj/elfeed-browser-open) ;; opens in external browser + ("d" . cj/elfeed-youtube-dl) ;; async download with yt-dlp and tsp + ("p" . cj/play-with-mpv) ;; async play with mpv + ("R" . cj/elfeed-mark-all-as-read) ;; capital marks all as read, since upper case marks one as read + ("U" . cj/elfeed-mark-all-as-unread)) ;; capital marks all as unread, since lower case marks one as unread + :config + (setq elfeed-db-directory "~/sync/org/ElfeedDB") + (setq-default elfeed-search-title-max-width 150) + (setq-default elfeed-search-title-min-width 80) + (setq-default elfeed-search-filter "+mustread +unread")) + +;; ---------------------------- Elfeed Org Feed List --------------------------- + +(use-package elfeed-org + :defer .5 + :after elfeed + :config + (setq rmh-elfeed-org-files (list (concat sync-dir "elfeed-feeds.org"))) + (elfeed-org)) + +;; ------------------------------ Elfeed Dashboard ----------------------------- + +(use-package elfeed-dashboard + :defer .5 + :bind + ("M-R" . elfeed-dashboard) + :config + (setq elfeed-dashboard-file (concat user-emacs-directory "elfeed-dashboard.org")) + ;; update feed counts on elfeed-quit + (advice-add 'elfeed-search-quit-window :after #'elfeed-dashboard-update-links)) + +;; ------------------------------ Elfeed Functions ----------------------------- + +(defun cj/elfeed-open () + "Open Elfeed, update all feeds, then move to the first entry." + (interactive) + (elfeed) + (elfeed-update) + (elfeed-search-update--force)) + +;; -------------------------- Elfeed Filter Functions -------------------------- + +(defun cj/elfeed-mark-all-as-read () + "Temove the 'unread' tag from all elfeed entries visible in the elfeed search buffer." + (interactive) + (mark-whole-buffer) + (elfeed-search-untag-all-unread)) + +(defun cj/elfeed-mark-all-as-unread () + "Add the 'unread' tag from all elfeed entries visible in the elfeed search buffer." + (interactive) + (mark-whole-buffer) + (elfeed-search-tag-all 'unread)) + +(defun cj/elfeed-set-filter-and-update(filterstring) + "Set the Elfeed filter to 'FILTERSTRING' and update the the buffer." + (interactive "P") + (setq elfeed-search-filter filterstring) + (elfeed-search-update--force) + (elfeed-search-first-entry)) + +;; -------------------------- Elfeed Browser Functions ------------------------- + +(defun cj/elfeed-eww-open (&optional use-generic-p) + "Open currently selected entry with EWW." + (interactive "P") + (let ((entries (elfeed-search-selected))) + (cl-loop for entry in entries + do (elfeed-untag entry 'unread) + when (elfeed-entry-link entry) + do + (add-hook 'eww-after-render-hook #'cj/eww-readable-nonce) + (eww-browse-url it)) + (mapc #'elfeed-search-update-entry entries) + (unless (use-region-p) (forward-line)))) + +;; hook for cj/elfeed-eww-open to open entry in eww readable mode +;; https://emacs.stackexchange.com/questions/36284/how-to-open-eww-in-readable-mode/47757 +(defun cj/eww-readable-nonce () + "Once-off call to eww-readable after EWW is done rendering." + (unwind-protect + (progn + (eww-readable) + (goto-char (point-min))) + (remove-hook 'eww-after-render-hook #'cj/eww-readable-nonce))) + +(defun cj/elfeed-browser-open (&optional use-generic-p) + "Open the currently selected entry with default browser." + (interactive "P") + (let ((entries (elfeed-search-selected))) + (cl-loop for entry in entries + do (elfeed-untag entry 'unread) + when (elfeed-entry-link entry) + do (browse-url-default-browser it)) + (mapc #'elfeed-search-update-entry entries) + (unless (use-region-p) (forward-line)))) + +;; --------------------- Elfeed Plan And Download Functions -------------------- + +(defun cj/elfeed-youtube-dl (&optional use-generic-p) + "Youtube-DL link." + (interactive "P") + (let ((entries (elfeed-search-selected))) + (cl-loop for entry in entries + do (elfeed-untag entry 'unread) + when (elfeed-entry-link entry) + do (cj/yt-dl-it it)) + (mapc #'elfeed-search-update-entry entries) + (unless (use-region-p) (forward-line)))) + +(defun cj/yt-dl-it (url) + "Downloads the URL in an async shell." + (let ((default-directory "~/videos")) + (save-window-excursion + (async-shell-command (format "tsp yt-dlp --add-metadata -ic -o '%%(channel)s-%%(title)s.%%(ext)s' '%s'" url))))) + +(defun cj/play-with-mpv (&optional use-generic-p) + "MPV link." + (interactive "P") + (let ((entries (elfeed-search-selected))) + (cl-loop for entry in entries + do (elfeed-untag entry 'unread) + when (elfeed-entry-link entry) + do (cj/mpv-play-it it)) + (mapc #'elfeed-search-update-entry entries) + (unless (use-region-p) (forward-line)))) + +(defun cj/mpv-play-it (url) + "Play the URL with mpv in an async shell." + (async-shell-command (format "mpv '%s'" url))) + + + +(provide 'elfeed-config) +;;; elfeed-config.el ends here diff --git a/modules/epa-config.el b/modules/epa-config.el new file mode 100644 index 00000000..8d9d8f13 --- /dev/null +++ b/modules/epa-config.el @@ -0,0 +1,30 @@ +;;; epa-config.el --- EasyPG Configuration -*- lexical-binding: t; -*- + +;;; Commentary: +;; + +;;; Code: + +;; -------------------------------- Auth Sources ------------------------------- +;; auth sources settings + +(use-package auth-source + :ensure nil ;; built in + :defer .5 + :config + (setq auth-sources '((:source "~/.authinfo.gpg"))) + (setenv "GPG_AGENT_INFO" nil) ;; emacs use internal prompt, not gpg agent + (setq auth-source-debug t)) ;; echo debug info to Messages + +;; ----------------------------- Easy PG Assistant ----------------------------- +;; Key management, cryptographic operations on regions and files, dired +;; integration, and automatic encryption/decryption of *.gpg files. + +(use-package epa + :ensure nil ;; built-in + :defer .5 + :config + (setq epg-gpg-program "gpg2")) ;; force use gpg2 (not gpg v.1) + +(provide 'epa-config) +;;; epa-config.el ends here. diff --git a/modules/eradio-config.el b/modules/eradio-config.el new file mode 100644 index 00000000..8dee7a23 --- /dev/null +++ b/modules/eradio-config.el @@ -0,0 +1,36 @@ +;;; eradio-config --- Simple Internet Radio Setup -*- lexical-binding: t; -*- +;; author Craig Jennings + +;;; Commentary: + +;;; Code: + +(use-package eradio + :bind + ("C-c r p" . eradio-play) + ("C-c r s" . eradio-stop) + ("C-c r " . eradio-toggle) + :config + (setq eradio-player '("mpv" "--no-video" "--no-terminal")) + (setq eradio-channels + '(("BAGeL Radio (alternative)" . "https://ais-sa3.cdnstream1.com/2606_128.mp3") + ("Blues Music Fan Radio (blues)" . "http://ais-sa2.cdnstream1.com/1992_128.mp3") + ("Blues Radio (blues)" . "http://cast3.radiohost.ovh:8352/") + ("Concertzender Baroque (classical)" . "http://streams.greenhost.nl:8080/barok") + ("Groove Salad (somafm)" . "https://somafm.com/groovesalad130.pls") + ("Indie Pop Rocks (somafm)" . "https://somafm.com/indiepop130.pls") + ("KDFC Classical (classical)" . "http://128.mp3.pls.kdfc.live/") + ("Radio Caprice Classical Lute (classical)" . "http://79.120.12.130:8000/lute") + ("Radio Swiss Classic German (classical)" . "http://stream.srg-ssr.ch/m/rsc_de/mp3_128") + ("Radio Caprice Acoustic Blues (blues)" . "http://79.111.14.76:8000/acousticblues") + ("Radio Caprice Delta Blues (blues)" . "http://79.120.77.11:8002/deltablues") + ("Seven Inch Soul (somafm)" . "https://somafm.com/nossl/7soul.pls") + ("Space Station Soma (somafm)" . "https://somafm.com/spacestation.pls") + ("Suburbs of Goa (somafm)" . "https://somafm.com/suburbsofgoa.pls") + ("Sunday Baroque (classical)" . "http://wshu.streamguys.org/wshu-baroque") + ("Underground 80s (somafm)" . "https://somafm.com/u80s256.pls") + ("Venice Classic Radio (classical)" . "https://www.veniceclassicradio.eu/live1/128.m3u") + ("WWOZ New Orleans (jazz/blues)" . "https://www.wwoz.org/listen/hi")))) + +(provide 'eradio-config) +;;; eradio-config.el ends here diff --git a/modules/erc-config.el b/modules/erc-config.el new file mode 100644 index 00000000..ddc8f737 --- /dev/null +++ b/modules/erc-config.el @@ -0,0 +1,109 @@ +;;; erc-config --- Preferences for Emacs Relay Chat (IRC Client) -*- lexical-binding: t; -*- +;; author Craig Jennings + +;;; Commentary: + +;; The main entry point into ERC is the C-c C-c keybinding. +;; Channels in erc-autojoin-channels-alist will open in background. +;; After connecting, switch to any ERC buffer with C-c C-c. +;; Quit current channel with C-c C-p; Quit ERC altogether with C-c C-q + +;;; Code: + +;; ------------------------------------ ERC ------------------------------------ +;; Emacs Relay Chat - an IRC client + +(use-package erc + :defer 1 + :ensure nil ;; built-in + :commands (erc erc-tls) + :bind + ;; the global keybinding + ("C-c I" . 'cj/erc-start-or-switch) + (:map erc-mode-map + ;; overrides erc-toggle-interpret-controls + ("C-c I" . 'cj/erc-start-or-switch)) + :hook + (erc-mode . emojify-mode) + :custom + (erc-modules + '(autojoin + button + completion + fill + irccontrols + list + log + match + move-to-prompt + noncommands + notifications + readonly + services + stamp)) + + (erc-autojoin-channels-alist + '(("libera" + "#erc" + "#ledger" + "#emacs" + "#emacs-social" + "#zfsonlinux" + "#systemcrafters" + "#org-mode"))) + + (erc-nick "craigjennings") + (erc-user-full-name user-whole-name) + (erc-use-auth-source-for-nickserv-password t) + (erc-kill-buffer-on-part t) + (erc-kill-queries-on-quit t) + (erc-kill-server-buffer-on-quit t) + (erc-fill-column 120) + (erc-fill-function 'erc-fill-static) + (erc-fill-static-center 20) + + :config + ;; use all text mode abbrevs in ercmode + (abbrev-table-put erc-mode-abbrev-table :parents (list text-mode-abbrev-table)) + + ;; create log directory if it doesn't exist + (setq erc-log-channels-directory (concat user-emacs-directory "erc/logs/")) + (if (not (file-exists-p erc-log-channels-directory)) + (mkdir erc-log-channels-directory t))) + +;; --------------------------------- ERC Image --------------------------------- +;; show inlined images (png/jpg/gif/svg) in erc buffers. + +(use-package erc-image + :defer 1 + :after erc + :config + (setq erc-image-inline-rescale 300) + (add-to-list 'erc-modules 'image) + (erc-update-modules)) + +;; -------------------------------- ERC Hl Nicks ------------------------------- +;; uniquely identify names in ERC + +(use-package erc-hl-nicks + :defer 1 + :after erc + :config + (add-to-list 'erc-modules 'hl-nicks)) + +;; -------------------------------- Connect IRC -------------------------------- +;; convenience function to auto-connect to irc.libera.chat + +(defun cj/erc-start-or-switch () + "Start ERC or switch to ERC buffer if it has started already." + (interactive) + (if (get-buffer "Libera.Chat") + (erc-switch-to-buffer) + (erc-tls + :server "irc.libera.chat" + :port 6697 + :nick "craigjennings" + :full-name user-whole-name))) + +(provide 'erc-config) +;;; erc-config.el ends here diff --git a/modules/eshell-vterm-config.el b/modules/eshell-vterm-config.el new file mode 100644 index 00000000..cf73205f --- /dev/null +++ b/modules/eshell-vterm-config.el @@ -0,0 +1,152 @@ +;;; eshell-vterm-config --- Settings for the Emacs Shell -*- lexical-binding: t; -*- +;; author Craig Jennings + +;;; Commentary: + +;; ESHELL +;; - Eshell is useful as a REPL +;; - Redirect to the kill ring : ls > /dev/kill +;; - Redirect to the clioboard : ls > /dev/clip +;; - Redirect to a buffer : ls > # +;; - Use elisp functions : write your own "detox" command in elisp +;; : then use it in eshell +;; - cd to remote directories : cd /sshx:c@cjennings.net:/home/cjennings +;; : and take all the elisp functionality remotely +;; : including Dired or Magit on a remote server + +;; VTERM +;; At the moment, vterm behaves like a real terminal. For most keys, vterm will +;; just send them to the process that is currently running. So, C-a may be +;; beginning-of-the-line in a shell, or the prefix key in a screen session. + +;; If you enter vterm-copy-mode C-c C-t or , the buffer will become a normal +;; Emacs buffer. You can then use your navigation keys, select rectangles, etc. +;; When you press RET, the region will be copied and you'll be back in a working +;; terminal session. + +;; ANSI-TERM & TERM +;; I haven't yet found a need for term or ansi-term in my workflows, so I leave +;; them with their default configurations. + +;;; Code: + +;; ------------------------------ Eshell ----------------------------- +;; the Emacs shell. + +(use-package eshell + :ensure nil ;; built-in + :defer .5 + :config + ;; for plan 9 smart shell functionality + (require 'em-smart) + (setq eshell-where-to-jump 'begin) + (setq eshell-review-quick-commands nil) + (setq eshell-smart-space-goes-to-end t) + + (setq eshell-banner-message "") + (setq eshell-scroll-to-bottom-on-input 'all) + (setq eshell-error-if-no-glob t) + (setq eshell-hist-ignoredups t) + (setq eshell-save-history-on-exit t) + (setq eshell-prefer-lisp-functions nil) + (setq eshell-destroy-buffer-when-process-dies t) + + (add-to-list 'eshell-modules-list 'eshell-tramp) + + (add-hook 'eshell-hist-mode-hook + (lambda () + (define-key eshell-hist-mode-map (kbd "") 'previous-line) + (define-key eshell-hist-mode-map (kbd "") 'next-line))) + + (add-hook 'eshell-mode-hook + (lambda () + (add-to-list 'eshell-visual-commands '("lf" "ranger" "tail" "htop" "gotop" "mc" "ncdu" "top")) + (add-to-list 'eshell-visual-subcommands '("git" "log" "diff" "show")) + (add-to-list 'eshell-visual-options '("git" "--help" "--paginate")) + + ;; aliases + (eshell/alias "clear" "clear 1") ;; leaves the prompt at the top of the window + (eshell/alias "e" "find-file $*") + (eshell/alias "gocj" "cd /sshx:cjennings@cjennings.net:/var/cjennings/") + (eshell/alias "gosb" "cd /sshx:cjennings@wolf.usbx.me:/home/cjennings/") + (eshell/alias "gowolf" "cd /sshx:cjennings@wolf.usbx.me:/home/cjennings/") + (eshell/alias "v" "eshell-exec-visual $*") + (eshell/alias "ff" "find-file-other-window $*") + (eshell/alias "f" "cj/eshell-find-using-dired $1") + (eshell/alias "r" "ranger") + (eshell/alias "em" "find-file $*") + (eshell/alias "emacs" "find-file $*") + (eshell/alias "ll" "ls -l")))) + +(defun cj/eshell-find-using-dired (file-pattern) + "Find a file FILE-PATTERN' using 'find-name-dired'." + (let ((escaped-pattern (regexp-quote file-pattern))) + (find-name-dired . escaped-pattern))) + +(defun cj/eshell-delete-window-on-exit () + "Close the eshell window when exiting." + (when (not (one-window-p)) + (delete-window))) +(advice-add 'eshell-life-is-too-much :after 'cj/eshell-delete-window-on-exit) + +(use-package eshell-toggle + :after eshell + :custom + (eshell-toggle-size-fraction 3) + (eshell-toggle-run-command nil) + (eshell-toggle-init-function #'eshell-toggle-init-eshell) + :bind + ("" . eshell-toggle)) + +(use-package xterm-color + :defer .5 + :after eshell + :hook (eshell-before-prompt-hook . (lambda () + (setq xterm-color-preserve-properties t))) + :config + (add-to-list 'eshell-preoutput-filter-functions 'xterm-color-filter) + (setq eshell-output-filter-functions (remove 'eshell-handle-ansi-color eshell-output-filter-functions)) + (setenv "TERM" "xterm-256color")) + +;; ------------------------------ Vterm ------------------------------ +;; faster and highly dependable, but not extensible + +(use-package vterm + :defer .5 + :commands (vterm vterm-other-window) + :init + (setq vterm-always-compile-module t) + + (defun cj/turn-off-chrome-for-vterm () + (hl-line-mode -1) + (display-line-numbers-mode -1)) + + :hook (vterm-mode . cj/turn-off-chrome-for-vterm) + :bind + (:map vterm-mode-map + ("C-y" . vterm-yank) + ("C-p" . vterm-copy-mode) + ("" . vterm-copy-mode)) + :custom + (vterm-kill-buffer-on-exit t) + (vterm-max-scrollback 100000)) + +(use-package vterm-toggle + :defer .5 + :bind + ("C-" . vterm-toggle) + :config + (setq vterm-toggle-fullscreen-p nil) + (add-to-list 'display-buffer-alist + '((lambda (buffer-or-name _) + (let ((buffer (get-buffer buffer-or-name))) + (with-current-buffer buffer + (or (equal major-mode 'vterm-mode) + (string-prefix-p vterm-buffer-name (buffer-name buffer)))))) + (display-buffer-reuse-window display-buffer-at-bottom) + (dedicated . t) ;dedicated is supported in Emacs 27+ + (reusable-frames . visible) + (window-height . 0.3)))) + +(provide 'eshell-vterm-config) +;;; eshell-vterm-config.el ends here. diff --git a/modules/eww-config.el b/modules/eww-config.el new file mode 100644 index 00000000..f0ee51a6 --- /dev/null +++ b/modules/eww-config.el @@ -0,0 +1,28 @@ +;;; eww-config --- EWW Text Browser Settings -*- lexical-binding: t; -*- +;; author Craig Jennings + +;;; Commentary: + +;;; Code: + +;; ------------------------------------ EWW ------------------------------------ + +(use-package eww + :ensure nil ;; built-in + :bind + ("M-E" . eww) + (:map eww-mode-map + ("<" . eww-back-url) ;; in addition to 'l' + (">" . eww-forward-url) ;; in addition to 'n' + ("i" . eww-toggle-images) + ("o" . eww-open-in-new-buffer)) + :config + (setq shr-use-colors nil) ;; respect colors in the html + (setq shr-bullet "• ") ;; unordered lists use bullet glyph + (setq shr-folding-mode t) + (setq eww-search-prefix "https://duckduckgo.com/html?q=") ;; use Duck Duck Go as search engine + (setq url-cookie-file "~/.local/share/cookies.txt") + (setq url-privacy-level '(email agent lastloc))) ;; don't send any info listed here + +(provide 'eww-config) +;;; eww-config.el ends here diff --git a/modules/flycheck-config.el b/modules/flycheck-config.el new file mode 100644 index 00000000..1bad4cbd --- /dev/null +++ b/modules/flycheck-config.el @@ -0,0 +1,47 @@ +;;; flycheck-config --- Syntax/Grammar Check -*- lexical-binding: t; -*- +;; author Craig Jennings + +;;; Commentary: + +;;; Code: + +(defun cj/prose-helpers-on () + "Ensure that abbrev, flyspell, and flycheck are on." + (interactive) + (if (not (abbrev-mode)) + (abbrev-mode)) + (flyspell-on-for-buffer-type) + (if (not (flycheck-mode)) + (flycheck-mode))) + +;;;; ---------------------------------- Linting -------------------------------- + +(use-package flycheck + :defer .5 + :hook (sh-mode emacs-lisp-mode) + :bind ("C-; ?" . flycheck-list-errors) + :config + ;; don't warn about double-spaces after period. + (setq-default checkdoc-arguments '("sentence-end-double-space" nil "warn-escape" nil)) + + ;; proselint must be installed via the OS + (flycheck-define-checker proselint + "A linter for prose." + :command ("proselint" source-inplace) + :error-patterns + ((warning line-start (file-name) ":" line ":" column ": " + (id (one-or-more (not (any " ")))) + (message) line-end)) + :modes (text-mode markdown-mode gfm-mode)) + (add-to-list 'flycheck-checkers 'proselint)) + +;; ;; https://github.com/emacs-grammarly/flycheck-grammarly +;; (use-package flycheck-grammarly +;; :defer 1 +;; :after flycheck +;; :config +;; (with-eval-after-load 'flycheck +;; (flycheck-grammarly-setup))) + +(provide 'flycheck-config) +;;; flycheck-config.el ends here diff --git a/modules/flyspell-config.el b/modules/flyspell-config.el new file mode 100644 index 00000000..6109eebc --- /dev/null +++ b/modules/flyspell-config.el @@ -0,0 +1,191 @@ +;;; flyspell-config.el --- Spell Check Configuration -*- lexical-binding: t; -*- + +;;; Commentary: + +;; Spell-Checking: Flyspell +;; C-' is now my main interface for all spell checking. It checks one word, then +;; you may proceed to your next misspelling by selecting 'C-' again. + +;; Flyspell will automatically run in a mode that's appropriate for the buffer type +;; - if it's a programming mode, it will only check comments +;; - if in text mode, it will check everything +;; - otherwise it will turn off. +;; This check happens on every mode switch. + +;; If you want flyspell on in another mode (say fundamental mode), or you'd like it off +;; to keep it from distracting, you can toggle flyspell's state with 'C-c f' + +;; The nicest thing is that each spell correction creates an abbrev. This essentially is a shortcut +;; that expands that same misspelling to the correct spelling the next time it's typed. That +;; idea comes courtesy Artur Malabarba, and it's increased my overall typing speed. +;; +;; Use M-o to get to 'other options', including saving to your personal dictionary. + +;;; Code: + +;; ------------------------ Reclaim Flyspell Keybinding ------------------------ +;; remove one of flyspell's many greedy keyboard bindings +;; this is now used for my personal-keymap + +(eval-after-load "flyspell" + '(define-key flyspell-mode-map (kbd "C-;") nil)) + +;; --------------------------------- Flyspell -------------------------------- +;; there's some ispell settings here as well. + +(use-package flyspell + :defer .5 + :ensure nil ;; built-in + :config + (setq ispell-alternate-dictionary (concat user-emacs-directory "assets/english-words.txt")) + (setq ispell-dictionary "american") ; better for aspell + (setq ispell-extra-args '("--sug-mode=ultra" "-W" "3" "--lang=en_US")) + (setq ispell-list-command "--list") ;; in aspell "-l" means --list, not --lang + (setq ispell-local-dictionary "en_US") + (setq ispell-program-name "aspell") ;; use aspell instead of ispell + (setq ispell-local-dictionary-alist '(("en_US" "[[:alpha:]]" "[^[:alpha:]]" "['‘’]" + t ; Many other characters + ("-d" "en_US") nil utf-8))) + + (setq ispell-personal-dictionary + (concat sync-dir "aspell-personal-dictionary")) ;; personal directory goes with sync'd files + (setq flyspell-issue-message-flag nil) ;; don't print message for every word when checking + (add-to-list 'ispell-skip-region-alist '("^#+BEGIN_SRC" . "^#+END_SRC"))) ;; skip code blocks in org mode + + +(use-package flyspell-correct + :defer 1 + :after flyspell + :bind + (:map flyspell-mode-map + ("C-'" . cj/flyspell-then-abbrev))) ;; disallow other entries to spelling corrections + + +(use-package flyspell-correct-ivy + :defer 1 + :after (ivy flyspell-correct) + :init + (setq flyspell-correct-interface #'flyspell-correct-ivy)) ;; use ivy as the flyspell interface + + +;; ------------------------ Flyspell On For Buffer Type ------------------------ +;; check strings and comments in prog mode; everything in text mode + +(defun flyspell-on-for-buffer-type () + "Enable Flyspell appropriately for the major mode and check the current buffer. +If flyspell is already enabled, do nothing. If the mode is derived from +`prog-mode', enable `flyspell-prog-mode' so only strings and comments get +checked. If the buffer is text based `flyspell-mode' is enabled to check +all text. Otherwise, flyspell is turned off." + (interactive) + (if (not (symbol-value flyspell-mode)) ; if not already on + (progn + (if (derived-mode-p 'prog-mode) + (progn + (flyspell-prog-mode) + (flyspell-buffer))) + (if (derived-mode-p 'text-mode) + (progn + (flyspell-mode 1) + (flyspell-buffer)) + ;; else + (progn + (flyspell-mode 0)))))) +(add-hook 'after-change-major-mode-hook 'flyspell-on-for-buffer-type) +(add-hook 'find-file-hook 'flyspell-on-for-buffer-type) + +;; ---------------------------- Flyspell Then Abbrev --------------------------- +;; A stroke of genius from Artur Malabarba. +;; http://endlessparentheses.com/ispell-and-abbrev-the-perfect-auto-correct.html +;; Spell checks backwards (after you've noticed a typo). Corrects, then automatically +;; creates an abbrev to autocorrect the same misspelling. +;; Bound to C-; above. + +(setq save-abbrevs 'silently) +(setq-default abbrev-mode t) + +(defun cj/flyspell-then-abbrev (p) + "Call `ispell-word', then create an abbrev for it. +With prefix P, create local abbrev. Otherwise it will +be global." + (interactive "P") + (save-excursion + (if (flyspell-goto-previous-word (point)) + (let ((bef (downcase (or (thing-at-point 'word) + ""))) + aft) + (call-interactively 'flyspell-correct-at-point) + (setq aft (downcase + (or (thing-at-point 'word) ""))) + (unless (or (string= aft bef) + (string= aft "") + (string= bef "")) + (message "\"%s\" now expands to \"%s\" %sally" + bef aft (if p "loc" "glob")) + (define-abbrev + (if p local-abbrev-table global-abbrev-table) + bef aft))) + (message "Cannot find a misspelled word")))) + +(defun flyspell-goto-previous-word (position) + "Go to the first misspelled word that occurs before point. +But don't look beyond what's visible on the screen." + (interactive "d") + (let ((top (window-start)) + (bot (window-end))) + (save-restriction + (narrow-to-region top bot) + (overlay-recenter (point)) + (add-hook 'pre-command-hook + (function flyspell-auto-correct-previous-hook) t t) + (unless flyspell-auto-correct-previous-pos + ;; only reset if a new overlay exists + (setq flyspell-auto-correct-previous-pos nil) + (let ((overlay-list (overlays-in (point-min) position)) + (new-overlay 'dummy-value)) + ;; search for previous (new) flyspell overlay + (while (and new-overlay + (or (not (flyspell-overlay-p new-overlay)) + ;; check if its face has changed + (not (eq (get-char-property + (overlay-start new-overlay) 'face) + 'flyspell-incorrect)))) + (setq new-overlay (car-safe overlay-list)) + (setq overlay-list (cdr-safe overlay-list))) + ;; if nothing new exits new-overlay should be nil + (if new-overlay ;; the length of the word may change so go to the start + (setq flyspell-auto-correct-previous-pos + (overlay-start new-overlay))))) + (if (not flyspell-auto-correct-previous-pos) + nil + (goto-char flyspell-auto-correct-previous-pos) + t)))) + +;; ------------------------------ Flyspell Toggle ------------------------------ +;; easy toggling flyspell which also leverages the 'for-buffer-type' functionality. + +(defun flyspell-toggle () + "Turn Flyspell on if it is off, or off if it is on. +When turning on,it uses `flyspell-on-for-buffer-type' so code-vs-text is +handled appropriately." + (interactive) + (if (symbol-value flyspell-mode) + (progn ; flyspell is on, turn it off + (message "Flyspell off") + (flyspell-mode -1)) + ;; else - flyspell is off, turn it on + (progn + (flyspell-on-for-buffer-type) + (message "Flyspell on")))) +(global-set-key (kbd "C-c f") 'flyspell-toggle ) + + +;; -------------------------------- Ispell STFU -------------------------------- +;; tell ispell where to send it's spurious error messages + +(advice-add 'ispell-lookup-words :around + (lambda (orig &rest args) + (shut-up (apply orig args)))) + +(provide 'flyspell-config) +;;; flyspell-config.el ends here. diff --git a/modules/font-config.el b/modules/font-config.el new file mode 100644 index 00000000..a5ed2771 --- /dev/null +++ b/modules/font-config.el @@ -0,0 +1,200 @@ +;;; font-config --- Font Defaults and Related Functionality -*- lexical-binding: t; -*- +;; author: Craig Jennings + +;;; Commentary: + +;;; Code: + +;; ----------------------- Font Family And Size Selection ---------------------- +;; preset your fixed and variable fonts, then apply them to text as a set + +(use-package fontaine + :demand t + :bind + ("M-F" . fontaine-set-preset) + :config + (setq fontaine-font-families + '((default "Hack Nerd Font Mono" "Fira Code Nerd Font" "MonoLisa" "Codelia Ligatures" "JetBrains Mono Nerd Font") + (fixed-pitch "Fira Code Nerd Font" "Hack Nerd Font" "JetBrains Mono Nerd Font" "Liberation Mono") + (variable-pitch "Literata" "Novaletra Serif CF" "FiraGO" "Nimbus Roman" "Nimbus Sans"))) + (setq fontaine-presets + '( + (Hack-Only + :default-family "Hack Nerd Font Mono" + :variable-pitch-family "Hack Nerd Font Mono") + (Berkeley-Only + :default-family "Berkeley Mono" + :variable-pitch-family "Berkeley Mono") + (Merriweather-Only + :default-family "Merriweather" + :variable-pitch-family "Merriweather") + (MonoLisa-Novaletra + :default-family "MonoLisa" + :variable-pitch-family "Novaletra Serif CF") + (FiraCode-Literata + :default-family "Fira Code Nerd Font" + :variable-pitch-family "Literata") + (JetBrains0Lato + :default-family "JetBrains Mono" + :variable-pitch-family "Lato") + (Codelia-Only + :default-family "Codelia Ligatures") + (Liberation_Mono-Only + :default-family "Liberation Mono") + (t ;; shared fallback properties go here + :default-family "Berkeley Mono" + :default-weight regular + :default-height 130 + :fixed-pitch-family nil ;; falls back to :default-family + :fixed-pitch-weight nil ;; falls back to :default-weight + :fixed-pitch-height 1.0 + :fixed-pitch-serif-family nil ;; falls back to :default-family + :fixed-pitch-serif-weight nil ;; falls back to :default-weight + :fixed-pitch-serif-height 1.0 + :variable-pitch-family "Merriweather" + :variable-pitch-weight light + :variable-pitch-height 1.0 + :bold-family nil ;; use whatever the underlying face has + :bold-weight bold + :italic-family nil + :italic-slant italic + :line-spacing nil)))) + +(with-eval-after-load 'fontaine + (defun cj/apply-font-settings-after-ui-creation() + "Apply font settings when Emacsclient is run in daemon mode. +Emacsclient connects to a headless Emacs daemon, when no UI +exists yet to apply these settings. This solution requests Emacsclient +to apply the default font settings after the server creates a frame. +Note that server-after-make-frame-hook is available only in Emacs 27+." + (interactive) + (fontaine-set-preset 'Berkeley-Only) + (if (daemonp) + (remove-hook 'server-after-make-frame-hook #'cj/apply-font-settings-after-ui-creation))) + + (if (daemonp) + (add-hook 'server-after-make-frame-hook #'cj/apply-font-settings-after-ui-creation) + (cj/apply-font-settings-after-ui-creation))) + +;; ----------------------------- Font Install Check ---------------------------- +;; convenience function to indicate whether a font is available by name. + +(defun cj/font-installed-p (font-name) + "Check if font with FONT-NAME is available." + (if (find-font (font-spec :name font-name)) + t + nil)) + +;; ------------------------------- All The Icons ------------------------------- +;; icons made available through fonts + +(use-package all-the-icons + :demand t + :config + (when (and (not (cj/font-installed-p "all-the-icons")) + (window-system)) + (all-the-icons-install-fonts t))) + +(use-package all-the-icons-nerd-fonts + :after all-the-icons + :demand t + :config + (all-the-icons-nerd-fonts-prefer)) + +;; ----------------------------- Emoji Fonts Per OS ---------------------------- +;; these are in reverse order of priority + +(when (member "Segoe UI Emoji" (font-family-list)) + (set-fontset-font + t 'symbol (font-spec :family "Segoe UI Emoji") nil 'prepend)) +(when (member "Apple Color Emoji" (font-family-list)) + (set-fontset-font + t 'symbol (font-spec :family "Apple Color Emoji") nil 'prepend)) +(when (member "Noto Color Emoji" (font-family-list)) + (set-fontset-font + t 'symbol (font-spec :family "Noto Color Emoji") nil 'prepend)) + +;; ---------------------------------- Emojify ---------------------------------- +;; converts emoji identifiers into emojis; allows for easy emoji entry. + +(use-package emojify + :hook ((erc-mode . emojify-mode) + (org-mode . emojify-mode) + (prog-mode . (lambda () (emojify-mode -1))) + (gptel-mode . (lambda () (emojify-mode -1)))) + :custom + (emojify-download-emojis-p t) ;; don't ask, just download emojis + :bind + ("C-c e i" . emojify-insert-emoji) ;; emoji insert + ("C-c e l" . emojify-list-emojis) ;; emoji list + :config + (setq emojify-show-help nil) + (setq emojify-point-entered-behaviour 'uncover) + (setq emojify-display-style 'image) + (setq emojify-emoji-styles '(ascii unicode github))) + +;; -------------------------- Display Available Fonts -------------------------- +;; display all available fonts on the system in a side panel + +(defun cj/display-available-fonts () + "Display a list of all font faces with sample text in another read-only buffer." + (interactive) + (pop-to-buffer "*Available Fonts*" '(display-buffer-in-side-window . ((side . right)(window-width . fit-window-to-buffer)))) + (let ((font-list (font-family-list))) + (setq font-list (remove-duplicates (cl-sort font-list 'string-lessp :key 'downcase))) + (with-current-buffer "*Available Fonts*" + (erase-buffer) + (dolist (font-family font-list) + (insert (propertize (concat font-family) 'face `((:foreground "Light Blue" :weight bold)))) + (insert (concat "\n"(propertize "Regular: "))) + (insert (propertize (concat "The quick brown fox jumps over the lazy dog I 1 l ! : ; . , 0 O o [ { ( ) } ] ?") + 'face `((:family, font-family)))) + (insert (concat "\n" (propertize "Bold: "))) + (insert (propertize (concat "The quick brown fox jumps over the lazy dog I 1 l ! : ; . , 0 O o [ { ( ) } ] ?") + 'face `((:family, font-family :weight bold)))) + (insert (concat "\n" (propertize "Italic: "))) + (insert (propertize (concat "The quick brown fox jumps over the lazy dog I 1 l ! : ; . , 0 O o [ { ( ) } ] ?") + 'face `((:family, font-family :slant italic)))) + (insert (concat "\n\n")))) + (move-to-window-line 0) + (special-mode))) + +(global-set-key (kbd "C-z F") 'cj/display-available-fonts) + +;; ----------------------- Increase / Decrease Font Size ----------------------- +;; make it easy to enlarge or shrink font sizes with keybindings + +(setq text-scale-mode-step 1.08) +(global-set-key (kbd "C-+") 'text-scale-increase) +(global-set-key (kbd "C-=") 'text-scale-increase) +(global-set-key (kbd "C-_") 'text-scale-decrease) +(global-set-key (kbd "C--") 'text-scale-decrease) + +;; --------------------------------- Ligatures --------------------------------- ' +;; fancy programming glyphs make code easier to read + +(use-package ligature + :defer 1 + :config + ;; Enable the www ligature in every possible major mode + (ligature-set-ligatures 't '("www")) + ;; Enable traditional ligature support in eww, if `variable-pitch' face supports it + (ligature-set-ligatures 'eww-mode '("ff" "fi" "ffi")) + ;; Enable ligatures in markdown mode + (ligature-set-ligatures 'markdown-mode '(("=" (rx (+ "=") (? (| ">" "<")))) + ("-" (rx (+ "-"))))) + ;; Enable ligatures in programming modes + (ligature-set-ligatures 'prog-mode '("www" "**" "***" "**/" "*>" "*/" "\\\\" "\\\\\\" "{-" "::" + ":::" ":=" "!!" "!=" "!==" "-}" "----" "-->" "->" "->>" + "-<" "-<<" "-~" "#{" "#[" "##" "###" "####" "#(" "#?" "#_" + "#_(" ".-" ".=" ".." "..<" "..." "?=" "??" ";;" "/*" "/**" + "/=" "/==" "/>" "//" "///" "&&" "||" "||=" "|=" "|>" "^=" "$>" + "++" "+++" "+>" "=:=" "==" "===" "==>" "=>" "=>>" "<=" + "=<<" "=/=" ">-" ">=" ">=>" ">>" ">>-" ">>=" ">>>" "<*" + "<*>" "<|" "<|>" "<$" "<$>" "