diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/ai-config.el | 202 | ||||
| -rw-r--r-- | modules/auth-config.el | 43 | ||||
| -rw-r--r-- | modules/custom-functions.el | 264 | ||||
| -rw-r--r-- | modules/diff-config.el | 100 | ||||
| -rw-r--r-- | modules/eshell-vterm-config.el | 79 | ||||
| -rw-r--r-- | modules/font-config.el | 5 | ||||
| -rw-r--r-- | modules/games-config.el | 40 | ||||
| -rw-r--r-- | modules/help-utils.el | 5 | ||||
| -rw-r--r-- | modules/keybindings.el | 4 | ||||
| -rw-r--r-- | modules/latex-config.el | 14 | ||||
| -rw-r--r-- | modules/ledger-config.el | 2 | ||||
| -rw-r--r-- | modules/mail-config.el | 135 | ||||
| -rw-r--r-- | modules/org-babel-config.el | 11 | ||||
| -rw-r--r-- | modules/org-config.el | 1 | ||||
| -rw-r--r-- | modules/org-contacts-config.el | 27 | ||||
| -rw-r--r-- | modules/org-export-config.el | 111 | ||||
| -rw-r--r-- | modules/system-defaults.el | 2 | ||||
| -rw-r--r-- | modules/ui-config.el | 141 | ||||
| -rw-r--r-- | modules/ui-navigation.el | 90 |
19 files changed, 787 insertions, 489 deletions
diff --git a/modules/ai-config.el b/modules/ai-config.el index 241ee42c..0db11009 100644 --- a/modules/ai-config.el +++ b/modules/ai-config.el @@ -8,62 +8,128 @@ ;; - Launch GPTel via F9 or C-h g t, and chat with the AI in the side window. ;; Remember that sending the message requires C-<return>. - - ;; Note that you can save a file, then turn on gptel-mode to resume your ;; conversation. - ;;; ------------------------------- Directives --------------------------------- (defvar default-directive "You are a large language model living in Emacs. You understand philosophy, critical theory, and comparative - literature at a university graduate student level. You are concise and always provide references to source material.") - -(defvar code-only-directive - "You are a large language model living in Emacs. You are an expert in emacs-lisp, Python, Golang, and Shell scripting. - You encourage unit testing and always provide unit tests when you provide code.") - -(defvar writing-directive - "You are a large language model and a writing assistant. Respond concisely.") + literature at a university graduate student level. You have deep knowledge of the You are concise and always provide references to source material + you refer to. You are a good-natured conversation partner who asks thoughtful questions.") + +(defvar contractor-directive + "I want you to act as an assistant who has deep understanding of construction, remodeling, design, and architecture. + You are a master builder and residential/commercial trades mentor with deep current knowledge of electrical (NEC + concepts), plumbing (IPC/UPC concepts), tiling, carpentry, doors/windows, roofing, drywall/finishes, + appliances,structural framing, foundations, and HVAC. Audience: an intelligent DIYer or junior tradesperson. Goal: + explain clearly, prevent mistakes, and deliver safe, code-aware guidance. Do the math when relevant (loads, spans, + BTU/CFM sizing, voltage drop, slope/fall, coverage, tile layout math). Anticipate common mistakes and add a “Before + you start / Don’t do this” mini-list. Reference standards precisely but briefly (e.g., “NEC 210.8 GFCI in bathrooms” + or “typical 1/4 in. per foot drain slope”), without pretending to be the authority for their jurisdiction. Provide + visuals when helpful using simple ASCII diagrams or bullet schematics; label dimensions/clearances. Be plain-spoken, + specific, and unambiguous. Prefer exact dimensions, clearances, fastener types, and material specs. Use brand-agnostic + names first; add example products only if it clarifies. If info is missing, state reasonable assumptions and proceed + (note them). Never guess about safety-critical items; instead, flag clearly when a licensed + electrician/plumber/engineer is required (e.g., service panel work, structural alterations). Avoid fluff. No + motivational talk; just practical guidance.") + +(defvar coder-directive + "You are an expert in emacs-lisp, Python, Golang, and Shell scripting. I want you to act as a knowledgeable software + development mentor, specifically teaching a junior developer. Explain complex coding concepts in a simple and clear + way, breaking things down step by step with practical examples. Use analogies and practical advice to ensure + understanding. Anticipate common mistakes and provide tips to avoid them. Provide precise answers, avoiding ambiguous + responses. You encourage unit testing and always provide unit tests when you provide code.") + +(defvar reviewer-directive + "I want you to act as a code reviewer who is experienced developer in the given code language. I will provide you with + the code block or methods or code file along with the code language name, and I would like you to review the code and + share the feedback, suggestions and alternative recommended approaches. Please write explanations behind the feedback + or suggestions or alternative approaches.") + +(defvar qa-directive + "Act as an expert software engineer in test with strong experience in the given code language who is working with a + junior developer on their code. Your job is to write tests for the functionality and performance of the code provided. + I will pass you code and you have to analyze it and reply to me with the test cases and the tests code. You will also + identify any issues or bugs you encounter, write tests that would uncover the bug if possible, and provide + recommendations for improvement.") + +(defvar proofreader-directive + "I want you act as a proofreader. I will provide you some text and I would like you to review it for any spelling, + grammar, or punctuation errors. Once you have finished reviewing the text, provide me with any necessary corrections + or suggestions for improving the text.") + +(defvar email-directive + "I want you to act as an email writing assistant. I will provide you some direction on what the +email should consist of, the tone of the email, and my guess as to the DISC profile of the email recipient. You will use +the DISC profile information to guide the tone and wording of the email. However, always lean towards simple, +straightforward, and clear language with little ambiguity. Ask questions to make any part of the email clearer if + needed.") + +(defvar historian-directive + "I want you to act as a historian and political scientist. You will research and analyze cultural, economic, political, and social events in the past, collect data from primary sources, and use the information to explain what happened during various periods of history, identify historical patterns, and explain plainly how the events of history inform our times today.") (defvar chat-directive - "You are a large language model and a funny conversation partner who asks good questions.") + "I want you to act as an old friend and highly intelligent person who is good at conversation. You are deeply + knowledgeable about academic philosophy and can discuss philosophical topics at the PhD level. When you do, you often + indicate the book or article relevant to the topic you discuss. You are very well + educated in history. You have a kind personality. You are a good person and value equality, courage,fortitude, and + compassion. You ask very good questions. You encourage people to improve themselves and you believe in them.") + +(defvar accountant-directive + "You are an experienced construction project accountant. Your job is to review budgets, invoices, and contracts for + accuracy and fairness. You carefully examine line items, materials, labor costs, markups, change orders, and + subcontractor charges. You are detail-oriented and skeptical: always check whether values are consistent with typical + construction practices and market prices. When something looks unusual, inflated, or inconsistent, you highlight it + clearly and explain why. You break down complex totals into simple per-unit costs (e.g., per square foot, per hour,per + unit of material). You provide step-by-step reasoning, showing the math and assumptions you use. You anticipate common + issues like: Double charging, Hidden or excessive markups, Material costs far above retail averages, Labor charges + that don’t match reasonable hourly rates or hours worked, Items billed but not in scope of contract When you find + potential overcharges, you suggest specific follow-up questions I should ask the contractor. + Respond in a clear, structured format: + * Summary of findings + * Line-by-line review with calculations + * Potential errors or overcharges + * Recommended questions or actions") ;;; ------------------------------ Toggle GPTel -------------------------------- - (defun cj/toggle-gptel () "Toggle the visibility of the ChatGPT buffer, and when shown place point at its end." (interactive) (let* ((buf-name "*AI-Assistant*") - (buffer (get-buffer buf-name)) - (win (and buffer (get-buffer-window buffer)))) - (if win - ;; If it's already visible, just close it - (delete-window win) - ;; Otherwise ensure the buffer exists - (unless buffer - (gptel buf-name gptel-model)) - (setq buffer (get-buffer buf-name)) - ;; Display in a side window, select it, and move point to end - (setq win - (display-buffer-in-side-window - buffer - '((side . right) - (window-width . 0.4)))) - (select-window win) - (with-current-buffer buffer - (goto-char (point-max)))))) + (buffer (get-buffer buf-name)) + (win (and buffer (get-buffer-window buffer)))) + (if win + ;; If it's already visible, just close it + (delete-window win) + ;; Otherwise ensure the buffer exists + (unless buffer + (gptel buf-name gptel-model)) + (setq buffer (get-buffer buf-name)) + ;; Display in a side window, select it, and move point to end + (setq win + (display-buffer-in-side-window + buffer + '((side . right) + (window-width . 0.4)))) + (select-window win) + (with-current-buffer buffer + (goto-char (point-max)))))) + +;; retry if authinfo.gpg authentication fails +(advice-add 'cj/toggle-gptel :before #'cj/ensure-auth-before) ;; ------------------------- GPTel Config And AI-Keymap ------------------------ (defvar ai-keymap (let ((map (make-sparse-keymap))) - (define-key map (kbd "t") #'cj/toggle-gptel) - (define-key map (kbd "c") #'cj/gptel-clear-buffer) - map) + (define-key map (kbd "t") #'cj/toggle-gptel) + (define-key map (kbd "c") #'cj/gptel-clear-buffer) + (define-key map (kbd "m") #'gptel-menu) + (define-key map (kbd "p") #'gptel-system-prompt) + map) "Keymap for AI commands, bound to C-h g…") (global-set-key (kbd "C-h g") ai-keymap) @@ -73,8 +139,9 @@ :bind ("<f9>" . cj/toggle-gptel) (:map gptel-mode-map - ("C-<return>" . gptel-send)) + ("C-<return>" . gptel-send)) :custom + (gptel-default-directive 'default-directive) (gptel-default-mode 'org-mode) (gptel-expert-commands t) (gptel-track-media t) @@ -85,52 +152,59 @@ :config ;; Directives (setq gptel-directives - `((default . ,default-directive) - (code-only . ,code-only-directive) - (writing . ,writing-directive) - (chat . ,chat-directive))) + `((default . ,default-directive) + (coder . ,coder-directive) + (chat . ,chat-directive) + (accountant . ,accountant-directive) + (proofreader . ,proofreader-directive) + (email . ,email-directive) + (qa . ,qa-directive) + (contractor . ,contractor-directive) + (historian . ,historian-directive) + (reviewer . ,reviewer-directive) + )) ;; Dynamic user prefix for org-mode heading (string, refreshed just before send) (defun cj/gptel--fresh-org-prefix () - (concat "*** cj " (format-time-string "[%Y-%m-%d %H:%M:%S]") "\n")) + (concat "*** cj " (format-time-string "[%Y-%m-%d %H:%M:%S]") "\n")) ;; Initialize as a string (GPTel expectation) (setf (alist-get 'org-mode gptel-prompt-prefix-alist) - (cj/gptel--fresh-org-prefix)) + (cj/gptel--fresh-org-prefix)) ;; Refresh immediately before each send for accurate timestamp (defun cj/gptel--refresh-org-prefix (&rest _) - (setf (alist-get 'org-mode gptel-prompt-prefix-alist) - (cj/gptel--fresh-org-prefix))) + (setf (alist-get 'org-mode gptel-prompt-prefix-alist) + (cj/gptel--fresh-org-prefix))) (advice-add 'gptel-send :before #'cj/gptel--refresh-org-prefix) ;; AI header on each reply: (e.g. "*** ChatGPT: <model> [timestamp]") (defun cj/gptel-backend-and-model () - "Return backend, model, and timestamp as a single string." - (let* ((backend (pcase (bound-and-true-p gptel-backend) - ((and v (pred vectorp)) (aref v 1)) ;; display name if vector - (_ "ChatGPT"))) - (model (format "%s" (or (bound-and-true-p gptel-model) ""))) - (ts (format-time-string "[%Y-%m-%d %H:%M:%S]"))) - (format "%s: %s %s" backend model ts))) + "Return backend, model, and timestamp as a single string." + (let* ((backend (pcase (bound-and-true-p gptel-backend) + ((and v (pred vectorp)) (aref v 1)) ;; display name if vector + (_ "ChatGPT"))) + (model (format "%s" (or (bound-and-true-p gptel-model) ""))) + (ts (format-time-string "[%Y-%m-%d %H:%M:%S]"))) + (format "%s: %s %s" backend model ts))) (defun cj/gptel-insert-model-heading (response-begin-pos _response-end-pos) - "Insert an Org heading for the AI reply at RESPONSE-BEGIN-POS." - (save-excursion - (goto-char response-begin-pos) - (insert (format "*** %s\n" (cj/gptel-backend-and-model))))) + "Insert an Org heading for the AI reply at RESPONSE-BEGIN-POS." + (save-excursion + (goto-char response-begin-pos) + (insert (format "*** %s\n" (cj/gptel-backend-and-model))))) (defun cj/gptel-clear-buffer () - "Erase the contents of the *AI-Assistant* buffer, re-insert the org heading, and message." - (interactive) - (let ((buf (get-buffer "*AI-Assistant*"))) - (if (not buf) - (message "No AI buffer found") - (with-current-buffer buf - (erase-buffer) - ;; re-insert the user heading with fresh timestamp - (insert (cj/gptel--fresh-org-prefix)) - (message "AI buffer cleared and heading reset"))))) + "Erase the contents of the *AI-Assistant* buffer, re-insert the org heading, and message." + (interactive) + (let ((buf (get-buffer "*AI-Assistant*"))) + (if (not buf) + (message "No AI buffer found") + (with-current-buffer buf + (erase-buffer) + ;; re-insert the user heading with fresh timestamp + (insert (cj/gptel--fresh-org-prefix)) + (message "AI buffer cleared and heading reset"))))) ;; Hook is called with (BEG END); add our per-reply heading (add-hook 'gptel-post-response-functions #'cj/gptel-insert-model-heading) diff --git a/modules/auth-config.el b/modules/auth-config.el index 43849f20..5dbce2ef 100644 --- a/modules/auth-config.el +++ b/modules/auth-config.el @@ -38,6 +38,47 @@ :config (setq epg-gpg-program "gpg2")) ;; force use gpg2 (not gpg v.1) +;; ----------------------------- Ensure-Auth-Before ---------------------------- + +(defun cj/ensure-auth-before (&rest _args) + "Ensure .authinfo.gpg is unlocked before calling the real function." + (cj/ensure-auth)) + +(defun cj/ensure-auth () + "Make sure .authinfo.gpg is decrypted (loops on failure)." + (interactive) + (auth-source-search :max 1)) + +(with-eval-after-load 'auth-source + (defun cj/auth-source-search-retry (orig-fun &rest args) + "Advice around `auth-source-search' to loop until we get non-nil." + (let (res) + (while (not (setq res (apply orig-fun args))) + ;; user hit RET or wrong passphrase → kill agent & retry + (message "Auth failed or cancelled; killing gpg-agent and retrying…") + (start-process "gpgconf-kill-gpg-agent" nil + "gpgconf" "--kill" "gpg-agent") + (sleep-for 0.5)) + res)) + + (advice-add 'auth-source-search :around #'cj/auth-source-search-retry)) + +;; Example: run it before your GPT toggle +;;(advice-add 'cj/toggle-gptel :before #'cj/ensure-auth-before) + +;; Example: before mu4e actually sends a message +;;(advice-add 'smtpmail-send-it ; or `mu4e~proc-send` if you prefer +;; :before #'cj/ensure-auth-before) + +;; Example: before Tramp prompts for a password +;; (advice-add 'tramp-read-passwd ; wherever Tramp reads your passphrase +;; :before #'cj/ensure-auth-before) + +;; ;; Example: before Dirvish opens a remote directory +;; (advice-add 'dirvish-find-file ; or the exact entry-point you use +;; :before #'cj/ensure-auth-before) + + (provide 'auth-config) ;;; auth-config.el ends here. @@ -50,7 +91,7 @@ (ert-deftest auth-config/authinfo-file-exists () "Verify that `authinfo-file` actually exists on disk." (should (and (stringp authinfo-file) - (file-exists-p authinfo-file)))) + (file-exists-p authinfo-file)))) (ert-deftest auth-config/gpg2-is-on-path () "Verify that the `gpg2` executable is on the user’s PATH." diff --git a/modules/custom-functions.el b/modules/custom-functions.el index 846475cb..16b68c51 100644 --- a/modules/custom-functions.el +++ b/modules/custom-functions.el @@ -13,6 +13,19 @@ (use-package expand-region :demand t) + +;; -------------------------------- Copy Buffer -------------------------------- +;; copies the contents of the buffer to the kill ring + +(defun cj/copy-buffer () + "Copy the entire contents of the current buffer to the kill ring. +Point and mark are left exactly where they were. No transient region +is created. A message is displayed when done." + (interactive) + (let ((contents (buffer-substring-no-properties (point-min) (point-max)))) + (kill-new contents) + (message "Buffer contents copied to kill ring"))) + ;; ------------------------ Jump To Matching Parentheses ----------------------- ;; shows you the other matching parenthesis by jumping to it. @@ -116,13 +129,13 @@ If no region is selected, operate on the whole buffer." "Reformat the region or the entire buffer." (interactive) (let ((start-pos (if (use-region-p) (region-beginning) (point-min))) - (end-pos (if (use-region-p) (region-end) (point-max)))) - (save-excursion - (save-restriction - (narrow-to-region start-pos end-pos) - (delete-trailing-whitespace) - (indent-region (point-min) (point-max)) - (untabify (point-min) (point-max)))))) + (end-pos (if (use-region-p) (region-end) (point-max)))) + (save-excursion + (save-restriction + (narrow-to-region start-pos end-pos) + (delete-trailing-whitespace) + (indent-region (point-min) (point-max)) + (untabify (point-min) (point-max)))))) ;; ------------------- Remove Leading And Trailing Whitespace ------------------ ;; removes leading and trailing whitespace on line, region, or buffer. @@ -135,20 +148,20 @@ If called with a prefix argument (C-u), remove throughout the entire buffer. START and END define region." (interactive "r") (let (deactivate-mark) - (if (or (use-region-p) current-prefix-arg) - (save-restriction - (if current-prefix-arg - (progn (widen) (setq start (point-min) end (point-max))) - (narrow-to-region start end)) - (goto-char (point-min)) - (while (re-search-forward "^[ \t]+" nil t) (replace-match "")) - (goto-char (point-min)) - (while (re-search-forward "[ \t]+$" nil t) (replace-match ""))) - (beginning-of-line) - (while (looking-at "^[ \t]+") (replace-match "")) - (end-of-line) - (while (re-search-backward "[ \t]+$" (line-beginning-position) t) - (replace-match ""))))) + (if (or (use-region-p) current-prefix-arg) + (save-restriction + (if current-prefix-arg + (progn (widen) (setq start (point-min) end (point-max))) + (narrow-to-region start end)) + (goto-char (point-min)) + (while (re-search-forward "^[ \t]+" nil t) (replace-match "")) + (goto-char (point-min)) + (while (re-search-forward "[ \t]+$" nil t) (replace-match ""))) + (beginning-of-line) + (while (looking-at "^[ \t]+") (replace-match "")) + (end-of-line) + (while (re-search-backward "[ \t]+$" (line-beginning-position) t) + (replace-match ""))))) ;; --------------------------- Arrayify / Unarrayify --------------------------- ;; unquoted text on newlines to quoted comma separated strings (and vice-versa). @@ -183,22 +196,22 @@ START and END indicate the region selected." "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")) + (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))) + (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. @@ -232,7 +245,7 @@ User is prompted for the optional descriptor." (unless (bolp) (insert "\n")) (insert "```\n") (goto-char start) - (insert (concat "```" lang "\n"))))) + (insert (concat "```" lang "\n"))))) ;; ------------------------ Insert Around Word Or Region ----------------------- @@ -240,22 +253,22 @@ User is prompted for the optional descriptor." "Prompt for a string, insert it before and after the word at point or selected region." (interactive) (let ((str (read-string "Enter a string: ")) - (regionp (use-region-p))) - (save-excursion - (if regionp - (let ((beg (region-beginning)) - (end (region-end))) - (goto-char end) - (insert str) - (goto-char beg) - (insert str)) - (if (thing-at-point 'word) - (let ((bounds (bounds-of-thing-at-point 'word))) - (goto-char (cdr bounds)) - (insert str) - (goto-char (car bounds)) - (insert str)) - (message "Can't insert around. No word at point and no region selected.")))))) + (regionp (use-region-p))) + (save-excursion + (if regionp + (let ((beg (region-beginning)) + (end (region-end))) + (goto-char end) + (insert str) + (goto-char beg) + (insert str)) + (if (thing-at-point 'word) + (let ((bounds (bounds-of-thing-at-point 'word))) + (goto-char (cdr bounds)) + (insert str) + (goto-char (car bounds)) + (insert str)) + (message "Can't insert around. No word at point and no region selected.")))))) (global-set-key (kbd "C-; i a") 'cj/insert-around-word-or-region) @@ -265,22 +278,22 @@ User is prompted for the optional descriptor." "Prompt for a string, insert it before and after the word at point or selected region." (interactive) (let ((str (read-string "Enter a string: ")) - (regionp (use-region-p))) - (save-excursion - (if regionp - (let ((beg (region-beginning)) - (end (region-end))) - (goto-char end) - (insert str) - (goto-char beg) - (insert str)) - (if (thing-at-point 'word) - (let ((bounds (bounds-of-thing-at-point 'word))) - (goto-char (cdr bounds)) - (insert str) - (goto-char (car bounds)) - (insert str)) - (message "Can't insert around. No word at point and no region selected.")))))) + (regionp (use-region-p))) + (save-excursion + (if regionp + (let ((beg (region-beginning)) + (end (region-end))) + (goto-char end) + (insert str) + (goto-char beg) + (insert str)) + (if (thing-at-point 'word) + (let ((bounds (bounds-of-thing-at-point 'word))) + (goto-char (cdr bounds)) + (insert str) + (goto-char (car bounds)) + (insert str)) + (message "Can't insert around. No word at point and no region selected.")))))) ;; -------------------- Append To Lines In Region Or Buffer -------------------- ;; append characters to the end of all lines in the region or the whole buffer. @@ -322,7 +335,7 @@ User is prompted for the optional descriptor." (forward-line 1))))) ;; ------------------------------ Hyphenate Region ----------------------------- -;; hyphenates any empty space in a region; complains if there's no Region +;; hyphenates any empty space in a region; complains if no region selected (defun cj/hyphenate-region (start end) "Hyphenate all continuous whitespace in the region. @@ -401,8 +414,8 @@ and all articles are considered minor words." ;; Check the beginning of the previous word doesn't reset first. (save-excursion (and - (not (zerop - (skip-chars-backward "[:blank:]" prev-word-end))) + (not (zerop + (skip-chars-backward "[:blank:]" prev-word-end))) (memq (char-before (point)) chars-skip-reset)))) (delete-region (point) (1+ (point))) (insert c-up)))))) @@ -418,20 +431,35 @@ and all articles are considered minor words." "Remove ^M from the current buffer." (interactive) (save-excursion - (goto-char (point-min)) - (while (search-forward "" nil t) + (goto-char (point-min)) + (while (search-forward "" nil t) (replace-match "" nil t)))) -;; ----------------------------- Clear Blank Lines ----------------------------- +;; -------------------- Remove Blank Lines Region Or Buffer -------------------- +;; removes lines contiaining whitespace from region or buffer. -(defun cj/clear-blank-lines (beginning end) - "Remove blank lines in the region or the buffer if no region is selected. -BEGINNING and END describe the selected region." - (interactive "r") +(defun cj/delete-blank-lines-region-or-buffer (start end) + "Delete all blank lines in the region between START and END. +Blank lines are lines that contain only whitespace (spaces or tabs). +If called interactively with an active region, operate on that region. +If no active region, prompt user before operating on the whole buffer. +Otherwise signal a user-error and do nothing. The point is restored +to its original position after deletion." + (interactive + (if (use-region-p) + ;; grab its boundaries if there's a region + (list (region-beginning) (region-end)) + ;; or ask if user intended operating on whole buffer + (if (yes-or-no-p "Delete blank lines in entire buffer? ") + (list (point-min) (point-max)) + (user-error "Aborted")))) (save-excursion - (goto-char beginning) - (while (re-search-forward "^[ \t]*\n" end t) - (replace-match "")))) + (save-restriction + (widen) + ;; Regexp "^[[:space:]]*$" matches lines of zero or more spaces/tabs. + (flush-lines "^[[:space:]]*$" start end))) + ;; Return nil (Emacs conventions). Point is already restored. + nil) ;; ---------------------- Fixup Whitespace Line Or Region ---------------------- @@ -442,21 +470,21 @@ whitespace. When called with a prefix argument, it operates on the current REGION." (interactive "P") (save-excursion - (let* ((beg (if region (region-beginning) (line-beginning-position))) - (end (if region (region-end) (line-end-position)))) - (save-restriction + (let* ((beg (if region (region-beginning) (line-beginning-position))) + (end (if region (region-end) (line-end-position)))) + (save-restriction (narrow-to-region beg end) ;; Replace all tabs with space (goto-char (point-min)) (replace-string "\t" " " nil beg end) - ;; Remove leading and trailing spaces - (goto-char (point-min)) - (while (re-search-forward "^\\s-+\\|\\s-+$" nil t) - (replace-match "" nil nil)) - ;; Ensure only one space between words/symbols. - (goto-char (point-min)) - (while (re-search-forward "\\s-\\{2,\\}" nil t) - (replace-match " " nil nil)))))) + ;; Remove leading and trailing spaces + (goto-char (point-min)) + (while (re-search-forward "^\\s-+\\|\\s-+$" nil t) + (replace-match "" nil nil)) + ;; Ensure only one space between words/symbols. + (goto-char (point-min)) + (while (re-search-forward "\\s-\\{2,\\}" nil t) + (replace-match " " nil nil)))))) ;; -------------------------- Replace Fraction Glyphs -------------------------- @@ -470,11 +498,11 @@ Replaces the text with the glyphs if called with C-u." ("1/3" . "⅓") ("2/3" . "⅔")) '(("¼" . "1/4") ("½" . "1/2") ("¾" . "3/4") ("⅓" . "1/3") ("⅔" . "2/3"))))) - (save-excursion - (dolist (r replacements) - (goto-char start) - (while (search-forward (car r) end t) - (replace-match (cdr r))))))) + (save-excursion + (dolist (r replacements) + (goto-char start) + (while (search-forward (car r) end t) + (replace-match (cdr r))))))) ;; ------------------------------ Insert Date Time ----------------------------- ;; insert a sortable or a readable datestamp or timestamp @@ -565,7 +593,7 @@ Uses `sortable-time-format' for the formatting the date/time." (rename-file filename new-name 1) (rename-buffer new-name) (set-visited-file-name new-name) - (set-buffer-modified-p nil)))))) + (set-buffer-modified-p nil)))))) ;; DELETE BUFFER + FILE (defun cj/delete-buffer-and-file () @@ -596,7 +624,7 @@ Uses `sortable-time-format' for the formatting the date/time." (defadvice align-regexp (around align-regexp-with-spaces activate) "Avoid tabs when aligning text." (let ((indent-tabs-mode nil)) - ad-do-it)) + ad-do-it)) ;; ----------------------------- Merge List To List ---------------------------- ;; Convenience method for merging two lists together @@ -605,12 +633,11 @@ Uses `sortable-time-format' for the formatting the date/time." (defun cj/merge-list-to-list (dst src) "Merge content of the 2nd list SRC with the 1st one DST." (set dst - (append (eval dst) src))) + (append (eval dst) src))) ;; ------------------------------ Personal Keymap ------------------------------ ;; a keymap to use the above functions. prefix key: "C-;" - ;; Unset Flyspell's greedy keybinding C-; so it can be used for personal-keymap (global-unset-key (kbd "C-;")) (eval-after-load "flyspell" @@ -623,26 +650,27 @@ Uses `sortable-time-format' for the formatting the date/time." (define-key map "A" 'cj/unarrayify) ;; de/duplicate lines (define-key map "d" 'cj/duplicate-line-or-region) - (define-key map "D" 'cj/remove-duplicate-lines-from-region-or-buffer) + (define-key map "D" 'cj/remove-duplicate-lines-from-region-or-buffer) + (define-key map "D" 'cj/remove-duplicate-lines-from-region-or-buffer) (define-key map ")" #'cj/jump-to-matching-paren) (define-key map "/" #'cj/replace-fraction-glyphs) (define-key map "L" #'cj/clear-blank-lines) - (define-key map "-" #'cj/hyphenate-region) - (define-key map "U" 'upcase-region) - (define-key map "w" 'cj/fixup-whitespace-line-or-region) - (define-key map "#" 'cj/count-words-buffer-or-region) - (define-key map "1" 'cj/alphabetize-and-replace-region) - (define-key map "C" 'display-fill-column-indicator-mode) - (define-key map "J" 'cj/join-paragraph) - (define-key map "f" 'cj/format-region-or-buffer) - (define-key map "j" 'cj/join-line-or-region) - (define-key map "l" 'downcase-dwim) - (define-key map "p" 'cj/append-to-lines-in-region-or-buffer) - (define-key map "P" 'cj/prepend-to-lines-in-region-or-buffer) - (define-key map "r" 'align-regexp) - (define-key map "u" 'cj/title-case-region) - (define-key map "c" 'cj/wrap-region-as-code-span) + (define-key map "-" #'cj/hyphenate-region) + (define-key map "U" 'upcase-region) + (define-key map "w" 'cj/fixup-whitespace-line-or-region) + (define-key map "#" 'cj/count-words-buffer-or-region) + (define-key map "1" 'cj/alphabetize-and-replace-region) + (define-key map "C" 'display-fill-column-indicator-mode) + (define-key map "J" 'cj/join-paragraph) + (define-key map "f" 'cj/format-region-or-buffer) + (define-key map "j" 'cj/join-line-or-region) + (define-key map "l" 'downcase-dwim) + (define-key map "p" 'cj/append-to-lines-in-region-or-buffer) + (define-key map "P" 'cj/prepend-to-lines-in-region-or-buffer) + (define-key map "r" 'align-regexp) + (define-key map "u" 'cj/title-case-region) + (define-key map "c" 'cj/wrap-region-as-code-span) map) "My personal key map.") (global-set-key (kbd "C-;") personal-keymap) @@ -656,10 +684,12 @@ Uses `sortable-time-format' for the formatting the date/time." (global-set-key (kbd "C-; b r") 'cj/rename-buffer-and-file) (global-set-key (kbd "C-; b d") 'cj/delete-buffer-and-file) (global-set-key (kbd "C-; b m") 'cj/move-buffer-and-file) +(global-set-key (kbd "C-; b c") 'cj/copy-buffer) ;; copy link to source file (global-set-key (kbd "C-; b l") 'cj/copy-link-to-source-file) ;; insert around (global-set-key (kbd "C-; i a") 'cj/insert-around-word-or-region) + (provide 'custom-functions) ;;; custom-functions.el ends here. diff --git a/modules/diff-config.el b/modules/diff-config.el index 98729639..f3ab4bdf 100644 --- a/modules/diff-config.el +++ b/modules/diff-config.el @@ -2,53 +2,85 @@ ;; author Craig Jennings <c@cjennings.net> ;;; 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. +;; I've tied Ediff with ZTree into a single keybinding for convenience. -(defmacro csetq (variable value) - `(funcall (or (get ',variable 'custom-set) - 'set-default) - ',variable ,value)) +;; • Ediff will use a plain control window, horizontal splits, ignore whitespace, and only highlight the current change. +;; • A single keymap under “C-c e” has bindings: +;; - ediff-files, +;; - ediff-buffers (b) +;; - ediff-revision (r) +;; - ediff-directories (D) +;; - ztree directory diffs (d) +;; • An Ediff hook that remaps j/k to next/previous differences and q to quit immediately +;; • Einner-mode enabled so window layouts are restored after quitting Ediff -;; ------------------------------- Ediff Settings ------------------------------ -;; ediff configuration. note the dired-ediff-files function in dirvish. +;; An accompanying ERT test ensures that the “d” key in the ediff map is correctly bound to ztree-diff. -;; 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") +;; Note: Here's a highly useful setup for configuring ediff. +;; https://oremacs.com/2015/01/17/setting-up-ediff/ -;; only highlight the current diff: -(setq-default ediff-highlight-all-diffs 'nil) +;;; Code: -;; 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) +(use-package ediff + :ensure nil ;; built-in + :defer 0.5 + :custom + (ediff-window-setup-function 'ediff-setup-windows-plain) + (ediff-split-window-function 'split-window-horizontally) + (ediff-diff-options "-w") + (ediff-highlight-all-diffs nil) + :bind-keymap ("C-c e" . cj/ediff-map) + :init + ;; adding this to a hook to make sure ediff is loaded due to :defer + (defvar cj/ediff-map + (let ((m (make-sparse-keymap))) + (define-key m "f" #'ediff-files) ; C-c e f + (define-key m "b" #'ediff-buffers) ; C-c e b + (define-key m "r" #'ediff-revision) ; C-c e r + (define-key m "D" #'ediff-directories) ; C-c e D + m) + "Prefix map for quick Ediff commands under C-c e.") + (winner-mode 1) + :config + (defun cj/ediff-hook () + "Use j/k to navigate and q to quit immediately in Ediff." + (ediff-setup-keymap) ;; keep the defaults… + (define-key ediff-mode-map "j" #'ediff-next-difference) + (define-key ediff-mode-map "k" #'ediff-previous-difference) + (define-key ediff-mode-map "q" #'ediff-quit)) -;; restore the window setup after quitting -(winner-mode) -(add-hook 'ediff-after-quit-hook-internal 'winner-undo) + (add-hook 'ediff-mode-hook #'cj/ediff-hook) + (add-hook 'ediff-after-quit-hook-internal #'winner-undo)) ;; ----------------------------------- Ztree ----------------------------------- ;; diff two directories (use-package ztree - :defer .5 + :after ediff + :defer 0.5 :bind - ("C-c D" . ztree-diff)) + (:map cj/ediff-map + ("d" . ztree-diff))) (provide 'diff-config) ;;; diff-config.el ends here. + + +;; --------------------------------- ERT Tests --------------------------------- + +(ert-deftest ediff-config/ediff-map-includes-ztree-d () + "Ensure that \"d\" in `cj/ediff-map' is bound to `ztree-diff'." + (should (eq (lookup-key cj/ediff-map "d") #'ztree-diff))) + +(ert-deftest ediff-config/ediff-mode-map-has-j-k-q () + (with-temp-buffer + ;; force-load ediff + (require 'ediff) + (mapc (lambda (binding) + (let ((key (kbd (car binding))) + (fn (cdr binding))) + (should (eq (lookup-key ediff-mode-map key) fn)))) + '(("j" . ediff-next-difference) + ("k" . ediff-previous-difference) + ("q" . ediff-quit))))) diff --git a/modules/eshell-vterm-config.el b/modules/eshell-vterm-config.el index 8e4470e1..178f23db 100644 --- a/modules/eshell-vterm-config.el +++ b/modules/eshell-vterm-config.el @@ -130,46 +130,45 @@ ;; ------------------------------ Vterm ------------------------------ ;; faster and highly dependable, but not extensible -;; (use-package vterm -;; :disable t -;; :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 -;; ("<f12>" . nil) -;; ("C-y" . vterm-yank) -;; ("C-p" . vtermf-copy-mode) -;; ("<pause>" . vterm-copy-mode)) -;; :custom -;; (vterm-kill-buffer-on-exit t) -;; (vterm-max-scrollback 100000) -;; :config -;; (setq vterm-timer-delay nil)) - -;; (use-package vterm-toggle -;; :defer .5 -;; :bind -;; ("C-<f12>" . 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.5)))) +(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 + ("<f12>" . nil) + ("C-y" . vterm-yank) + ("C-p" . vtermf-copy-mode) + ("<pause>" . vterm-copy-mode)) + :custom + (vterm-kill-buffer-on-exit t) + (vterm-max-scrollback 100000) + :config + (setq vterm-timer-delay nil)) + +(use-package vterm-toggle + :defer .5 + :bind + ("C-<f12>" . 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.25)))) (provide 'eshell-vterm-config) ;;; eshell-vterm-config.el ends here. diff --git a/modules/font-config.el b/modules/font-config.el index 7da2e8af..6198954d 100644 --- a/modules/font-config.el +++ b/modules/font-config.el @@ -134,8 +134,9 @@ Note that server-after-make-frame-hook is available only in Emacs 27+." :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 + ;; using this keybinding for ediff + ;; ("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) diff --git a/modules/games-config.el b/modules/games-config.el index f7a3b652..7b25fb0c 100644 --- a/modules/games-config.el +++ b/modules/games-config.el @@ -9,15 +9,15 @@ ;; --------------------------------- Games Menu -------------------------------- -(defhydra hydra-games (:color blue :hint nil) - "\n" - ("2" 2048-game "2048 : Combine the Numbered Tiles to get to 2048" :column "Game") - ("c" chess "Chess : Play the 64 Squares and Checkmate the King" :column "Game") - ("d" dunnet "Dunnet : Emacs' Built-n Text Adventure" :column "Game") - ("g" gomoku "Gomoku : Tic Tac Toe, but Five in a Row" :column "Game") - ("m" malyon "Malyon : The Text Adventure Player" :column "Game") - ("t" tetris "Tetris : Combine falling blocks and score" :column "Game")) -(global-set-key (kbd "M-G") 'hydra-games/body) +;; (defhydra hydra-games (:color blue :hint nil) +;; "\n" +;; ("2" 2048-game "2048 : Combine the Numbered Tiles to get to 2048" :column "Game") +;; ("c" chess "Chess : Play the 64 Squares and Checkmate the King" :column "Game") +;; ("d" dunnet "Dunnet : Emacs' Built-n Text Adventure" :column "Game") +;; ("g" gomoku "Gomoku : Tic Tac Toe, but Five in a Row" :column "Game") +;; ("m" malyon "Malyon : The Text Adventure Player" :column "Game") +;; ("t" tetris "Tetris : Combine falling blocks and score" :column "Game")) +;; (global-set-key (kbd "M-G") 'hydra-games/body) ;; ----------------------------------- Malyon ---------------------------------- ;; text based adventure player @@ -34,17 +34,17 @@ ;; ----------------------------------- Chess ----------------------------------- ;; play the 64 squares and checkmate the opponent's king -(use-package chess - :defer 1 - :config - (setq chess-default-display 'chess-images) - (setq chess-images-directory - (concat user-emacs-directory "assets/chess/pieces/xboard/")) - (setq chess-images-dark-color "#779556") - (setq chess-images-light-color "#EBECD0") - (setq chess-images-default-size 100) - (setq chess-full-name user-whole-name) - (setq chess-default-engine 'chess-fruit)) +;; (use-package chess +;; :defer 1 +;; :config +;; (setq chess-default-display 'chess-images) +;; (setq chess-images-directory +;; (concat user-emacs-directory "assets/chess/pieces/xboard/")) +;; (setq chess-images-dark-color "#779556") +;; (setq chess-images-light-color "#EBECD0") +;; (setq chess-images-default-size 100) +;; (setq chess-full-name user-whole-name) +;; (setq chess-default-engine 'chess-fruit)) ;; Notes from source code diff --git a/modules/help-utils.el b/modules/help-utils.el index 73fc2add..2048e0f6 100644 --- a/modules/help-utils.el +++ b/modules/help-utils.el @@ -37,6 +37,11 @@ (defun cj/local-arch-wiki-search () + "Prompt for an ArchWiki topic and open its local HTML copy in EWW. +Looks for “*.html” files under \"/usr/share/doc/arch-wiki/html/en\", +lets you complete on their basenames, and displays the chosen file +with `eww-browse-url'. If no file is found, reminds you to install +arch-wiki-docs." (interactive) (let* ((dir "/usr/share/doc/arch-wiki/html/en") (full-filenames (directory-files dir t "\\.html\\'")) diff --git a/modules/keybindings.el b/modules/keybindings.el index a8b3eb41..613e0202 100644 --- a/modules/keybindings.el +++ b/modules/keybindings.el @@ -24,6 +24,10 @@ ;;; Code: + +;; make org-store-link binding global +(global-set-key (kbd "C-c l") 'org-store-link) + ;; remap Shift Backspace to Delete (global-set-key (kbd "S-<backspace>") 'delete-forward-char) diff --git a/modules/latex-config.el b/modules/latex-config.el index 69f84948..e0efafca 100644 --- a/modules/latex-config.el +++ b/modules/latex-config.el @@ -2,12 +2,17 @@ ;; author Craig Jennings <c@cjennings.net> ;;; Commentary: +;; +;; WORKFLOW: +;; +;; Opening any tex file will put you into LaTeX mode. +;; ;; C-c C-m to enter macros ;; C-c C-e to enter environment - -;; C-c C-c to compile a tex document +;; +;; C-c C-c to compile a tex document using latexmk ;; C-c C-v to view the resulting pdf - +;; ;;; Code: ;; ----------------------------- Auctex And Related ---------------------------- @@ -39,14 +44,11 @@ :ensure t :init (company-auctex-init)) - ;; ----------------------------- Graphviz Dot Mode ----------------------------- (use-package graphviz-dot-mode :config (setq graphviz-dot-indent-width 4)) - - (provide 'latex-config) ;;; latex-config.el ends here diff --git a/modules/ledger-config.el b/modules/ledger-config.el index e2969dd7..443c8c83 100644 --- a/modules/ledger-config.el +++ b/modules/ledger-config.el @@ -3,8 +3,6 @@ ;;; Commentary: -;; Use C-c ? in ledger mode for discoverability. - ;; Unlike Mu4e and Elfeed (with Elfeed dashboard), there's no landing page for ;; Ledger. You're thrown immediately into your financial data file in edit mode. ;; Ok, but might be frightening for someone learning how to use Ledger. The diff --git a/modules/mail-config.el b/modules/mail-config.el index 64437155..0ac383ca 100644 --- a/modules/mail-config.el +++ b/modules/mail-config.el @@ -9,46 +9,7 @@ ;;; Code: -;; ----------------------- Mu4e View Save All Attachments ---------------------- -;; replacement for the extract function which saves all attachments - -(defun cj/mu4e-view-save-all-attachments (&optional msg) - "Save all attachments from the current email. -Prompt user for directory, creating if necessary, select all attachments in the -email, and save them in the specified directory. The optional MSG is only -provided when calling this function from mu4e's action context. This function is -intended to be used as a `mu4e-view-action' and/or bound to a key in -mu4e-view-mode-map." - (interactive) - (let ((msg (or msg (mu4e-message-at-point)))) - (let* ((parts (mu4e-view-mime-parts)) - ;; build cons list of candidate mime parts - (candidates (seq-map - (lambda (attachment-part) - (cons ;; (filename . annotation) - (plist-get attachment-part :filename) - attachment-part)) - ;; scope to parts with a filename - (seq-filter - (lambda (part) (plist-get part :attachment-like)) - parts))) - (candidates (or candidates (mu4e-warn "No attachments for this message"))) - (files (mapcar 'car candidates)) ;; Select all attachments - (custom-dir (read-directory-name "Save to directory: "))) - (unless (file-exists-p custom-dir) - (make-directory custom-dir t)) ;; t creates parent dirs if needed - ;; iterate over each file - (seq-do - (lambda (fname) - (let* ((part (cdr (assoc fname candidates))) - ;; build unique full file path - (path (mu4e--uniqify-file-name - (mu4e-join-paths - custom-dir - (plist-get part :filename))))) - ;; save the file - (mm-save-part-to-file (plist-get part :handle) path))) - files)))) +(require 'user-constants) ;; ------------------------- Mark All Headers ------------------------ ;; convenience function to mark all headers for an action @@ -63,6 +24,7 @@ Prompts user for the action when executing." ;;; ------------------ Smtpmail & Easy PG Assistant ----------------- ;; send mail to smtp host from smtpmail temp buffer. + (use-package smtpmail :ensure nil ;; built-in :defer .5 @@ -88,30 +50,33 @@ Prompts user for the action when executing." ("d" . mu4e-headers-mark-for-delete)) (:map mu4e-view-mode-map ("r" . mu4e-compose-wide-reply) - ("R" . mu4e-compose-reply) - ("e" . cj/mu4e-view-save-all-attachments)) + ("R" . mu4e-compose-reply)) :hook (mu4e-view-mode . turn-on-visual-line-mode) :config - (setq gnus-blocked-images "http") ;; block external images + (setq gnus-blocked-images "http") ;; block external images (i.e., 1 px trackers) (setq mail-user-agent 'mu4e-user-agent) ;; default to mu4e for email (setq message-citation-line-format "On %a %d %b %Y at %R, %f wrote:\n") ;; helps show up properly in Outlook/Gmail threads (setq message-citation-line-function 'message-insert-formatted-citation-line) (setq message-kill-buffer-on-exit t) ;; don't keep message buffers around - (setq message-signature-file (concat user-emacs-directory "signature")) ;; look for the signature here (setq mu4e-change-filenames-when-moving t) ;; avoid gmail dup UID issues: https://goo.gl/RTCgVa (setq mu4e-completing-read-function 'completing-read) ;; use generic completing read, rather than ido (setq mu4e-compose-context-policy 'ask) ;; ask for context if no context matches - (setq mu4e-compose-format-flowed t) ;; plain text mails must flow correctly for recipients + ;; (setq mu4e-compose-format-flowed t) ;; plain text mails must flow correctly for recipients (setq mu4e-compose-keep-self-cc t) ;; keep me in the cc list (setq mu4e-compose-signature-auto-include nil) ;; don't include signature by default (setq mu4e-confirm-quit nil) ;; don't ask when quitting (setq mu4e-context-policy 'pick-first) ;; start with the first (default) context (setq mu4e-headers-auto-update nil) ;; updating headers buffer on email is too jarring (setq mu4e-root-maildir mail-dir) ;; root directory for all email accounts + (setq mu4e-maildir mail-dir) ;; same as above (newer mu4e) (setq mu4e-sent-messages-behavior 'delete) ;; don't save to "Sent", IMAP does this already (setq mu4e-show-images t) ;; show embedded images - (setq mu4e-update-interval nil) ;; don't update automatically + (setq mu4e-update-interval nil) ;; disallow automatic checking for new emails + + + (setq mu4e-compose-format-flowed nil + mu4e-html2text-command 'mu4e-shr2text) ;; email conversion to html via shr2text (setq mu4e-mu-binary (executable-find "mu")) (setq mu4e-get-mail-command (concat (executable-find "mbsync") " -a")) ;; command to sync mail @@ -182,19 +147,20 @@ Prompts user for the action when executing." "My settings for message composition." (set-fill-column 72))) - ;; add save-all-attachments to view actions list - (add-to-list 'mu4e-view-actions - '("save all attachments" . cj/mu4e-view-save-all-attachments)) + (defun no-auto-fill () + "Turn off \'auto-fill-mode\'." + (auto-fill-mode -1)) + (add-hook 'mu4e-compose-mode-hook #'no-auto-fill) ;; Always BCC myself ;; http://www.djcbsoftware.nl/code/mu/mu4e/Compose-hooks.html - (defun cj/add-header () - "Add CC: and Bcc: to myself header." + (defun cj/add-cc-bcc-header () + "Add CC: and BCC: to myself header." (save-excursion (message-add-header (concat "CC: " "\n") ;; pre hook above changes user-mail-address. (concat "Bcc: " user-mail-address "\n")))) - (add-hook 'mu4e-compose-mode-hook 'cj/add-header) + (add-hook 'mu4e-compose-mode-hook 'cj/add-cc-bcc-header) ;; remap the awkward mml-attach-file to the quicker mail-add-attachment (define-key mu4e-compose-mode-map [remap mml-attach-file] 'mail-add-attachment) @@ -206,34 +172,47 @@ Prompts user for the action when executing." (when (fboundp 'imagemagick-register-types) (imagemagick-register-types)) - ;; xwidgets not able to be built into emacs on linux - ;; ;; view in xwidget html rendererer - ;; (add-to-list 'mu4e-headers-actions - ;; '("xWidget" . mu4e-action-view-with-xwidget) t) - ;; (add-to-list 'mu4e-view-actions - ;; '("xWidget" . mu4e-action-view-with-xwidget) t)) - ) ;; end use-package mu4e -(defun no-auto-fill () - "Turn off \'auto-fill-mode\'." - (auto-fill-mode -1)) -(add-hook 'mu4e-compose-mode-hook #'no-auto-fill) - -;; ----------------------------- Compose Mode Hydra ---------------------------- -;; WIP: menu available in compose mode - -(defhydra hydra-mu4e-compose (:color blue :timeout 10 :hint nil) - "Compose Mode Menu\n\n" - ("q" quit-window "Quit" :column "") - ("a" mail-add-attachment "Add Attachment" :column "") - ("r" message-new-line-and-reformat "Newline and Reformat" :column "") - ("d" message-delete-not-region "Delete Outside Region" :column "")) - -(defun mu4e-compose-mode-hook-hydra-setup () - "Create hydra/menu keybinding when entering compose mode." - (local-set-key (kbd "C-c ?") 'hydra-mu4e-compose/body)) -(add-hook 'mu4e-compose-mode-hook 'mu4e-compose-mode-hook-hydra-setup) + +;; ---------------------------------- Org-Msg ---------------------------------- +;; user composes org mode; recipient receives html + +(use-package org-msg + :after (org mu4e) + :ensure t + :config + ;; automatic greetings for html + (setq org-msg-greeting-fmt "\nHi%s,\n\n") + + ;; inline CSS, no postamble, no TOC, no stars or footers + (setq org-msg-options "html-postamble:nil H:5 num:nil ^:{} toc:nil author:nil email:nil") + + ;; hide org markup, show inline images + (setq org-msg-startup "hidestars inlineimages") + + ;; new and html emails get the option for both text and html, + ;; text emails get text only replies + (setq org-msg-default-alternatives + '((new . (text html)) + (reply-to-html . (text html)) + (reply-to-text . (text)))) + + ;; Convert Org Citations to Blockquote + (setq org-msg-convert-citation t) + + ;; Signature (Org Syntax) + (setq org-msg-signature "\nCraig\n\n") + + ;; turn on org-msg in all compose buffers + (org-msg-mode +1)) + + +(advice-add #'mu4e-compose-reply + :after (lambda (&rest _) (org-msg-edit-mode))) +(advice-add #'mu4e-compose-wide-reply + :after (lambda (&rest _) (org-msg-edit-mode))) + (provide 'mail-config) ;;; mail-config.el ends here diff --git a/modules/org-babel-config.el b/modules/org-babel-config.el index 8bd7a6ed..0dcc3c3e 100644 --- a/modules/org-babel-config.el +++ b/modules/org-babel-config.el @@ -21,8 +21,8 @@ (setq org-src-window-setup 'current-window) ;; don't split window when source editing wih C-c ' (setq org-confirm-babel-evaluate nil) ;; just evaluate the source code (setq org-babel-default-header-args - (cons '(:tangle . "yes") - (assq-delete-all :tangle org-babel-default-header-args)))) ;; default header args for babel + (cons '(:tangle . "yes") + (assq-delete-all :tangle org-babel-default-header-args)))) ;; default header args for babel ;; ------------------- Babel Execution Confirmation Toggle ------------------- @@ -33,9 +33,9 @@ If invoked with C-u, toggle the setting." (interactive "P") (if (equal flag '(4)) - (setq org-confirm-babel-evaluate (not org-confirm-babel-evaluate))) + (setq org-confirm-babel-evaluate (not org-confirm-babel-evaluate))) (message "Babel evaluation confirmation is %s" - (if org-confirm-babel-evaluate "on" "off"))) + (if org-confirm-babel-evaluate "on" "off"))) ;; ---------------------------- Org Babel Languages ---------------------------- @@ -134,5 +134,8 @@ ;; requires ob-racket, not yet in repositories ;; (add-to-list 'org-structure-template-alist '("sicp" . "src racket :lang sicp")) +;; drop Org’s default footnote list at the end +(setq org-html-footnote-separator "") + (provide 'org-babel-config) ;;; org-babel-config.el ends here. diff --git a/modules/org-config.el b/modules/org-config.el index 3faea70a..9e55ac6f 100644 --- a/modules/org-config.el +++ b/modules/org-config.el @@ -53,6 +53,7 @@ org-archive-subtree-default are placed.") (setq org-startup-indented t) ;; load org files indented (setq org-adapt-indentation t) ;; adapt indentation to outline node level (setq org-indent-indentation-per-level 2) ;; indent two character-widths per level + (setq tab-width 8) ;; org-mode complains when tabs aren't @ 8 ;; INLINE IMAGES (setq org-startup-with-inline-images t) ;; preview images by default diff --git a/modules/org-contacts-config.el b/modules/org-contacts-config.el deleted file mode 100644 index f6f14c80..00000000 --- a/modules/org-contacts-config.el +++ /dev/null @@ -1,27 +0,0 @@ -;;; org-contacts-config.el --- Org Contacts Customizations -*- lexical-binding: t; -*- -;; author: Craig Jennings <c@cjennings.net> -;;; Commentary: -;; Configuration for Org Contacts. - -;;; Code: - -(use-package org-contacts - :after (org mu4e) - :defer .5 - :bind ("C-z C" . org-contacts) ; starts contacts search - :config - (setq org-contacts-files (cons contacts-file '())) - - (add-to-list 'org-capture-templates - '("c" "Contact" entry (file+headline contacts-file "Contacts") - "*%?\n:PROPERTIES:\n:ADDRESS: \n:PHONE: \n:EMAIL: \n:BIRTHDAY: \n:NOTES: \n:END:" :prepend t)) - - (setq mu4e-contacts-file contacts-file) - - (add-to-list 'mu4e-headers-actions - '("add contact" . mu4e-action-add-org-contact)) - (add-to-list 'mu4e-view-actions - '("add contact" . mu4e-action-add-org-contact))) - -(provide 'org-contacts-config) -;;; org-contacts-config.el ends here. diff --git a/modules/org-export-config.el b/modules/org-export-config.el index b1967170..6e4db7e8 100644 --- a/modules/org-export-config.el +++ b/modules/org-export-config.el @@ -8,31 +8,110 @@ ;; backends for exporting from org-mode (use-package ox + :defer .5 :ensure nil :after org :config - (setq org-export-coding-system 'utf-8) ;; force utf-8 in org - (setq org-export-headline-levels 6) ;; export headlines 6 levels deep - (setq org-export-initial-scope 'subtree) ;; export the current subtree by default - (setq org-export-with-author nil) ;; export without author by default - (setq org-export-with-section-numbers nil) ;; export without section numbers by default - (setq org-export-with-tags nil) ;; export without tags by default - (setq org-export-with-tasks nil) ;; export without tasks by default - (setq org-export-with-toc nil)) ;; export without table of contents by default - -;; markdown -(use-package ox-md - :ensure nil ;; built-in - :after ox) + ;; load every built-in backend + (dolist (feat '(ox-ascii + ox-beamer + ox-html + ox-icalendar + ox-latex + ox-md + ox-odt)) + (require feat)) + + ;; now tell Org exactly which backends to use + (setq org-export-backends + '(ascii + beamer + html + latex + md + odt)) + + ;; Other Settings + (setq org-export-preserve-breaks t) ;; keep line breaks in all Org export back-ends + (setq org-export-coding-system 'utf-8) ;; force utf-8 in org + (setq org-export-headline-levels 6) ;; export headlines 6 levels deep + (setq org-export-initial-scope 'subtree) ;; export the current subtree by default + (setq org-export-with-author nil) ;; export without author by default + (setq org-export-with-section-numbers nil) ;; export without section numbers by default + (setq org-export-with-tags nil) ;; export without tags by default + (setq org-export-with-tasks '("TODO")) ;; export with tasks by default + ;; (setq org-export-with-tasks nil) ;; export WITHOUT tasks by default + ;; (setq org-export-with-toc nil) ;; export without table of contents by default + (setq org-export-with-toc t)) ;; export WITH table of contents by default ;; hugo markdown (use-package ox-hugo :after ox) -;; beamer presentations -(use-package ox-beamer - :ensure nil ;; built-in +;; github flavored markdown +(use-package ox-gfm :after ox) +;; reveal .js +(use-package ox-reveal + :after ox + :config + (setq org-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js")) + +;; JIRA markup +;; (use-package ox-jira +;; :after ox) + +;; ;; Confluence Wiki markup +;; (use-package ox-confluence +;; :after ox) + +;; ------------------------------- Org Tufte CSS ------------------------------- +;; Hacks to inline a superior tufte.css style sheet into your generated html files. + +;; +;; Inline tufte.css into all generated HTML documents +;; Note: the tufte.css file in my configuration has an extra style for +;; TODO and DONE task items. +;; + +(defun cj/org-inline-css (path) + "Return the contents of the file at PATH as a string. +If the file doesn’t exist, return an empty string." + (if (file-readable-p path) + (with-temp-buffer + (insert-file-contents path) + (buffer-string)) + (progn + (message "[org-inline-css] could not read %s" path) + ""))) + +(with-eval-after-load 'ox-html + ;; Disable the postamble (footer) completely: + (setq org-html-postamble nil) + + ;; inject tufte.css from the assets folder. + (setq org-html-html5-fancy t + org-html-head-include-default-style nil + org-html-head-extra + (let* ((css-file (expand-file-name "assets/tufte.css" + user-emacs-directory)) + (css (cj/org-inline-css css-file))) + (format "<style type=\"text/css\">\n%s\n</style>" css)))) +;; +;; Don't allow Table of Contents to link to TODO items +;; +(defun cj/org-html-toc-remove-todo (toc-entry backend _info) + "Remove any <span class=\"todo …\">TODO</span> from a single TOC entry." + (when (eq backend 'html) + (replace-regexp-in-string + "<span class=\"todo [^\"]*\">[^<]*</span>[[:space:]]*" + "" toc-entry))) + +(with-eval-after-load 'ox-html + ;; register our filter so each TOC line is run through it + (add-hook 'org-export-filter-toc-entry-functions + #'cj/org-html-toc-remove-todo)) + (provide 'org-export-config) ;;; org-export-config.el ends here. diff --git a/modules/system-defaults.el b/modules/system-defaults.el index c52b3e04..7aa07eee 100644 --- a/modules/system-defaults.el +++ b/modules/system-defaults.el @@ -5,7 +5,6 @@ ;;; Code: - ;; -------------------------- Native Comp Preferences -------------------------- ;; After async compiler starts, set preferences and warning level @@ -142,6 +141,7 @@ Return non-nil to indicate the warning was handled." ;; disabling mouse prevents accidental mouse moves modifying text (use-package inhibit-mouse + :demand t ;; run immediately :hook (after-init . inhibit-mouse-mode) :custom (inhibit-mouse-adjust-mouse-highlight t) diff --git a/modules/ui-config.el b/modules/ui-config.el index f7a4c7b7..18c7772f 100644 --- a/modules/ui-config.el +++ b/modules/ui-config.el @@ -3,26 +3,89 @@ ;;; Commentary: +;; This file centralizes user interface preferences, including: + +;; • Frame and window behavior +;; – Start all frames maximized +;; – Disable file‐ and dialog‐boxes +;; – Pixel scroll precision +;; – Show column numbers in the mode-line + +;; • Transparency controls +;; – Customizable variables 'cj/enable-transparency' and 'cj/transparency-level' +;; – Interactive 'cj/toggle-transparency' command + +;; • Cursor appearance +;; – Cursor color changes on the buffer's write and insertion state +;; (i.e., read-only, overwrite, normal) +;; – Option to customize cursor shape with 'cj/set-cursor-type' + +;; • Icons +;; – Load and enable 'nerd-icons' for UI glyphs + +;; Customize the transparency and cursor color options at the top of this file. + ;;; Code: +;; -------------------------------- UI Constants ------------------------------- + +(defcustom cj/enable-transparency nil + "Non-nil means use `cj/transparency-level' for frame transparency." + :type 'boolean + :group 'ui-config) + +(defcustom cj/transparency-level 84 + "Opacity level for Emacs frames when `cj/enable-transparency' is non-nil. +100 = fully opaque, 0 = fully transparent." + :type 'integer + :group 'ui-config) + + +(defconst cj/cursor-colors + '((read-only . "#f06a3f") ; red – buffer is read-only + (overwrite . "#c48702") ; gold – overwrite mode + (normal . "#64aa0f")) ; green – insert & read/write + "Alist mapping cursor states to their colors.") + ;; ----------------------------- System UI Settings ---------------------------- -(add-to-list 'initial-frame-alist '(fullscreen . maximized)) ;; start the initial frame maximized -(add-to-list 'default-frame-alist '(fullscreen . maximized)) ;; start every frame maximized -(setq pixel-scroll-precision-mode nil) ;; smooth scroll past images - enabled if nil! +(add-to-list 'initial-frame-alist '(fullscreen . maximized)) ;; start the initial frame maximized +(add-to-list 'default-frame-alist '(fullscreen . maximized)) ;; start every frame maximized +(setq pixel-scroll-precision-mode nil) ;; smooth scroll past images - enabled if nil! -(setq-default frame-inhibit-implied-resize t) ;; don't resize frames when setting ui-elements -(setq frame-title-format '("Emacs " emacs-version" : %b")) ;; the title is emacs with version and buffer name +(setq-default frame-inhibit-implied-resize t) ;; don't resize frames when setting ui-elements +(setq frame-title-format '("Emacs " emacs-version" : %b")) ;; the title is emacs with version and buffer name -(setq use-file-dialog nil) ;; no file dialog -(setq use-dialog-box nil) ;; no dialog boxes either -(column-number-mode 1) ;; show column number in the modeline -(setq switch-to-buffer-obey-display-actions t) ;; manual buffer switching obeys display action rules +(setq use-file-dialog nil) ;; no file dialog +(setq use-dialog-box nil) ;; no dialog boxes either +(column-number-mode 1) ;; show column number in the modeline +(setq switch-to-buffer-obey-display-actions t) ;; manual buffer switching obeys display action rules ;; -------------------------------- Transparency ------------------------------- -;; (set-frame-parameter (selected-frame) 'alpha '(84 84)) -;; (add-to-list 'default-frame-alist '(alpha 84 84)) +(defun cj/apply-transparency () + "Apply `cj/transparency-level' to the selected frame and future frames. +When `cj/enable-transparency' is nil, reset alpha to fully opaque." + (let ((alpha (if cj/enable-transparency + (cons cj/transparency-level cj/transparency-level) + '(100 . 100)))) + ;; apply to current frame + (set-frame-parameter nil 'alpha alpha) + ;; update default for new frames + (setq default-frame-alist + (assq-delete-all 'alpha default-frame-alist)) + (add-to-list 'default-frame-alist `(alpha . ,alpha)))) + +;; apply once at startup +(cj/apply-transparency) + +(defun cj/toggle-transparency () + "Toggle `cj/enable-transparency' and re-apply." + (interactive) + (setq cj/enable-transparency (not cj/enable-transparency)) + (cj/apply-transparency) + (message "Transparency %s" + (if cj/enable-transparency "enabled" "disabled"))) ;; ----------------------------------- Cursor ---------------------------------- ;; set cursor color according to mode @@ -31,39 +94,41 @@ ;; #c48702 indicates overwrite mode ;; #64aa0f indicates insert and read/write mode +;; ----------------------------------- Cursor ---------------------------------- -(defvar cj/set-cursor-color-color "") -(defvar cj/set-cursor-color-buffer "") +(defvar cj/-cursor-last-color nil + "Last color applied by `cj/set-cursor-color-according-to-mode`.") +(defvar cj/-cursor-last-buffer nil + "Last buffer name where color was applied by `cj/set-cursor-color-according-to-mode`.") (defun cj/set-cursor-color-according-to-mode () - "Change the cursor color based on selected minor modes. -Cursor becomes a red hue when in a read-only buffer, -turns goldenrod when in overwrite mode, and green otherwise." - ;; set-cursor-color is somewhat costly, so only call it when needed: - (let ((color - (if buffer-read-only "#f06a3f" - (if overwrite-mode "#c48702" - "#64aa0f")))) - (unless (and - (string= color cj/set-cursor-color-color) - (string= (buffer-name) cj/set-cursor-color-buffer)) - (set-cursor-color (setq cj/set-cursor-color-color color)) - (setq cj/set-cursor-color-buffer (buffer-name))))) -(add-hook 'post-command-hook 'cj/set-cursor-color-according-to-mode) -(setq cursor-in-non-selected-windows 'nil) ;; don't show cursor in unselected windows - -;; SET-CURSOR-TYPE + "Change the cursor color according to 'buffer-read-only' or 'overwrite' state." + (let* ((state (cond + (buffer-read-only 'read-only) + (overwrite-mode 'overwrite) + (t 'normal))) + (color (alist-get state cj/cursor-colors))) + (unless (and (string= color cj/-cursor-last-color) + (string= (buffer-name) cj/-cursor-last-buffer)) + (set-cursor-color color) + (setq cj/-cursor-last-color color + cj/-cursor-last-buffer (buffer-name))))) + +(add-hook 'post-command-hook #'cj/set-cursor-color-according-to-mode) + +;; Don’t show a cursor in non-selected windows: +(setq cursor-in-non-selected-windows nil) + +;; Initialize to box cursor (or any type you prefer) (defun cj/set-cursor-type (new-cursor-type) - "Set the cursor type of the selected frame to NEW-CURSOR-TYPE. -When called interactively, prompt for the type to use. -To get the frame's current cursor type, use `frame-parameters'." + "Set the cursor type of the selected frame to NEW-CURSOR-TYPE." (interactive (list (intern (completing-read - "Cursor type: " - (mapcar 'list '("box" "hollow" "bar" "hbar" nil)))))) - (modify-frame-parameters (selected-frame) - (list (cons 'cursor-type new-cursor-type)))) -(cj/set-cursor-type 'box) ;; start with the box cursor + "Cursor type: " + (mapcar #'list '("box" "hollow" "bar" "hbar" nil)))))) + (modify-frame-parameters nil `((cursor-type . ,new-cursor-type)))) + +(cj/set-cursor-type 'box) ;; --------------------------------- Nerd Icons -------------------------------- ;; use icons from nerd fonts in the Emacs UI diff --git a/modules/ui-navigation.el b/modules/ui-navigation.el index 49c9f631..77d1fcbe 100644 --- a/modules/ui-navigation.el +++ b/modules/ui-navigation.el @@ -3,9 +3,9 @@ ;;; Commentary: -;; Window/Frame Navigation +;; Window Navigation -;; This section handles situations where we're nagivating with more than one window +;; This section handles situations where we're navigating or arranging windows ;; Shift + arrow keys = move the cursors around the windows/buffers ;; Control + Shift + arrow keys = resize the windows @@ -17,9 +17,17 @@ ;; M-S - swap window positions ;; M-C - kill the current window ;; M-O - kill the other window +;; M-Z - undo kill buffer +;; M-U - winner undo (revert to the previous layout) -;;; Code: +;; Adjusting Window Sizes +;; Note: C-s is pressing Control + Super keys +;; C-s-<left> move window left +;; C-s-<right> move window right +;; C-s-<up> move window up +;; C-s-<down> move window down +;;; Code: ;; ------------------------------ Window Placement ----------------------------- @@ -32,8 +40,11 @@ (use-package windsize :defer .5 - :config - (windsize-default-keybindings)) + :bind + ("C-s-<left>" . windsize-left) + ("C-s-<right>" . windsize-right) + ("C-s-<up>" . windsize-up) + ("C-s-<down>" . windsize-down)) ;; M-shift = to balance multiple split windows (global-set-key (kbd "M-+") 'balance-windows) @@ -69,27 +80,27 @@ If it's vertical, change the split to be to horizontal. This function won't work with more than one split window." (interactive) (if (= (count-windows) 2) - (let* ((this-win-buffer (window-buffer)) - (next-win-buffer (window-buffer (next-window))) - (this-win-edges (window-edges (selected-window))) - (next-win-edges (window-edges (next-window))) - (this-win-2nd (not (and (<= (car this-win-edges) - (car next-win-edges)) - (<= (cadr this-win-edges) - (cadr next-win-edges))))) - (splitter - (if (= (car this-win-edges) - (car (window-edges (next-window)))) - 'split-window-horizontally - 'split-window-vertically))) - (delete-other-windows) - (let ((first-win (selected-window))) - (funcall splitter) - (if this-win-2nd (other-window 1)) - (set-window-buffer (selected-window) this-win-buffer) - (set-window-buffer (next-window) next-win-buffer) - (select-window first-win) - (if this-win-2nd (other-window 1)))))) + (let* ((this-win-buffer (window-buffer)) + (next-win-buffer (window-buffer (next-window))) + (this-win-edges (window-edges (selected-window))) + (next-win-edges (window-edges (next-window))) + (this-win-2nd (not (and (<= (car this-win-edges) + (car next-win-edges)) + (<= (cadr this-win-edges) + (cadr next-win-edges))))) + (splitter + (if (= (car this-win-edges) + (car (window-edges (next-window)))) + 'split-window-horizontally + 'split-window-vertically))) + (delete-other-windows) + (let ((first-win (selected-window))) + (funcall splitter) + (if this-win-2nd (other-window 1)) + (set-window-buffer (selected-window) this-win-buffer) + (set-window-buffer (next-window) next-win-buffer) + (select-window first-win) + (if this-win-2nd (other-window 1)))))) (global-set-key (kbd "M-T") 'toggle-window-split) ;; SWAP WINDOW POSITIONS @@ -98,6 +109,7 @@ This function won't work with more than one split window." ;; ---------------------------- Buffer Manipulation ---------------------------- ;; MOVE BUFFER +;; allows you to move between buffers (use-package buffer-move ;; :straight (buffer-move :type git :host github :repo "lukhas/buffer-move" ;; :fork (:host github :repo "cjennings/buffer-move")) @@ -113,19 +125,19 @@ This function won't work with more than one split window." "Re-open the last buffer killed. With ARG, re-open the nth buffer." (interactive "p") (let ((recently-killed-list (copy-sequence recentf-list)) - (buffer-files-list - (delq nil (mapcar (lambda (buf) - (when (buffer-file-name buf) + (buffer-files-list + (delq nil (mapcar (lambda (buf) + (when (buffer-file-name buf) (expand-file-name (buffer-file-name buf)))) (buffer-list))))) - (mapc - (lambda (buf-file) - (setq recently-killed-list - (delq buf-file recently-killed-list))) - buffer-files-list) - (find-file - (if arg (nth arg recently-killed-list) - (car recently-killed-list))))) + (mapc + (lambda (buf-file) + (setq recently-killed-list + (delq buf-file recently-killed-list))) + buffer-files-list) + (find-file + (if arg (nth arg recently-killed-list) + (car recently-killed-list))))) (global-set-key (kbd "M-Z") 'cj/undo-kill-buffer) ;; ---------------------------- Undo Layout Changes ---------------------------- @@ -135,7 +147,7 @@ This function won't work with more than one split window." (use-package winner-mode :ensure nil ;; built-in :defer .5 - :bind ("<f5>" . winner-undo)) + :bind ("M-U" . winner-undo)) - (provide 'ui-navigation) +(provide 'ui-navigation) ;;; ui-navigation.el ends here |
