Find a file
2022-05-06 13:04:05 +02:00
.gitignore Setup org-agenda launch script 2022-04-11 21:59:34 +02:00
.lohr Setup lohr mirroring 2021-03-30 00:07:35 +02:00
config.org Don't count all new mail as notif worthy 2022-05-06 13:04:05 +02:00
init.el Enable vterm module 2022-05-05 16:30:47 +02:00
packages.el Use beancount module 2022-04-12 12:24:26 +02:00
README.org Add README symlink for GitHub display 2020-04-10 17:42:01 +02:00

Doom Emacs literal configuration

Misc

Lexical bindings

Enable lexical binding, of course…

;;; -*- lexical-binding: t; -*-

Dir local variables

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)

Taking SVG screenshots

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)))

Theme

Main theme

A list of all doom themes can be found here:

https://github.com/hlissner/emacs-doom-themes

(setq doom-theme 'doom-solarized-light)

Dark theme toggle

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))

Font

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 for doom-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))

Line numbers

Possible values of display-line-numbers-type are nil, t, and 'relative.

(setq display-line-numbers-type 'relative)

Battery indicator

I'm on a laptop, so let's display my battery in the modeline:

(display-battery-mode 1)

Programming

Tridactyl mode

(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 ""))

Smart parens

Disable smart parens because half of the time it doesn't do what I want:

(remove-hook 'doom-first-buffer-hook #'smartparens-global-mode)

Rust

Column width

rustfmt limits lines to 100 characters, let's display it correctly.

(add-hook! rustic-mode
  (set-fill-column 100))

Run clippy in rust-analyzer

The default is "check", but I want clippy lints as well.

(setq lsp-rust-analyzer-cargo-watch-command "clippy")

Enable proc macro support

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)

C/C++

Default style

Setup the default format for C/C++ editing.

(add-hook! (c-mode c++-mode)
  (setq c-default-style "gnu")
  (setq c-basic-offset 2))

Nix

Formatting

Use alejandra to format Nix code.

(set-formatter! 'alejandra "alejandra --quiet" :modes '(nix-mode))

Org mode

Directory

Set a default directory for all my org-mode files.

(setq org-directory "~/org/")

Appearance

Fancier ellipsis indicator

(setq org-ellipsis " ▼ ")

Logging

Log state changes in a

:LOGBOOK:
drawer so that it doesn't pollute the main content.

(after! org
  (setq org-log-into-drawer t))

Archiving

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::"))

Agenda setup

Default task keywords

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)"))))

Org capture setup

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)

Main agenda view

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 ""))

Habits

Let's enable the org-habit module:

(add-to-list 'org-modules 'org-habit)

Save all org buffers shortcut

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)

Script to open agenda window automatically

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 " "))

Roam

Setup for org-roam.

Roam Directory

First, set a directory where org-roam will index things.

(setq org-roam-directory (expand-file-name "notes/" org-directory))

org-roam-ui

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))

Export backends

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 specific

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))

Bugfix

Fix a bug with capture mode not working correctly when agenda is opened, stolen from https://github.com/hlissner/doom-emacs/issues/5714#issuecomment-1018788028

(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)))))

Magit

Gitlab CI skip flag

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.

magit-delta

(use-package! magit-delta
  :hook (magit-mode . magit-delta-mode))

Email

(after! mu4e
  <<after-mu4e>>)

Account configuration

This setting instructs mu4e to prompt for login credentials if none are found when trying to connect to one of the servers that match the regex (see variable documentation).

(setq smtpmail-servers-requiring-authorization "smtp.migadu.com\\|smtp.lrde.epita.fr")

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      . "antoine@alarsyo.net")
    (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      . "amartin@lrde.epita.fr")
    (user-full-name         . "Antoine Martin")
    (mu4e-compose-signature . "Antoine Martin"))
  nil)

Sending mail

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)

Reading plain text

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")

Disable org-msg by default

Doom adds a hook, making it impossible to disable. This allows us to toggle it manually.

(after! org-msg
  (setq +mu4e-compose-org-msg-toggle-next nil))

Message quoting style

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:"))

Disable format=flowed

(setq mu4e-compose-format-flowed nil)

Don't permanently delete when trashing mails

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 https://github.com/djcb/mu/issues/1136#issuecomment-1066303788, 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~proc-move
                       docid (mu4e~mark-check-target target) "-N"))))

Add git-apply-path to mu4e actions

;; 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)

Enable auto updates

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")

Ask which address to send with when composing a new mail

(setq mu4e-compose-context-policy 'ask)

Only fetch main directories by default

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"))
    (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)

Headers view format

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)
                            (:from-or-to . 30)
                            (:subject)))

Message view fields

(setq mu4e-view-fields '(:from :to :cc :subject :flags :date :mailing-list :maildir :path :size :tags :attachments :user-agent :signature :decryption))

Mailing list pretty names

(setq mu4e-mailing-list-patterns '("[0-9]+\\.\\(.+\\)\\.gitlab\\.lrde\\.epita\\.fr"
                                   "[0-9]+\\.\\(.+\\)\\.gitlab\\.com"
                                   "\\(.+\\)\\.github\\.com"))

Fix attachment icon with light theme

(setq mu4e-headers-attach-mark (cons "a" (+mu4e-normalised-icon "file-text-o" :color "cyan")))

Bookmarks

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" :key ?U)
                       (: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)))