- Misc
- Theme
- Programming
- Org mode
- Magit
- Email
- Account configuration
- Sending mail
- Reading plain text
- =org-msg= setup
- Message quoting style
- Disable format=flowed
- Don’t permanently delete when trashing mails
- Add git-apply-path to mu4e actions
- Enable auto updates
- Ask which address to send with when composing a new mail
- Only fetch main directories by default
- Headers view format
- Message view fields
- Mailing list pretty names
- Fix attachment icon with light theme
- Bookmarks
- Apply marks on quit
Enable lexical binding, of course…
;;; -*- lexical-binding: t; -*-
Disable these because I don’t use them and don’t want to get prompted by them in some projects.
(setq enable-dir-local-variables nil)
Since Emacs 27, we can take SVG screenshots! Emacs needs to be built with
cairo
to support this.
(defun my/screenshot-svg ()
"Save a screenshot of the current frame as an SVG image.
Saves to a temp file and puts the filename in the kill ring."
(interactive)
(let ((filename (make-temp-file "Emacs" nil ".svg"))
(data (x-export-frames nil 'svg)))
(with-temp-file filename
(insert data))
(kill-new filename)
(message filename)))
A list of all doom themes can be found here:
https://github.com/hlissner/emacs-doom-themes
(setq doom-theme 'doom-one-light)
I’ve come to prefer using a light theme during the day, and a dark theme at night. Using a dark theme with daylight leads to cranking up the screen brightness, which hurts my eyes more than using the light theme.
Set my light and dark themes:
(setq my/light-theme doom-theme
my/dark-theme 'doom-one)
Function to toggle between the two easily:
(defun my/toggle-dark-theme ()
(interactive)
(if (eq my/dark-theme doom-theme)
(load-theme my/light-theme t)
(load-theme my/dark-theme t)))
Bind this to SPC t d
:
(map! :leader
(:prefix-map ("t" . "toggle")
:desc "Dark theme" "d" #'my/toggle-dark-theme))
Doom exposes five (optional) variables for controlling fonts in Doom. Here are the three important ones:
doom-font
doom-variable-pitch-font
doom-big-font
– used fordoom-big-font-mode
; use this for presentations or streaming.
They all accept either a font-spec, font string (Input Mono-12
), or xlfd font
string. You generally only need these two:
(setq doom-font
(font-spec :family "Iosevka Fixed" :size 10.0 :weight 'medium))
Possible values of display-line-numbers-type
are nil
, t
, and 'relative
.
(setq display-line-numbers-type 'relative)
I’m on a laptop, so let’s display my battery in the modeline:
(display-battery-mode 1)
(defvar tridactylrc-font-lock-keywords
`( ;; Line comment
("^[\t ]*\\(\"\\)\\(.*\\)$"
(1 font-lock-comment-delimiter-face)
(2 font-lock-comment-face))
;; Trailing comment
("[\t ]+\\(\"\\)\\([^\"\r\n]*\\)$"
(1 font-lock-comment-delimiter-face)
(2 font-lock-comment-face))
;; String start:
("\\(\"[^\n\r\"]*\"\\)\\|\\('[^\n\r]*'\\)"
(0 font-lock-string-face)) ;; String end;
))
(defvar tridactylrc-mode-syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?' "\"" table)
(modify-syntax-entry ?\" "<" table)
(modify-syntax-entry ?\n ">" table)
table))
(define-derived-mode tridactylrc-mode prog-mode "tridactylrc"
"Major mode for editing tridactylrc configuration files."
:group 'tridactylrc-mode
:syntax-table tridactylrc-mode-syntax-table
(font-lock-add-keywords nil tridactylrc-font-lock-keywords)
(setq-local comment-start "\"")
(setq-local comment-end ""))
Disable smart parens because half of the time it doesn’t do what I want:
(remove-hook 'doom-first-buffer-hook #'smartparens-global-mode)
rustfmt
limits lines to 100 characters, let’s display it correctly.
(add-hook! rustic-mode
(set-fill-column 100))
The default is "check"
, but I want clippy lints as well.
(setq lsp-rust-analyzer-cargo-watch-command "clippy")
By default lsp-mode disable these, I want them.
(setq lsp-rust-analyzer-experimental-proc-attr-macros t)
(setq lsp-rust-analyzer-proc-macro-enable t)
Setup the default format for C/C++ editing.
(add-hook! (c-mode c++-mode)
(setq c-default-style "gnu")
(setq c-basic-offset 2))
Use alejandra to format Nix code.
(set-formatter! 'alejandra '("alejandra" "--quiet") :modes '(nix-mode))
Just load the lilypond mode
(require 'lilypond-init)
Set a default directory for all my org-mode files.
(setq org-directory "~/org/")
(setq org-ellipsis " ▼ ")
Log state changes in a src_org{:LOGBOOK:} drawer so that it doesn’t pollute the main content.
(after! org
(setq org-log-into-drawer t))
I don’t want to see archival files appearing when listing files in the current directory, so hide them by default.
(after! org
(setq org-archive-location ".%s_archive::"))
Here are the keywords I’m using to track task progress. I’m also making use of some automatic state changes.
keyword | meaning |
---|---|
TODO | Self explanatory |
DONE | This task is finished, no longer displayed in the agenda |
CANCELLED | This task isn’t finished but is no longer relevant |
(after! org
(setq org-todo-keywords
'((sequence
"TODO(t)"
"|"
"DONE(d!)"
"CANCELLED(c@/!)")
(sequence
"[ ](T)"
"|"
"[X](D)"))))
Of course I also need to setup capture templates:
The first one just prompts me for a new task to add to my inbox, I can then refile them where I want later.
The second one exists because I like to keep a separate list of articles / papers / books to read.
(after! org
(setq org-capture-templates
'(("t" "New entry" entry (file "inbox.org")
"* TODO %?")
("T" "Task" entry (file+headline "tasks.org" "Misc")
"* TODO %?")
("r" "Reading" entry (file "reading.org")
"* TODO %x"
:immediate-finish t)
("w" "Watching" entry (file "watching.org")
"* TODO %x"
:immediate-finish t))))
I also change the default Doom binding for #'org-capture
to be SPC x
instead
of SPC X
. Also need to rebind what was previously bound to SPC x
, to SPC
X
.
(map! :leader
:desc "Org Capture" "x" #'org-capture
:desc "Pop up scratch buffer" "X" #'doom/open-scratch-buffer)
All these tasks, once captured, are then centralized in my agenda view.
I’m using multiple categories to organize tasks, depending on their triage / status (inspired by https://blog.jethro.dev/posts/org_mode_workflow_preview/).
(after! org-agenda
(setq org-agenda-custom-commands
'((" " "Agenda"
((agenda ""
((org-agenda-span 'day)
(org-agenda-start-day nil)
(org-deadline-warning-days 365)))
(todo "TODO"
((org-agenda-overriding-header "Triage")
(org-agenda-files '("~/org/inbox.org"))))
(todo "TODO"
((org-agenda-overriding-header "Job")
(org-agenda-files '("~/org/job.org"))
(org-agenda-skip-function '(org-agenda-skip-entry-if 'deadline
'scheduled))))
(todo "TODO"
((org-agenda-overriding-header "Tasks")
(org-agenda-files '("~/org/tasks.org"))
(org-agenda-skip-function '(org-agenda-skip-entry-if 'deadline
'scheduled))))
)))))
I want the default agenda view to be a weekly view, with a log of what I’ve done during the day.
(after! org-agenda
(setq org-agenda-span 'week)
(setq org-agenda-start-on-weekday 1)
(setq org-agenda-start-with-log-mode '(clock)))
I also remove the block separators in the agenda view:
(after! org-agenda
(setq org-agenda-block-separator ""))
Let’s enable the org-habit
module:
(add-to-list 'org-modules 'org-habit)
(after! org-agenda
(setq org-habit-show-all-today t))
By default bound to C-x C-s
, rebind it to SPC m s
in org-agenda-mode
:
(map! :after org-agenda
:map org-agenda-mode-map
:localleader
"s" #'org-save-all-org-buffers)
I use this script to automatically open the agenda when pressing a specific key binding in my window manager.
(find-file org-directory)
(with-selected-window (split-window-horizontally)
;; need to wait for the window to appear
(sleep-for 0.1)
(org-agenda nil " "))
Setup for org-roam.
First, set a directory where org-roam
will index things.
(setq org-roam-directory (expand-file-name "notes/" org-directory))
Setup org-roam-ui
(use-package! websocket
:after org-roam)
(use-package! org-roam-ui
:after org-roam
:config (setq org-roam-ui-sync-theme t
org-roam-ui-follow t
org-roam-ui-update-on-save t
org-roam-ui-open-on-start t))
Sometimes I need to export an Org subtree to a file, which is quite easy with
the org
export backend. It doesn’t seem to be enabled by default, so let’s add
it to the list:
(after! org
(add-to-list 'org-export-backends 'org))
Doom makes some changes to org-id behaviour which I don’t like / think are necessary.
(after! org
(setq org-id-locations-file (expand-file-name "~/.config/emacs/.org-id-locations"))
(setq org-id-locations-file-relative nil))
Doom replaces the default tab behavior on headings, this restores the default one. Taken from here.
(after! evil-org
(remove-hook 'org-tab-first-hook #'+org-cycle-only-current-subtree-h))
(add-to-list 'org-modules 'ol-info)
(add-to-list 'org-modules 'ol-doi)
Fix a bug with capture mode not working correctly when agenda is opened, stolen from doomemacs/doomemacs#5714 (comment)
(after! org
(defadvice! dan/+org--restart-mode-h-careful-restart (fn &rest args)
:around #'+org--restart-mode-h
(let ((old-org-capture-current-plist (and (bound-and-true-p org-capture-mode)
(bound-and-true-p org-capture-current-plist))))
(apply fn args)
(when old-org-capture-current-plist
(setq-local org-capture-current-plist old-org-capture-current-plist)
(org-capture-mode +1)))))
This option tells GitLab to skip the CI run for this push, in case I know it’s not ready yet.
(after! magit
(transient-append-suffix 'magit-push "-n"
'(4 "-s" "Skip GitLab CI" "--push-option=ci.skip")))
GitLab push options are documented here.
(after! mu4e
<<after-mu4e>>)
Setup my main email account.
(set-email-account! "alarsyo"
'((mu4e-sent-folder . "/alarsyo/Sent")
(mu4e-drafts-folder . "/alarsyo/Drafts")
(mu4e-refile-folder . "/alarsyo/Archive")
(mu4e-trash-folder . "/alarsyo/Trash")
(user-mail-address . "[email protected]")
(user-full-name . "Antoine Martin")
(mu4e-compose-signature . "Antoine Martin"))
t)
(set-email-account! "lrde"
'((mu4e-sent-folder . "/lrde/Sent")
(mu4e-drafts-folder . "/lrde/Drafts")
(mu4e-trash-folder . "/lrde/Trash")
(user-mail-address . "[email protected]")
(user-full-name . "Antoine Martin")
(mu4e-compose-signature . "Antoine Martin"))
nil)
(set-email-account! "prologin"
'((mu4e-sent-folder . "/prologin/Sent")
(mu4e-drafts-folder . "/prologin/Drafts")
(mu4e-trash-folder . "/prologin/Trash")
(user-mail-address . "[email protected]")
(user-full-name . "Antoine Martin")
(mu4e-compose-signature . "Antoine Martin"))
nil)
I use msmtp
as a SMTP forwarder
(setq sendmail-program (executable-find "msmtp")
send-mail-function #'smtpmail-send-it
message-sendmail-f-is-evil t
message-sendmail-extra-arguments '("--read-envelope-from")
message-send-mail-function #'message-send-mail-with-sendmail)
I don’t want something like ws-butler
to mess with my formatting, especially
the trailing space after a signature delimiter:
(add-hook! mu4e-compose-mode
(ws-butler-mode -1))
Ask the gnus-view
(default viewer used by mu4e
) to avoid HTML whenever
possible.
(add-to-list 'mm-discouraged-alternatives "text/html")
(add-to-list 'mm-discouraged-alternatives "text/richtext")
Add -:nil
to the export options list, otherwise the --
in the signature gets
converted to a single hyphen when exporting to utf-8. See info:org#Export settings
(setq org-msg-options (concat org-msg-options " -:nil"))
Setup signature as well. The double backslashes are there to make sure that the linebreak is preserved even in the html export, which otherwise wraps the signature lines as a single paragraph.
(setq org-msg-signature "\n#+begin_signature\n-- \\\\\nAntoine Martin\n#+end_signature")
I don’t want to send HTML emails at all unless I choose to explicitely.
(setq org-msg-default-alternatives '((new utf-8)
(reply-to-text utf-8)
(reply-to-html utf-8)))
Has to be duplicated because mu4e
doesn’t use message-cite-style
’s values.
(defconst message-cite-style-custom
'((message-cite-function 'message-cite-original-without-signature)
(message-citation-line-function 'message-insert-formatted-citation-line)
(message-cite-reply-position 'traditional)
(message-yank-prefix "> ")
(message-yank-cited-prefix "> ")
(message-yank-empty-prefix ">")
(message-citation-line-format "%f writes:"))
"Message citation style used for email. Use with `message-cite-style'.")
(after! message
(setq message-cite-style message-cite-style-custom
message-cite-function 'message-cite-original-without-signature
message-citation-line-function 'message-insert-formatted-citation-line
message-cite-reply-position 'traditional
message-yank-prefix "> "
message-yank-cited-prefix "> "
message-yank-empty-prefix ">"
message-citation-line-format "%f writes:"))
(setq mu4e-compose-format-flowed nil)
By default mu4e
sets the trashed
flag on emails trashed using the d
keybinding. This just replaces the action to just move the message to the trash
instead.
See djcb/mu#1136 (comment), the code will have to be adapted soon.
(setf (alist-get 'trash mu4e-marks)
(list :char '("d" . "▼")
:prompt "dtrash"
:dyn-target (lambda (target msg)
(mu4e-get-trash-folder msg))
:action (lambda (docid msg target)
(mu4e--server-move
docid (mu4e--mark-check-target target) "-N"))))
;; TODO: upstream this, Doom emacs adds a view in browser action but it seems
;; to be present by default now.
(setq mu4e-view-actions
(remove '("View in browser" . mu4e-action-view-in-browser) mu4e-view-actions))
(add-to-list 'mu4e-view-actions
'("GitApply" . mu4e-action-git-apply-patch) t)
(add-to-list 'mu4e-view-actions
'("MboxGitApply" . mu4e-action-git-apply-mbox) t)
mu4e
refreshes my email in the background.
(setq mu4e-update-interval 900)
If it fetches new mail while I’m browsing some messages, it will refresh the headers view, potentially loosing context (like some messages that got marked as read because I skimmed over them, but that I don’t want to see disappear yet). So let’s disable this automatic update of headers:
(setq mu4e-headers-auto-update nil)
Additionally, don’t show all new mail in the modeline, only relevant ones:
(setq mu4e-alert-interesting-mail-query "flag:unread AND NOT flag:list")
(setq mu4e-compose-context-policy 'ask)
I have a lot (100+) directories on my main email account, I only want to fetch the “important” ones (i.e. those coming from real individuals, addressed to me directly) when I ask for a refresh explicitely (updating everything in the background is fine the rest of the time).
Let’s define a new function that does just that:
(defun my/mu4e-update-main-mail-and-index (run-in-background)
"Get mail for all folders, not just the main ones"
(interactive "P")
(let ((mu4e-get-mail-command "mbsync alarsyo-main lrde prologin-main"))
(mu4e-update-mail-and-index run-in-background)))
Let’s also bind it to u
in the main view, overriding the default binding
(which I’ll remap to U
).
(map! :map mu4e-main-mode-map
:ne "u" #'my/mu4e-update-main-mail-and-index
:ne "U" #'mu4e-update-mail-and-index)
Use “french” date format in header view:
(setq mu4e-headers-date-format "%d/%m/%y")
Set the time display to 24h:
(setq mu4e-headers-time-format "%T")
Setup the headers view columns how I like them
(setq mu4e-headers-fields '((:account-stripe . 1)
;; just enough room for dd/mm/yy or hh:mm:ss
(:human-date . 8)
(:flags . 6)
(:mailing-list . 30)
(:maildir . 30)
(:from-or-to . 30)
(:subject)))
(setq mu4e-view-fields '(:from :to :cc :subject :flags :date :mailing-list :maildir :path :size :tags :attachments :user-agent :signature :decryption))
(setq mu4e-mailing-list-patterns '("[0-9]+\\.\\(.+\\)\\.gitlab\\.lrde\\.epita\\.fr"
"[0-9]+\\.\\(.+\\)\\.gitlab\\.com"
"\\(.+\\)\\.github\\.com")
mu4e-user-mailing-lists '(("info.prologin.org" . "Infos Prologin")
("membres.ml.prologin.org" . "Membres Prologin")))
(setq mu4e-headers-attach-mark (cons "a" (+mu4e-normalised-icon "file-text-o" :color "cyan")))
Let’s not display messages from mailing lists in main views, leave them to specific bookmarks.
(setq mu4e-bookmarks '((:name "Unread messages" :query "flag:unread AND NOT flag:list" :key ?u)
(:name "Today's messages" :query "date:today..now AND NOT flag:list" :key ?t)
(:name "Last 7 days" :query "date:7d..now AND NOT flag:list" :hide-unread t :key ?w)
(:name "Messages with images" :query "mime:image/* AND NOT flag:list" :key ?p)
(:name "All unread messages" :query "flag:unread AND NOT maildir:/prologin/*" :key ?U)
(:name "All Prologin messages" :query "flag:unread AND maildir:/prologin/*" :key ?P)
(:name "Today's messages (lists included)" :query "date:today..now" :key ?T)
(:name "Last 7 days (lists included)" :query "date:7d..now" :hide-unread t :key ?W)
(:name "Orgmode mailing list new posts" :query "list:emacs-orgmode.gnu.org AND flag:unread" :key ?o)
(:name "All messages with images" :query "mime:image/*" :key ?P)))
I don’t want confirm to apply marks everytime I quit the headers view.
(setq mu4e-headers-leave-behavior 'apply)