<commit_before>;;; email/mu4e/config.el -*- lexical-binding: t; -*-

(defvar +mu4e-backend 'mbsync
  "Which backend to use. Can either be offlineimap, mbsync or nil (manual).")

(defvar +mu4e-personal-addresses 'nil
  "Alternative to mu4e-personal-addresses that can be set for each account (mu4e context).")


;;
;;; Packages

(use-package! mu4e
  :commands mu4e mu4e-compose-new
  :init
  (provide 'html2text) ; disable obsolete package
  (when (or (not (require 'mu4e-meta nil t))
            (version< mu4e-mu-version "1.4"))
    (setq mu4e-maildir "~/.mail"
          mu4e-user-mail-address-list nil))
  (setq mu4e-attachment-dir
        (lambda (&rest _)
          (expand-file-name ".attachments" (mu4e-root-maildir))))
  :config
  (pcase +mu4e-backend
    (`mbsync
     (setq mu4e-get-mail-command "mbsync -a"
           mu4e-change-filenames-when-moving t))
    (`offlineimap
     (setq mu4e-get-mail-command "offlineimap -o -q")))

  (setq mu4e-update-interval nil
        mu4e-view-show-addresses t
        mu4e-sent-messages-behavior 'sent
        mu4e-hide-index-messages t
        ;; try to show images
        mu4e-view-show-images t
        mu4e-view-image-max-width 800
        mu4e-view-use-gnus t ; the way of the future: https://github.com/djcb/mu/pull/1442#issuecomment-591695814
        ;; configuration for sending mail
        message-send-mail-function #'smtpmail-send-it
        smtpmail-stream-type 'starttls
        message-kill-buffer-on-exit t ; close after sending
        ;; start with the first (default) context;
        mu4e-context-policy 'pick-first
        ;; compose with the current context, or ask
        mu4e-compose-context-policy 'ask-if-none
        ;; use helm/ivy/vertico
        mu4e-completing-read-function
        (cond ((featurep! :completion ivy)     #'ivy-completing-read)
              ((featurep! :completion helm)    #'completing-read)
              ((featurep! :completion vertico) #'completing-read)
              (t #'ido-completing-read))
        mu4e-attachment-dir
        (concat
         (if (executable-find "xdg-user-dir")
             ;; remove trailing newline
             (substring (shell-command-to-string "xdg-user-dir DOWNLOAD") 0 -1)
           (expand-file-name (or (getenv "XDG_DOWNLOAD_DIR")
                                 "Downloads")
                             "~"))
         "/")
        ;; no need to ask
        mu4e-confirm-quit nil
        mu4e-headers-thread-single-orphan-prefix '("─>" . "─▶")
        mu4e-headers-thread-orphan-prefix '("┬>" . "┬▶ ")
        mu4e-headers-thread-last-child-prefix '("└>" . "╰▶")
        mu4e-headers-thread-child-prefix '("├>" . "├▶")
        mu4e-headers-thread-connection-prefix '("│" . "│ ")
        ;; remove 'lists' column
        mu4e-headers-fields
        '((:account-stripe . 1)
          (:human-date . 8)
          (:flags . 6) ; 3 icon flags
          (:from-or-to . 25)
          (:subject)))

  ;; set mail user agent
  (setq mail-user-agent 'mu4e-user-agent
        message-mail-user-agent 'mu4e-user-agent)

  ;; Set the icons only when a graphical frame has been created
  (if (display-graphic-p)
      (+mu4e-initialise-icons)
    ;; When it's the server, wait till the first graphical frame
    (add-hook!
     'server-after-make-frame-hook
     (defun +mu4e-initialise-icons-hook ()
       (when (display-graphic-p)
         (+mu4e-initialise-icons)
         (remove-hook 'server-after-make-frame-hook
                      #'+mu4e-initialise-icons-hook)))))

  (plist-put (cdr (assoc :flags mu4e-header-info)) :shortname " Flags") ; default=Flgs
  (add-to-list 'mu4e-bookmarks
               '(:name "Flagged messages" :query "flag:flagged" :key ?f) t)

  ;; TODO avoid assuming that all-the-icons is present
  (defvar +mu4e-header-colorized-faces
    '(all-the-icons-green
      all-the-icons-lblue
      all-the-icons-purple-alt
      all-the-icons-blue-alt
      all-the-icons-purple
      all-the-icons-yellow)
    "Faces to use when coloring folders and account stripes.")

  (defvar +mu4e-min-header-frame-width 120
    "Minimum reasonable with for the header view.")

  ;; Add a column to display what email account the email belongs to,
  ;; and an account color stripe column
  (defvar +mu4e-header--maildir-colors nil)
  (setq mu4e-header-info-custom
        '((:account .
           (:name "Account"
            :shortname "Account"
            :help "which account/maildir this email belongs to"
            :function
            (lambda (msg)
              (let ((maildir (replace-regexp-in-string
                              "\\`/?\\([^/]+\\)/.*\\'" "\\1"
                              (mu4e-message-field msg :maildir))))
                (+mu4e-colorize-str
                 (replace-regexp-in-string
                  "^gmail"
                  (propertize "g" 'face 'bold-italic)
                  maildir)
                 '+mu4e-header--maildir-colors
                 maildir)))))
          (:account-stripe .
           (:name "Account"
            :shortname "▐"
            :help "Which account/maildir this email belongs to"
            :function
            (lambda (msg)
              (let ((account
                     (replace-regexp-in-string
                      "\\`/?\\([^/]+\\)/.*\\'" "\\1"
                      (mu4e-message-field msg :maildir))))
                (propertize
                 (+mu4e-colorize-str "▌" '+mu4e-header--maildir-colors account)
                 'help-echo account)))))
          (:recipnum .
           (:name "Number of recipients"
            :shortname " ⭷"
            :help "Number of recipients for this message"
            :function
            (lambda (msg)
              (propertize (format "%2d"
                                  (+ (length (mu4e-message-field msg :to))
                                     (length (mu4e-message-field msg :cc))))
                          'face 'mu4e-footer-face))))))

  ;; Marks usually affect the current view
  (defadvice! +mu4e--refresh-current-view-a (&rest _)
    :after #'mu4e-mark-execute-all (mu4e-headers-rerun-search))

  ;; Wrap text in messages
  (setq-hook! 'mu4e-view-mode-hook truncate-lines nil)

  ;; Html mails might be better rendered in a browser
  (add-to-list 'mu4e-view-actions '("View in browser" . mu4e-action-view-in-browser))
  (when (fboundp 'make-xwidget)
    (add-to-list 'mu4e-view-actions '("xwidgets view" . mu4e-action-view-with-xwidget)))

  ;; The header view needs a certain amount of horizontal space to
  ;; actually show you all the information you want to see
  ;; so if the header view is entered from a narrow frame,
  ;; it's probably worth trying to expand it
  (defun +mu4e-widen-frame-maybe ()
    "Expand the frame with if it's less than `+mu4e-min-header-frame-width'."
    (when (< (frame-width) +mu4e-min-header-frame-width)
      (set-frame-width (selected-frame) +mu4e-min-header-frame-width)))
  (add-hook 'mu4e-headers-mode-hook #'+mu4e-widen-frame-maybe)

  (when (fboundp 'imagemagick-register-types)
    (imagemagick-register-types))

  (map! :map mu4e-main-mode-map
        :ne "h" #'+workspace/other)

  (map! :map mu4e-headers-mode-map
        :vne "l" #'+mu4e/capture-msg-to-agenda)

  ;; Functionality otherwise obscured in mu4e 1.6
  (when (version<= "1.6" mu4e-mu-version)
    (defun +mu4e-view-select-attachment ()
      "Use completing-read to select a single attachment.
Acts like a singular `mu4e-view-save-attachments', without the saving."
      (let ((parts (mu4e~view-gather-mime-parts)) files)
        (dolist (part parts)
          (let ((fname (cdr (assoc 'filename (assoc "attachment" (cdr part))))))
            (when fname
              (push (cons (concat (format "%-2s " (car part))
                                  (when (featurep 'all-the-icons) (all-the-icons-icon-for-file fname))
                                  (format " %s " fname)
                                  (format "(%s)" (file-size-human-readable (with-current-buffer (cadr part) (buffer-size)))))
                          part)
                    files))))
        (if files
            (cdr (assoc (completing-read "Select attachment: " (mapcar #'car files))
                        files))
          (user-error (mu4e-format "No attached files found")))))

    (defun +mu4e-view-open-attachment ()
      "Select an attachment, and open it."
      (interactive)
      (mu4e~view-open-file
       (mu4e~view-mime-part-to-temp-file (cdr (+mu4e-view-select-attachment)))))

    (defun +mu4e-view-select-part ()
      (let ((parts (mu4e~view-gather-mime-parts)) partinfo labeledparts
            maxfnamelen fnamefmt maxsizelen sizefmt)
        (dolist (part parts)
          (push (list :index (car part)
                      :mimetype (if (string= "text/plain" (caaddr part))
                                    (format "%s (%s)"
                                            (caaddr part)
                                            (alist-get 'charset (cdaddr part)))
                                  (caaddr part))
                      :type (car (nth 5 part))
                      :filename (cdr (assoc 'filename (assoc "attachment" (cdr part))))
                      :size (file-size-human-readable (with-current-buffer (cadr part) (buffer-size)))
                      :part part)
                partinfo))
        (setq maxfnamelen (apply #'max 7 (mapcar (lambda (i) (length (plist-get i :filename))) partinfo))
              fnamefmt (format " %%-%ds  " maxfnamelen)
              maxsizelen (apply #'max (mapcar (lambda (i) (length (plist-get i :size))) partinfo))
              sizefmt (format "%%-%ds " maxsizelen))
        (dolist (pinfo partinfo)
          (push (cons (concat (propertize (format "%-2s " (plist-get pinfo :index)) 'face '(bold font-lock-type-face))
                              (when (featurep 'all-the-icons)
                                (all-the-icons-icon-for-file (or (plist-get pinfo :filename) "")))
                              (format fnamefmt (or (plist-get pinfo :filename)
                                                   (propertize (plist-get pinfo :type) 'face '(italic font-lock-doc-face))))
                              (format sizefmt (propertize (plist-get pinfo :size) 'face 'font-lock-builtin-face))
                              (propertize (plist-get pinfo :mimetype) 'face 'font-lock-constant-face))
                      (plist-get pinfo :part))
                labeledparts))
        (cdr (assoc (completing-read "Select part: " (mapcar #'car labeledparts))
                    labeledparts))))

    (defun +mu4e-view-select-mime-part-action ()
      "Select a MIME part, and perform an action on it."
      (interactive)
      (mu4e-view-mime-part-action (car (+mu4e-view-select-part))))

    (map! :map mu4e-view-mode-map
          :ne "A" #'+mu4e-view-select-mime-part-action
          :ne "p" #'mu4e-view-save-attachments
          :ne "o" #'+mu4e-view-open-attachment))

  (map! :localleader
        :map mu4e-compose-mode-map
        :desc "send and exit" "s" #'message-send-and-exit
        :desc "kill buffer"   "d" #'message-kill-buffer
        :desc "save draft"    "S" #'message-dont-send<commit_msg>refactor(mu4e): extract part pickers duplication<commit_after>;;; email/mu4e/config.el -*- lexical-binding: t; -*-

(defvar +mu4e-backend 'mbsync
  "Which backend to use. Can either be offlineimap, mbsync or nil (manual).")

(defvar +mu4e-personal-addresses 'nil
  "Alternative to mu4e-personal-addresses that can be set for each account (mu4e context).")


;;
;;; Packages

(use-package! mu4e
  :commands mu4e mu4e-compose-new
  :init
  (provide 'html2text) ; disable obsolete package
  (when (or (not (require 'mu4e-meta nil t))
            (version< mu4e-mu-version "1.4"))
    (setq mu4e-maildir "~/.mail"
          mu4e-user-mail-address-list nil))
  (setq mu4e-attachment-dir
        (lambda (&rest _)
          (expand-file-name ".attachments" (mu4e-root-maildir))))
  :config
  (pcase +mu4e-backend
    (`mbsync
     (setq mu4e-get-mail-command "mbsync -a"
           mu4e-change-filenames-when-moving t))
    (`offlineimap
     (setq mu4e-get-mail-command "offlineimap -o -q")))

  (setq mu4e-update-interval nil
        mu4e-view-show-addresses t
        mu4e-sent-messages-behavior 'sent
        mu4e-hide-index-messages t
        ;; try to show images
        mu4e-view-show-images t
        mu4e-view-image-max-width 800
        mu4e-view-use-gnus t ; the way of the future: https://github.com/djcb/mu/pull/1442#issuecomment-591695814
        ;; configuration for sending mail
        message-send-mail-function #'smtpmail-send-it
        smtpmail-stream-type 'starttls
        message-kill-buffer-on-exit t ; close after sending
        ;; start with the first (default) context;
        mu4e-context-policy 'pick-first
        ;; compose with the current context, or ask
        mu4e-compose-context-policy 'ask-if-none
        ;; use helm/ivy/vertico
        mu4e-completing-read-function
        (cond ((featurep! :completion ivy)     #'ivy-completing-read)
              ((featurep! :completion helm)    #'completing-read)
              ((featurep! :completion vertico) #'completing-read)
              (t #'ido-completing-read))
        mu4e-attachment-dir
        (concat
         (if (executable-find "xdg-user-dir")
             ;; remove trailing newline
             (substring (shell-command-to-string "xdg-user-dir DOWNLOAD") 0 -1)
           (expand-file-name (or (getenv "XDG_DOWNLOAD_DIR")
                                 "Downloads")
                             "~"))
         "/")
        ;; no need to ask
        mu4e-confirm-quit nil
        mu4e-headers-thread-single-orphan-prefix '("─>" . "─▶")
        mu4e-headers-thread-orphan-prefix '("┬>" . "┬▶ ")
        mu4e-headers-thread-last-child-prefix '("└>" . "╰▶")
        mu4e-headers-thread-child-prefix '("├>" . "├▶")
        mu4e-headers-thread-connection-prefix '("│" . "│ ")
        ;; remove 'lists' column
        mu4e-headers-fields
        '((:account-stripe . 1)
          (:human-date . 8)
          (:flags . 6) ; 3 icon flags
          (:from-or-to . 25)
          (:subject)))

  ;; set mail user agent
  (setq mail-user-agent 'mu4e-user-agent
        message-mail-user-agent 'mu4e-user-agent)

  ;; Set the icons only when a graphical frame has been created
  (if (display-graphic-p)
      (+mu4e-initialise-icons)
    ;; When it's the server, wait till the first graphical frame
    (add-hook!
     'server-after-make-frame-hook
     (defun +mu4e-initialise-icons-hook ()
       (when (display-graphic-p)
         (+mu4e-initialise-icons)
         (remove-hook 'server-after-make-frame-hook
                      #'+mu4e-initialise-icons-hook)))))

  (plist-put (cdr (assoc :flags mu4e-header-info)) :shortname " Flags") ; default=Flgs
  (add-to-list 'mu4e-bookmarks
               '(:name "Flagged messages" :query "flag:flagged" :key ?f) t)

  ;; TODO avoid assuming that all-the-icons is present
  (defvar +mu4e-header-colorized-faces
    '(all-the-icons-green
      all-the-icons-lblue
      all-the-icons-purple-alt
      all-the-icons-blue-alt
      all-the-icons-purple
      all-the-icons-yellow)
    "Faces to use when coloring folders and account stripes.")

  (defvar +mu4e-min-header-frame-width 120
    "Minimum reasonable with for the header view.")

  ;; Add a column to display what email account the email belongs to,
  ;; and an account color stripe column
  (defvar +mu4e-header--maildir-colors nil)
  (setq mu4e-header-info-custom
        '((:account .
           (:name "Account"
            :shortname "Account"
            :help "which account/maildir this email belongs to"
            :function
            (lambda (msg)
              (let ((maildir (replace-regexp-in-string
                              "\\`/?\\([^/]+\\)/.*\\'" "\\1"
                              (mu4e-message-field msg :maildir))))
                (+mu4e-colorize-str
                 (replace-regexp-in-string
                  "^gmail"
                  (propertize "g" 'face 'bold-italic)
                  maildir)
                 '+mu4e-header--maildir-colors
                 maildir)))))
          (:account-stripe .
           (:name "Account"
            :shortname "▐"
            :help "Which account/maildir this email belongs to"
            :function
            (lambda (msg)
              (let ((account
                     (replace-regexp-in-string
                      "\\`/?\\([^/]+\\)/.*\\'" "\\1"
                      (mu4e-message-field msg :maildir))))
                (propertize
                 (+mu4e-colorize-str "▌" '+mu4e-header--maildir-colors account)
                 'help-echo account)))))
          (:recipnum .
           (:name "Number of recipients"
            :shortname " ⭷"
            :help "Number of recipients for this message"
            :function
            (lambda (msg)
              (propertize (format "%2d"
                                  (+ (length (mu4e-message-field msg :to))
                                     (length (mu4e-message-field msg :cc))))
                          'face 'mu4e-footer-face))))))

  ;; Marks usually affect the current view
  (defadvice! +mu4e--refresh-current-view-a (&rest _)
    :after #'mu4e-mark-execute-all (mu4e-headers-rerun-search))

  ;; Wrap text in messages
  (setq-hook! 'mu4e-view-mode-hook truncate-lines nil)

  ;; Html mails might be better rendered in a browser
  (add-to-list 'mu4e-view-actions '("View in browser" . mu4e-action-view-in-browser))
  (when (fboundp 'make-xwidget)
    (add-to-list 'mu4e-view-actions '("xwidgets view" . mu4e-action-view-with-xwidget)))

  ;; The header view needs a certain amount of horizontal space to
  ;; actually show you all the information you want to see
  ;; so if the header view is entered from a narrow frame,
  ;; it's probably worth trying to expand it
  (defun +mu4e-widen-frame-maybe ()
    "Expand the frame with if it's less than `+mu4e-min-header-frame-width'."
    (when (< (frame-width) +mu4e-min-header-frame-width)
      (set-frame-width (selected-frame) +mu4e-min-header-frame-width)))
  (add-hook 'mu4e-headers-mode-hook #'+mu4e-widen-frame-maybe)

  (when (fboundp 'imagemagick-register-types)
    (imagemagick-register-types))

  (map! :map mu4e-main-mode-map
        :ne "h" #'+workspace/other)

  (map! :map mu4e-headers-mode-map
        :vne "l" #'+mu4e/capture-msg-to-agenda)

  ;; Functionality otherwise obscured in mu4e 1.6
  (when (version<= "1.6" mu4e-mu-version)
    (defun +mu4e-view-select-attachment ()
      "Use completing-read to select a single attachment.
Acts like a singular `mu4e-view-save-attachments', without the saving."
      (if-let ((parts (delq nil (mapcar
                                 (lambda (part)
                                   (when (assoc "attachment" (cdr part))
                                     part))
                                 (mu4e~view-gather-mime-parts))))
               (files (+mu4e-part-selectors parts)))
          (cdr (assoc (completing-read "Select attachment: " (mapcar #'car files)) files))
        (user-error (mu4e-format "No attached files found"))))

    (defun +mu4e-view-open-attachment ()
      "Select an attachment, and open it."
      (interactive)
      (mu4e~view-open-file
       (mu4e~view-mime-part-to-temp-file (cdr (+mu4e-view-select-attachment)))))

    (defun +mu4e-view-select-mime-part-action ()
      "Select a MIME part, and perform an action on it."
      (interactive)
      (let ((labeledparts (+mu4e-part-selectors (mu4e~view-gather-mime-parts))))
        (if labeledparts
            (mu4e-view-mime-part-action
             (cadr (assoc (completing-read "Select part: " (mapcar #'car labeledparts))
                          labeledparts)))
          (user-error (mu4e-format "No parts found")))))

    (map! :map mu4e-view-mode-map
          :ne "A" #'+mu4e-view-select-mime-part-action
          :ne "p" #'mu4e-view-save-attachments
          :ne "o" #'+mu4e-view-open-attachment)

    (defun +mu4e-part-selectors (parts)
      "Generate selection strings for PARTS."
      (if parts
          (let (partinfo labeledparts maxfnamelen fnamefmt maxsizelen sizefmt)
            (dolist (part parts)
              (push (list :index (car part)
                          :mimetype (if (and (string= "text/plain" (caaddr part))
                                             (alist-get 'charset (cdaddr part)))
                                        (format "%s (%s)"
                                                (caaddr part)
                                                (alist-get 'charset (cdaddr part)))
                                      (caaddr part))
                          :type (car (nth 5 part))
                          :filename (cdr (assoc 'filename (assoc "attachment" (cdr part))))
                          :size (file-size-human-readable (with-current-buffer (cadr part) (buffer-size)))
                          :part part)
                    partinfo))
            (setq maxfnamelen (apply #'max 7 (mapcar (lambda (i) (length (plist-get i :filename))) partinfo))
                  fnamefmt (format " %%-%ds  " maxfnamelen)
                  maxsizelen (apply #'max (mapcar (lambda (i) (length (plist-get i :size))) partinfo))
                  sizefmt (format "%%-%ds " maxsizelen))
            (dolist (pinfo partinfo)
              (push (cons (concat (propertize (format "%-2s " (plist-get pinfo :index)) 'face '(bold font-lock-type-face))
                                  (when (featurep 'all-the-icons)
                                    (all-the-icons-icon-for-file (or (plist-get pinfo :filename) "")))
                                  (format fnamefmt (or (plist-get pinfo :filename)
                                                       (propertize (plist-get pinfo :type) 'face '(italic font-lock-doc-face))))
                                  (format sizefmt (propertize (plist-get pinfo :size) 'face 'font-lock-builtin-face))
                                  (propertize (plist-get pinfo :mimetype) 'face 'font-lock-constant-face))
                          (plist-get pinfo :part))
                    labeledparts))
            labeledparts))))

  (map! :localleader
        :map mu4e-compose-mode-map
        :desc "send and exit" "s" #'message-send-and-exit
        :desc "kill buffer"   "d" #'message-kill-buffer
        :desc "save draft"    "S" #'message-dont-send
{'fraction_non_alphanumeric': 0.1358743995730297, 'fraction_numerical': 0.012097491549546344, 'mean_word_length': 1.8272350056582423, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 11, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 38, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7221177', 'n_tokens_mistral': 7347, 'n_tokens_neox': 7032, 'n_words': 1677}