diff --git a/init.el b/init.el index e1b0606..4312076 100644 --- a/init.el +++ b/init.el @@ -200,24 +200,24 @@ ;; Configure VC backend for Git (setq vc-handled-backends '(Git)) (setq vc-git-diff-switches '("--histogram")) - + ;; IMPORTANT: Tell diff-hl to use VC backend, not Magit (setq diff-hl-reference-revision nil) ; Use working tree, not index (setq diff-hl-disable-on-remote nil) ; Work even on remote files - + ;; Make diff-hl use the left fringe (setq diff-hl-side 'left) - + ;; Ensure diff-hl draws in fringe, not margin (setq diff-hl-draw-borders nil) (setq diff-hl-margin-mode nil) - + ;; Set diff-hl fringe bitmaps (ensure they're visible) (setq diff-hl-fringe-bmp-function 'diff-hl-fringe-bmp-from-type) - + ;; Enable flydiff for real-time updates using VC (diff-hl-flydiff-mode 1) - + ;; Update immediately when visiting a file (setq diff-hl-flydiff-delay 0.3) @@ -229,12 +229,12 @@ ;; Enable globally (global-diff-hl-mode 1) - + ;; Explicitly disable Magit integration in base config ;; (Magit hooks will only be added when dev-mode is enabled) (setq diff-hl-disable-on-remote nil) - ;; Manual refresh command + ;; Manual refresh command (defun diff-hl-refresh () "Manually refresh diff-hl indicators in all buffers." (interactive) @@ -242,7 +242,7 @@ (with-current-buffer buf (when diff-hl-mode (diff-hl-update))))) - + ;; Troubleshooting function (defun diff-hl-diagnose () "Diagnose diff-hl issues." @@ -261,7 +261,7 @@ (push (format "diff-hl-reference-revision: %s" diff-hl-reference-revision) diagnosis) (push (format "Magit loaded: %s" (if (fboundp 'magit-status) "yes" "no")) diagnosis) (message (mapconcat 'identity (nreverse diagnosis) "\n")))) - + ;; Force VC to refresh its state (defun diff-hl-force-vc-refresh () "Force VC to refresh state and then update diff-hl." @@ -270,7 +270,7 @@ (vc-refresh-state) (diff-hl-update) (message "VC state refreshed and diff-hl updated"))) - + ;; Disable Magit integration if causing issues (defun diff-hl-disable-magit () "Disable Magit integration with diff-hl." @@ -279,7 +279,7 @@ (remove-hook 'magit-pre-refresh-hook 'diff-hl-magit-pre-refresh) (remove-hook 'magit-post-refresh-hook 'diff-hl-magit-post-refresh) (message "Magit integration with diff-hl disabled. Using pure VC backend.")) - + ;; Ensure we're using VC backend (defun diff-hl-ensure-vc-backend () "Ensure diff-hl is using VC backend." @@ -708,6 +708,13 @@ (when (file-exists-p elfeed-config) (load-file elfeed-config))) + ;; Reload mu4e config if it exists + (let ((mu4e-config (expand-file-name "mu4e-config.el" user-emacs-directory))) + (when (file-exists-p mu4e-config) + (condition-case err + (load-file mu4e-config) + (error + (message "mu4e config available but mu4e not installed"))))) (message "Emacs configuration fully reloaded!")) @@ -722,6 +729,16 @@ (load-file elfeed-config) (message "Elfeed RSS reader configuration loaded."))) +;;; Email Configuration (mu4e) +(let ((mu4e-config (expand-file-name "mu4e-config.el" user-emacs-directory))) + (when (file-exists-p mu4e-config) + (condition-case err + (progn + (load-file mu4e-config) + (message "mu4e email configuration loaded.")) + (error + (message "mu4e configuration available but mu4e not installed. Install mu4e package to enable email."))))) + ;;; Development Mode Information (defun show-dev-mode-info () "Show information about development mode." diff --git a/mu4e-config.el b/mu4e-config.el new file mode 100644 index 0000000..3dab993 --- /dev/null +++ b/mu4e-config.el @@ -0,0 +1,528 @@ +;;; mu4e-config.el --- mu4e email configuration -*- lexical-binding: t; -*- + +;; mu4e should already be loaded from .emacs before this file is loaded +;; If not loaded, try to load it +(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/mu4e/") +(require 'mu4e) + +;; HTML rendering configuration for mu4e 1.12 + +;; Use shr as the default renderer +(setq mm-text-html-renderer 'shr) + +;; Configure shr for better plain text display +;; (setq shr-use-colors nil) ; No colors +;; (setq shr-use-fonts nil) ; No variable fonts +;; (setq shr-width 80) ; 80 column width +(setq shr-bullet "• ") ; Nice bullet + +;; Create a custom command to view HTML with pandoc +(defun mu4e-view-html-with-pandoc () + "View the HTML part of the current message using pandoc." + (interactive) + (let ((msg (mu4e-message-at-point))) + (unless msg + (mu4e-error "No message at point")) + (let* ((path (mu4e-message-field msg :path)) + (pandoc-buffer "*mu4e-pandoc*") + (temp-dir (make-temp-file "mu4e-extract" t))) + (if path + (progn + (with-current-buffer (get-buffer-create pandoc-buffer) + (erase-buffer) + (insert "=== HTML Email Rendered with Pandoc ===\n\n") + ;; Extract all parts + (call-process "mu" nil nil nil + "extract" "--save-all" + (format "--target-dir=%s" temp-dir) path) + ;; Find HTML files and convert them + (let ((html-files (directory-files temp-dir t "\\.html?$"))) + (if html-files + (dolist (html-file html-files) + (insert (shell-command-to-string + (format "pandoc -f html -t markdown --wrap=auto --columns=80 '%s' 2>/dev/null" + html-file)))) + ;; No HTML files found, try extracting from message directly + (let ((raw-msg (shell-command-to-string (format "cat '%s'" path)))) + ;; Look for HTML content between boundaries + (if (string-match "Content-Type: text/html" raw-msg) + (progn + (insert "Converting HTML content...\n\n") + (let ((temp-html (make-temp-file "mu4e-msg" nil ".html"))) + (with-temp-file temp-html + (insert raw-msg)) + (insert (shell-command-to-string + (format "cat '%s' | sed -n '/Content-Type: text\\/html/,/^--/p' | sed '1,/^$/d' | sed '/^--/,$d' | pandoc -f html -t markdown --wrap=auto --columns=80 2>/dev/null" + temp-html))) + (delete-file temp-html))) + (insert "No HTML content found in this message.\n"))))) + ;; Clean up temp directory + (delete-directory temp-dir t) + (goto-char (point-min)) + (view-mode) + (display-buffer (current-buffer)))) + (mu4e-warn "Cannot access message file"))))) + +;; Simpler approach: Add action to view HTML with pandoc +(add-to-list 'mu4e-view-actions + '("pandoc" . (lambda (msg) + (let* ((path (mu4e-message-field msg :path)) + (pandoc-buffer "*mu4e-pandoc*")) + (when path + (with-current-buffer (get-buffer-create pandoc-buffer) + (erase-buffer) + (insert "=== HTML Email Rendered with Pandoc ===\n\n") + (let ((html-content + (shell-command-to-string + (format "mu view '%s' 2>/dev/null | awk '/text\\/html/{flag=1; next} flag && /^--/{exit} flag' | pandoc -f html -t markdown --wrap=auto --columns=80 2>/dev/null" path)))) + (if (and html-content (> (length html-content) 0)) + (insert html-content) + ;; If no HTML part found, show message + (insert "No HTML content found in this message.\n\n") + (insert (shell-command-to-string + (format "mu view '%s'" path))))) + (goto-char (point-min)) + (view-mode) + (display-buffer (current-buffer))))))) + t) + +;; Add keybinding for pandoc view +(with-eval-after-load 'mu4e-view + (define-key mu4e-view-mode-map (kbd "H") 'mu4e-view-html-with-pandoc)) + +(message "mu4e: HTML rendering configured. Press 'H' in message view to render with pandoc.") + +;; Basic mu4e settings +(setq mu4e-maildir "~/Maildir" + ;; Use our processing script instead of plain mbsync + ;; This will sync mail and fix List-Id headers + mu4e-get-mail-command (expand-file-name "process-mail.sh" user-emacs-directory) + mu4e-update-interval 300 ; Update every 5 minutes + mu4e-compose-signature-auto-include nil + mu4e-view-show-images t + mu4e-view-show-addresses t + mu4e-change-filenames-when-moving t ; Needed for mbsync + mu4e-index-cleanup t ; Clean up after moving + mu4e-index-lazy-check nil ; Don't be lazy about indexing + mu4e-hide-index-messages t) ; Hide indexing messages to avoid errors + +;; Function to get current context's maildir prefix +(defun mu4e-current-context-maildir-prefix () + "Get the maildir prefix for the current context." + (if mu4e-context-current + (let ((context-name (mu4e-context-name mu4e-context-current))) + (format "maildir:/%s/*" context-name)) + "")) + +;; Bookmarks (shortcuts to common searches) +;; Use setq to define the complete list at once +(setq mu4e-bookmarks + '(;; Basic views - work in current context + (:name "Unread messages" + :query "flag:unread AND NOT flag:trashed" + :key ?u) + (:name "Today's messages" + :query "date:today..now" + :key ?t) + (:name "Last 7 days" + :query "date:7d..now" + :key ?w) + + ;; Smart mailboxes - search across all contexts + (:name "📌 Important" + :query "(flag:flagged OR prio:high OR from:/boss|manager|ceo|director|important/ OR subject:/urgent|important|critical|asap/) AND NOT flag:trashed" + :key ?i) + + (:name "📰 Newsletters" + :query "(from:/newsletter|news|digest|update|weekly|daily|monthly|bulletin|announcement/ OR subject:/newsletter|digest|update|weekly|edition/) AND NOT flag:trashed AND NOT flag:flagged" + :key ?n) + + (:name "🛍️ Purchases & Orders" + :query "(from:/amazon|ebay|paypal|stripe|shopify|order|store|shop|invoice|receipt/ OR subject:/order|invoice|receipt|purchase|payment|confirmation|shipping|delivery|tracking/) AND NOT flag:trashed" + :key ?p) + + (:name "📎 Attachments" + :query "flag:attach AND NOT flag:trashed" + :key ?a) + + (:name "🎫 Travel & Tickets" + :query "(from:/airline|hotel|booking|expedia|airbnb|uber|lyft|train|eventbrite|ticketmaster/ OR subject:/booking|reservation|ticket|flight|itinerary|confirmation/) AND NOT flag:trashed" + :key ?v) + + (:name "💰 Finance & Banking" + :query "(from:/bank|credit|visa|mastercard|amex|insurance|tax|accountant/ OR subject:/statement|balance|transaction|payment/) AND NOT flag:trashed" + :key ?f) + + (:name "👥 Social & Forums" + :query "(from:/facebook|twitter|linkedin|instagram|reddit|github|gitlab|discourse|forum/ OR subject:/commented|replied|mentioned|tagged|followed/) AND NOT flag:trashed" + :key ?s) + + ;; Mailing Lists (Personal context) + (:name "📋 All Mailing Lists" + :query "maildir:/Personal/Lists AND NOT flag:trashed" + :key ?L) + + (:name "📋 C++ std-discussion" + :query "list:std-discussion.lists.isocpp.org AND NOT flag:trashed" + :key ?C) + + (:name "📋 Qt Interest" + :query "list:interest.qt-project.org AND NOT flag:trashed" + :key ?q) + + (:name "📋 Boost" + :query "list:boost.lists.boost.org AND NOT flag:trashed" + :key ?b) + + (:name "📋 GCC" + :query "list:gcc.gnu.gcc.org AND NOT flag:trashed" + :key ?G) + + (:name "📋 LKML" + :query "list:linux-kernel.vger.kernel.org AND NOT flag:trashed" + :key ?K))) + +(setq mu4e-maildir-shortcuts + '(("/Personal/INBOX" . ?i) + ("/Personal/Sent" . ?s) + ("/Personal/Trash" . ?t) + ("/Personal/Drafts" . ?d) + ("/Personal/Spam " . ?S) + ("/Personal/Archive" . ?a) + ("/Personal/Lists" . ?l) + ("/IONOS/INBOX" . ?I) + ("/IONOS/Sent" . ?S) + ("/IONOS/Trash" . ?T) + ("/IONOS/Drafts" . ?D) + ("/IONOS/Archive" . ?A))) + +;; UI Configuration - use default headers for now +;; (setq mu4e-headers-fields +;; '((:human-date . 12) +;; (:flags . 6) +;; (:from-or-to . 22) +;; (:subject))) + +;; Make mu4e respect the current color theme +(setq mu4e-view-use-gnus t) ; Use Gnus article mode for better theme support +(setq shr-use-colors nil) ; Let the theme handle colors in HTML emails + +;; Use full window for reading emails +(setq mu4e-split-view nil) ; Don't split, use full window for message view + +;; Make headers/search view respect theme +(setq mu4e-headers-unread-face 'bold) ; Use theme's bold face instead of custom color +(setq mu4e-headers-highlight-face 'highlight) ; Use theme's highlight face +(setq mu4e-headers-flagged-face 'font-lock-warning-face) ; Use theme's warning face + +;; Enable inline images +(setq mu4e-view-show-images t) +(when (fboundp 'imagemagick-register-types) + (imagemagick-register-types)) + +;; Colorize inline patches in emails +(require 'diff-mode) + +(defun mu4e-colorize-patch () + "Colorize patches in mu4e view buffers." + (save-excursion + (goto-char (point-min)) + ;; Look for patch sections (starting with diff, ---, or @@) + (while (re-search-forward "^\\(diff \\|--- \\|\\+\\+\\+ \\|@@ \\)" nil t) + (let ((patch-start (match-beginning 0))) + ;; Find the end of the patch + (if (re-search-forward "^[^-+@ \t]" nil t) + (backward-char) + (goto-char (point-max))) + (let ((patch-end (point)) + (inhibit-read-only t)) + ;; Apply diff-mode font-lock to the patch region + (add-text-properties patch-start patch-end + '(face nil)) ; Reset face first + (save-restriction + (narrow-to-region patch-start patch-end) + (diff-mode) + (font-lock-fontify-region patch-start patch-end) + (widen)))))) + ;; Also colorize simple diff lines + (save-excursion + (goto-char (point-min)) + (while (re-search-forward "^\\(-.*\\)$" nil t) + (let ((inhibit-read-only t)) + (add-face-text-property (match-beginning 1) (match-end 1) + 'diff-removed))) + (goto-char (point-min)) + (while (re-search-forward "^\\(\\+.*\\)$" nil t) + (let ((inhibit-read-only t)) + (add-face-text-property (match-beginning 1) (match-end 1) + 'diff-added))) + (goto-char (point-min)) + (while (re-search-forward "^\\(@@.*@@\\).*$" nil t) + (let ((inhibit-read-only t)) + (add-face-text-property (match-beginning 1) (match-end 1) + 'diff-hunk-header))))) + +;; Hook to colorize patches when viewing messages +(add-hook 'mu4e-view-mode-hook 'mu4e-colorize-patch) + +;; For Gnus article mode (when mu4e-view-use-gnus is t) +(with-eval-after-load 'gnus-art + (add-hook 'gnus-article-mode-hook 'mu4e-colorize-patch)) + +;; Use mu4e for composing email +(setq mail-user-agent 'mu4e-user-agent) + +;; Keybindings +(global-set-key (kbd "C-x m") 'mu4e) +(global-set-key (kbd "C-x M") 'mu4e-compose-new) + +;; Double-check pandoc is available and being used +(if (executable-find "pandoc") + (message "Pandoc found at: %s" (executable-find "pandoc")) + (message "WARNING: Pandoc not found!")) + +;; Prefer plain text when available, but show HTML when it's the only option +(setq mu4e-view-prefer-html nil) + +;; Actions +(add-to-list 'mu4e-view-actions + '("ViewInBrowser" . mu4e-action-view-in-browser) t) + +;; Don't keep message buffers around +(setq message-kill-buffer-on-exit t) + +;; For Proton Bridge: Comment out custom marks for now to avoid errors +;; The default behavior will be: +;; d - move to trash +;; D - delete permanently +;; r - refile/archive + +;; If you want to customize deletion behavior, uncomment and adjust: +;; (with-eval-after-load 'mu4e +;; ;; Make 'd' archive instead of trash +;; (setf (alist-get 'trash mu4e-marks) +;; (list :char '("d" . "▼") +;; :prompt "archive" +;; :dyn-target (lambda (target msg) +;; (mu4e-get-refile-folder msg)) +;; :action (lambda (docid msg target) +;; (mu4e--server-move docid +;; (mu4e--mark-check-target target) +;; "-N"))))) + +;; Test function to verify pandoc is working +(defun mu4e-test-pandoc () + "Test if pandoc is being used for HTML rendering." + (interactive) + (let ((test-html "
This is a test paragraph with emphasis.