Skip to content

wo/emacs-config

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

92 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Doom emacs config

I use doom emacs to write notes, articles, and code, as an email client, and to keep track of projects and appointments. Most of this happens in org-mode.

General settings

Speed up processing of this file

;;; config.el -*- lexical-binding: t; -*-
(advice-remove 'set-window-buffer #'ad-Advice-set-window-buffer)

Personal info

(setq user-full-name "Wolfgang Schwarz"
      user-mail-address "wo@umsu.de")

Automatically find projects on startup

(setq projectile-project-search-path '("~/words/" "~/notes/" "~/programming/"))

Make Y yank the whole line

(evil-put-command-property 'evil-yank-line :motion 'evil-line)

Fix broken M-RET and C-RET behaviour in org files

;; (setq org-insert-heading-respect-content nil)
(map! :after evil-org
      :map evil-org-mode-map
      :ni "C-<return>" #'org-insert-heading-respect-content)

Delete files to trash

(setq-default delete-by-moving-to-trash t)

Raise undo-limit and allow fine-grained undo

(setq undo-limit 80000000
      evil-want-fine-undo t)

Auto-save buffers, backups in ~/.emacsbup/

(auto-save-visited-mode 1)
(setq auto-save-timeout 20)
(setq auto-save-file-name-transforms
  `((".*" "~/.emacsbup/" t)))
(setq backup-by-copying t      ; don't clobber symlinks
      backup-directory-alist '(("." . "~/.emacsbup"))
      delete-old-versions t
      make-backup-files t
      vc-make-backup-files t ; backup files even if version controlled
      kept-new-versions 6
      kept-old-versions 2
      version-control t) ; use versioned backups

Spell-checking

Why is it so hard to set up spell-checking with multiple dictionaries?

;; (setq ispell-local-dictionary "en_GB")
;; (setq ispell-program-name "hunspell")
;; (setq ispell-hunspell-dictionary-alist '(("de_DE"
;;                                             "[[:alpha:]]"
;;                                             "[^[:alpha:]]"
;;                                             "['.ß-]" 'many-otherchars
;;                                             ("-r" "-d" "de_DE") nil utf-8)
;;                                            ("en_GB"
;;                                             "[[:alpha:]]"
;;                                             "[^[:alpha:]]"
;;                                             "[']" nil
;;                                             ("-r" "-d" "en_GB") nil utf-8)))
;; (when (boundp 'ispell-hunspell-dictionary-alist)
;;   (setq ispell-hunspell-dictionary-alist ispell-local-dictionary-alist))
;; For saving words to the personal dictionary, don't infer it from
;; the locale, otherwise it would save to ~/.hunspell_en_GB.
;; (setq ispell-personal-dictionary "~/.hunspell_personal")
;; The personal dictionary file has to exist, otherwise hunspell will
;; silently not use it.

The guess-language package guesses which spellchecker to use, but I can’t get it to work properly.

;; (setq guess-language-langcodes
;;   '((en . ("en_GB" "English"))
;;     (de . ("de_DE" "German"))))
;; (setq guess-language-languages '(en de))
;; (add-hook 'org-mode-hook (lambda () (guess-language-mode 1)))

So I’m switching manually:

;; (defun fd-switch-dictionary()
;; (interactive)
;; (let* ((dic ispell-current-dictionary)
;;     (change (if (string= dic "deutsch8") "english" "deutsch8")))
;; (ispell-change-dictionary change)
;; (message "Dictionary switched from %s to %s" dic change)
;; ))

;; (global-set-key (kbd "<f9>")   'fd-switch-dictionary)

Turn off smartparens in org

I get confused by automatically inserted closing brackets and parentheses.

(remove-hook 'doom-first-buffer-hook #'smartparens-global-mode)
;; (add-hook 'org-mode-hook 'turn-off-smartparens-mode)

Fix ws-butler

Don’t erase whitespace just before cursor on auto-save, and prevent resulting error message, from here:

(after! ws-butler
  (setq ws-butler-keep-whitespace-before-point t)
  (setq ws-butler-trim-predicate
      (lambda (beg end)
        (let* ((current-line (line-number-at-pos))
               (beg-line (line-number-at-pos beg))
               (end-line (line-number-at-pos end))
               ;; Assuming the use of evil-mode for insert mode detection. Adjust if using a different system.
               (in-insert-mode (and (bound-and-true-p evil-mode)
                                    (eq 'insert evil-state))))
          ;; Return true (allow trimming) unless in insert mode and the current line is within the region.
          (not (and in-insert-mode
                    (>= current-line beg-line)
                    (<= current-line end-line))))))
)

A function to rename the current file and buffer

from stackoverflow:

(defun rename-file-and-buffer ()
  "Renames current buffer and file it is visiting."
  (interactive)
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (if (not (and filename (file-exists-p filename)))
        (message "Buffer '%s' is not visiting a file!" name)
      (let ((new-name (read-file-name "New name: " filename)))
        (cond ((get-buffer new-name)
               (message "A buffer named '%s' already exists!" new-name))
              (t
               (rename-file name new-name 1)
               (rename-buffer new-name)
               (set-visited-file-name new-name)
               (set-buffer-modified-p nil)))))))

Appearance

Start in fullscreen

(add-to-list 'default-frame-alist '(fullscreen . maximized))

Open org files folded

Show only headings on opening:

(setq org-startup-folded 'content)

Increase line-spacing

(setq-default line-spacing 0.2)

Show/hide absolute or relative line numbers

I’m going back and forth between relative line numbers and no line numbers.

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

Show word count

(setq doom-modeline-enable-word-count t)

Theme and font

(setq
      doom-font (font-spec :family "monospace" :size 15)
      doom-theme 'doom-one
      doom-enable-brighter-comments 1
      +doom-dashboard-banner-file (expand-file-name "logo.png" doom-user-dir)
      )

Colour adjustments for org

(after! org
  (set-face-attribute 'org-link nil :weight 'normal :background nil)
  (set-face-attribute 'org-code nil :foreground "#a9a1e1" :background nil)
  (set-face-attribute 'org-date nil :foreground "#5B6268" :background nil)
  (set-face-attribute 'org-level-1 nil :foreground "steelblue2" :background nil :height 1.2 :weight 'bold)
  (set-face-attribute 'org-level-2 nil :foreground "slategray2" :background nil :height 1.1 :weight 'bold)
  (set-face-attribute 'org-level-3 nil :foreground "SkyBlue2" :background nil :height 1.0 :weight 'normal)
  (set-face-attribute 'org-level-4 nil :foreground "DodgerBlue2" :background nil :height 1.0 :weight 'normal)
  (set-face-attribute 'org-level-5 nil :weight 'normal) (set-face-attribute 'org-level-6 nil :weight 'normal)
  (set-face-attribute 'org-document-title nil :foreground "SlateGray1" :background nil :height 1.75 :weight 'bold)
  )

Prettier org bullets

(after! org
  (setq org-ellipsis ""
        org-bullets-bullet-list '("·"))
  )

Center org files, and don’t indent

Don’t indent:

(setq org-startup-indented nil
      org-adapt-indentation nil)

Center text:

(use-package olivetti
  :commands olivetti-mode
  :config
  (setq olivetti-body-width 0.7)
  (setq olivetti-minimum-body-width 90))
(add-hook 'org-mode-hook #'olivetti-mode)
(map!
:leader
:desc "toggle olivetti-mode" "t o" #'olivetti-mode
)

Hide emphasis and sub/superscript markers

Hide slashes and stars:

(after! org
  ;; (setq org-hide-emphasis-markers t)
  (setq org-hide-emphasis-markers nil)
  )

Add colour to italics:

(after! org
  (add-to-list 'org-emphasis-alist '("/" (italic :foreground "#dddd99")))
  )

Properly display sub- and superscripts:

(after! org
  (setq org-pretty-entities-include-sub-superscripts t)
  )

Add colour to comment blocks and make comments brigher

(defun highlight-org-comment-blocks-with-comment-face ()
  (set-face-foreground 'font-lock-comment-face "#99aabb")
  (font-lock-add-keywords nil
    '(("^[ \t]*#\\+begin_comment[ \t]*$" 0 'font-lock-comment-face t)
      ("^[ \t]*#\\+end_comment[ \t]*$" 0 'font-lock-comment-face t)
      ("^[ \t]*#\\+begin_comment[ \t]*\\([[:space:]\n]*\\(.\\|\n\\)*?\\)[ \t]*#\\+end_comment[ \t]*$"
       (1 'font-lock-comment-face t)))))

(add-hook 'org-mode-hook 'highlight-org-comment-blocks-with-comment-face)

Disable strikethrough rendering

I often have two ‘+’ in a line, and never want to strike through text.

(after! org
  (add-to-list 'org-emphasis-alist '("+" (:strike-through f)))
  )

Expand invisible markup when editing

(use-package! org-appear
  :hook (org-mode . org-appear-mode)
  :config
  (setq org-appear-autoemphasis t
        org-appear-autosubmarkers t
        org-appear-autolinks nil)
  ;; for proper first-time setup, `org-appear--set-elements'
  ;; needs to be run after other hooks have acted.
  (run-at-time nil nil #'org-appear--set-elements))

Render LaTeX commands for special characters as unicode

This way, I can simply type LaTeX commands like ∀ or ¢ or \aleph to insert the relevant symbols:

(after! org
  (setq org-pretty-entities t)
  )

Some symbols I often use aren’t standardly recognised by org-pretty-entities. But we can add them:

(after! org
  (setq org-entities-user '(
                            ("bot" "\\bot" nil "" "" "" "")
                            ("top" "\⊤" nil "" "" "" "")
                            ("box" "$\\box$" nil "" "" "" "")
                            ("diamond" "$\\diamond$" nil "" "" "" "")
                            ("Box" "$\\Box$" nil "" "" "" "")
                            ("Diamond" "$\Diamond$" nil "" "" "" "")
                            ("boxright" "$\\boxright$" nil "" "" "" "□→")
                            ("models" "$\\models$" nil "" "" "" "")
                            ("vdash" "$\\vdash$" nil "" "" "" "")
                            ("leadsto" "$\leadsto$" nil "" "" "" "")
                            ("llb" "$\\llbracket$" nil "" "" "" "")
                            ("rrb" "$\\rrbracket$" nil "" "" "" "")
                           )
        )
  )

LaTeX preview in org buffers

Preview LaTeX environments in org buffers, mostly adapted from tecosaur:

(after! org
  ; cdlatex allows, among other things, inserting latex environments with C-c {:
  ;; (add-hook 'org-mode-hook 'turn-on-org-cdlatex)
  ; toggle LaTeX preview as cursor moves in/out:
  (add-hook 'org-mode-hook 'org-fragtog-mode)
  ; the default dvipng program cuts off qtree lines, so we use dvisvgm instead:
  (setq org-preview-latex-default-process 'dvisvgm)
  ; make LaTex snippets look better:
  (setq org-highlight-latex-and-related '(native script entities))
  ; automatically preview latex when file is opened:
  (setq org-startup-latex-with-latex-preview t)
)

Customize rendering of LaTeX fragments:

(setq org-format-latex-header "\\documentclass{article}
\\usepackage[usenames]{xcolor}

\\usepackage[T1]{fontenc}
\\usepackage{mathtools}
\\usepackage{textcomp,txfonts,latexsym,amssymb}
\\usepackage[makeroom]{cancel}
\\usepackage{qtree}
\\usepackage{booktabs}

\\newcommand{\\sem}[2][]{\\mbox{$[\\![ \#2 ]\\!]^{\#1}$}}
\\newcommand{\\Fr}[1][]{\\mathfrak{\#1}}
\\newcommand{\\Sc}[1][]{\\mathfrak{\#1}}
\\renewcommand{\\t}[1]{\\ensuremath{\\langle #1 \\makebox[.2ex]{}\\rangle}}

\\pagestyle{empty}
\\setlength{\\textwidth}{\\paperwidth}
\\addtolength{\\textwidth}{-3cm}
\\setlength{\\oddsidemargin}{1.5cm}
\\addtolength{\\oddsidemargin}{-2.54cm}
\\setlength{\\evensidemargin}{\\oddsidemargin}
\\setlength{\\textheight}{\\paperheight}
\\addtolength{\\textheight}{-\\headheight}
\\addtolength{\\textheight}{-\\headsep}
\\addtolength{\\textheight}{-\\footskip}
\\addtolength{\\textheight}{-3cm}
\\setlength{\\topmargin}{1.5cm}
\\addtolength{\\topmargin}{-2.54cm}
\\usepackage{arev}
\\usepackage{arevmath}
")

Increase font-size:

(after! org
  (setq org-format-latex-options (plist-put org-format-latex-options :scale 1.2))
)

Snippets and auto-complete

Snippets

Snippets are useful for quickly inserting environments, complex logic expressions and the like. (C-s brings up the menu of predefined snippets, as per below; M-x yas-new-snippet creates a new snippet.)

(setq yas-snippet-dirs '("~/.doom.d/snippets"))

Auto-complete

The company package suggests completions for words. I rarely use this.

(after! company
  (setq company-idle-delay 2
        company-minimum-prefix-length 1)
  (setq company-show-quick-access t)
  ;; only autocomplete words, not numerals:
  (setq company-dabbrev-char-regexp "[A-z:-]")
  ;; make aborting less annoying:
  (add-hook 'evil-normal-state-entry-hook #'company-abort)
)

Org project management and agenda

I use separate org files for different projects (e.g. research, teaching, supervision, software projects). Often these org files lie in dedicated project directories, but they are all symlinked to my ~/org directory.

(after! org
  (setq org-directory "~/org")
  (setq org-agenda-files '("~/org"))
  )

Quick access to project files

I use SPC - to quickly access the project files. (This doesn’t seem work if ~/org is a git repository because then symlinks are ignored.)

(map!
 :leader
 :desc "open ~/org file" "-"  '(lambda () (interactive) (ido-find-file-in-dir "~/org/"))
 )

‘TODO’ states

(after! org
  (setq org-todo-keywords '((sequence "TODO(t)" "NEXT(n)" "ACTV(a)" "WAIT(w)" "URGT(u)" "|" "DONE(d)" "CANC(c)")))
  (setq org-todo-keyword-faces
    (quote (("TODO" :foreground "#69f" :weight bold)
            ("NEXT" :foreground "#6cc" :weight bold)
            ("ACTV" :foreground "#fc6" :weight bold)
            ("URGT" :foreground "#f66" :weight bold)
            ("WAIT" :foreground "#699" :weight bold)
            ("DONE" :foreground "#676" :weight bold)
            ("CANC" :foreground "#676" :weight bold))))
  )

Agenda

Basic config:

(use-package! org-agenda
  :init
  (map! "<f1>"'(lambda (&optional arg) (interactive) (org-agenda arg " ")))
  (setq org-agenda-skip-scheduled-if-done t
        org-agenda-skip-deadline-if-done t
        org-agenda-include-deadlines t
        org-agenda-include-diary t  ;; Include diary entries (birthdays)
        org-agenda-block-separator nil
        org-log-repeat nil ; don't log state changes
        ; show clocked items in the agenda:
        ; org-agenda-start-with-log-mode t
        )
  ;; :config
  ;; (setq org-columns-default-format "%40ITEM(Task) %Effort(EE){:} %CLOCKSUM(Time Spent) %SCHEDULED(Scheduled) %DEADLINE(Deadline)")
  )

Show birthdays:

;; (use-package! memoize
;;   :ensure t)

;(use-package! org-contacts
;  :after org
;  :init
;  (setq org-contacts-birthday-property "BIRTHDAY")
;  (setq org-contacts-files '("~/org/contacts.org"))
  ;; :config
  ;; (require 'memoize)  ;; Ensure memoize is loaded explicitly
;)

My custom agenda view:

(use-package! org-super-agenda
  :after org-agenda
  :init
  ;; don't break evil on org-super-agenda headings, see https://github.com/alphapapa/org-super-agenda/issues/50
  (setq org-super-agenda-header-map (make-sparse-keymap))

  ;; (setq org-agenda-span 1; show only current day
  ;;       org-agenda-start-day nil
  ;;       )
  (setq org-agenda-custom-commands
        '((" " "Today"
           ((agenda "" ((org-agenda-span 1)
                        (org-agenda-start-day nil)
                        (org-agenda-overriding-header "Day Agenda\n")
                        (org-super-agenda-groups
                         '((:name "" :time-grid t :date today :order 1)
                           (:name "Deadlines" :deadline t :order 2)
                            ;; catch "Other Items", e.g. scheduled yesterday:
                           (:name " " :date t :order 2)
                           ))))
            (alltodo "" ((org-agenda-overriding-header "")
                         (org-super-agenda-groups
                          '(
                            (:name "Write" :and(:tag "write" :todo ("ACTV" "URGT")))
                            (:name "Read" :and(:tag "read" :todo ("ACTV" "URGT")))
                            (:name "Programming" :and(:tag "prog" :todo ("ACTV" "URGT")))
                            (:name "Other" :todo ("ACTV" "URGT"))
                            (:name "Routines" :tag "routine")
                            (:name "Deadlines" :deadline t :order 2)
                            (:name "To Read" :and(:tag "read" :todo "NEXT"))
                            (:name "To Write" :and(:tag "write" :todo "NEXT"))
                            (:name "Programming tasks" :and(:tag "prog" :todo "NEXT"))
                            (:name "Other tasks" :todo "NEXT")
                            (:name "Waiting" :todo "WAIT")
                            ;; (:name "To refile" :file-path "inbox.org")
                            ;; (:name "Active projects"
                            ;;        :file-path "journal/")
                            ;; (:name "Future Schedule"
                            ;;        :scheduled future
                            ;;        :order 8)
                            ;; (:name "Projects"
                            ;;        :tag "project"
                            ;;        :order 5)
                            (:discard (:anything t))
                            )
                          )
                         )
            ))
          ))
  )

  (custom-set-faces!
    '(org-agenda-day :foreground "#ff0000"))

  :config
  (org-super-agenda-mode)
  )

Calendar

(use-package! calfw
  :after org
  :init
  (map! "<f2>"'(lambda (&optional arg) (interactive) (cfw:open-org-calendar)))
  (setq cfw:render-line-breaker 'cfw:render-line-breaker-wordwrap) ; doesn't seem to work
  (setq calendar-week-start-day 1)
  )

Display UK bank holidays only (from https://emacs.stackexchange.com/questions/44851/uk-holidays-definitions):

(setq calendar-holidays
      '((holiday-fixed 1 1 "New Year's Day")
        (holiday-new-year-bank-holiday)
        (holiday-fixed 2 14 "Valentine's Day")
        (holiday-fixed 3 17 "St. Patrick's Day")
        (holiday-fixed 4 1 "April Fools' Day")
        (holiday-easter-etc -47 "Shrove Tuesday")
        (holiday-easter-etc -21 "Mother's Day")
        (holiday-easter-etc -2 "Good Friday")
        (holiday-easter-etc 0 "Easter Sunday")
        (holiday-easter-etc 1 "Easter Monday")
        (holiday-float 5 1 1 "Early May Bank Holiday")
        (holiday-float 5 1 -1 "Spring Bank Holiday")
        (holiday-float 6 0 3 "Father's Day")
        (holiday-float 8 1 -1 "Summer Bank Holiday")
        (holiday-fixed 10 31 "Halloween")
        (holiday-fixed 12 24 "Christmas Eve")
        (holiday-fixed 12 25 "Christmas Day")
        (holiday-fixed 12 26 "Boxing Day")
        (holiday-christmas-bank-holidays)
        (holiday-fixed 12 31 "New Year's Eve")))
;; N.B. It is assumed that 1 January is defined with holiday-fixed -
;; this function only returns any extra bank holiday that is allocated
;; (if any) to compensate for New Year's Day falling on a weekend.
;;
;; Where 1 January falls on a weekend, the following Monday is a bank
;; holiday.
(defun holiday-new-year-bank-holiday ()
  (let ((m displayed-month)
        (y displayed-year))
    (calendar-increment-month m y 1)
    (when (<= m 3)
      (let ((d (calendar-day-of-week (list 1 1 y))))
        (cond ((= d 6)
                (list (list (list 1 3 y)
                            "New Year's Day Bank Holiday")))
              ((= d 0)
                (list (list (list 1 2 y)
                            "New Year's Day Bank Holiday"))))))))

;; N.B. It is assumed that 25th and 26th are defined with holiday-fixed -
;; this function only returns any extra bank holiday(s) that are
;; allocated (if any) to compensate for Christmas Day and/or Boxing Day
;; falling on a weekend.
(defun holiday-christmas-bank-holidays ()
  (let ((m displayed-month)
        (y displayed-year))
    (calendar-increment-month m y -1)
    (when (>= m 10)
      (let ((d (calendar-day-of-week (list 12 25 y))))
        (cond ((= d 5)
                (list (list (list 12 28 y)
                            "Boxing Day Bank Holiday")))
              ((= d 6)
                (list (list (list 12 27 y)
                            "Boxing Day Bank Holiday")
                      (list (list 12 28 y)
                            "Christmas Day Bank Holiday")))
              ((= d 0)
                (list (list (list 12 27 y)
                            "Christmas Day Bank Holiday"))))))))

Alerts

(defun my-appt-send-notification (min-to-app new-time msg)
  "Send a notification using notify-send."
  (call-process "notify-send" nil 0 nil
                "-u" "critical" ;; Set urgency to critical
                "-t" "10000"    ;; Show notification for 10 seconds
                (format "Appointment in %s minutes" min-to-app) msg))

(setq appt-disp-window-function 'my-appt-send-notification)
(setq appt-message-warning-time 15)  ;; first notification 15 minutes before
(setq appt-display-interval 10)  ;; second notification 5 minutes before

(defun my-refresh-appt ()
  "Refresh appointments from the Org agenda."
  (setq appt-time-msg-list nil)
  (org-agenda-to-appt))

(appt-activate 1)

; refresh appt on startup, after creating the agenda, and every hour:
(my-refresh-appt)
(add-hook 'org-agenda-finalize-hook 'my-refresh-appt)
(run-at-time "24:01" 3600 'my-refresh-appt)

A function to schedule tasks for the last day of the month

From stackoverflow. Usage:

(defun diary-last-day-of-month (date)
"Return `t` if DATE is the last day of the month."
  (let* ((day (calendar-extract-day date))
         (month (calendar-extract-month date))
         (year (calendar-extract-year date))
         (last-day-of-month
            (calendar-last-day-of-month month year)))
    (= day last-day-of-month)))

Capture templates

(after! org-capture
  (setq org-capture-templates '(
          ;; ("t" "single task (todo.org)" entry (file+headline "todo.org" "Single Tasks")
          ;;  "\n* TODO %?")
          ("t" "TODO (single task)")
          ("tt" "General (todo.org)" entry (file+headline "todo.org" "Single Tasks")
                           "\n* TODO %?" :empty-lines 1)
          ("tr" "Research (research.org)" entry (file+headline "research.org" "Single Tasks")
                           "\n* TODO %?" :empty-lines 1)
          ("ta" "Uni Admin (admin.org)" entry (file+headline "admin.org" "Single Tasks")
                           "\n* TODO %?" :empty-lines 1)
          ("to" "Philosophical Progress (opp.org)" entry (file+headline "opp.org" "Tasks")
                           "\n* TODO %?  %(org-set-tags \"prog\")" :empty-lines 1)
          ("ti" "Investing (investing.org)" entry (file+headline "investing.org" "App Tasks")
                           "\n* TODO %?  %(org-set-tags \"prog\")" :empty-lines 1)
          ("tp" "Tree Proof Generator (trees-todo.org)" entry (file+headline "tpg.org" "Tasks")
                           "\n* TODO %?  %(org-set-tags \"prog\")" :empty-lines 1)
          ("s" "scheduled task (schedule.org)" entry (file+headline "schedule.org" "Tickler")
                           "\n* TODO %?\nSCHEDULED: %^t\n" :empty-lines 1)
          ("b" "buy (add to shopping list in todo.org)" entry (file+headline "todo.org" "Shopping list")
                           "\n* TODO buy %?" :empty-lines 1)
          ("a" "appointment (schedule.org)" entry (file+headline "schedule.org" "Calendar")
                           "\n* %?\n%^t" :empty-lines 1)
          ("i" "inbox entry" entry (file "inbox.org")
                           "\n\n* %?" :empty-lines 1)
          ("j" "journal/logbook entry (logbook.org)" entry (file+datetree "logbook.org")
                           "* %<%H:%M>\n%?\n" :tree-type week)
          ; from browser:
          ("l" "link (from browser)" entry (file "inbox.org")
           ;; "* %a\n %?\n %i" :immediate-finish txx
                           "\n* %a\n %?\n %i\n")
          )
    )
    (setq org-protocol-default-template-key "l")
  )

Fix broken capture mode after agenda

see doomemacs/doomemacs#5714

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

Refiling

Create new parent nodes when refiling by adding /New Heading in the prompt:

(after! org
  (setq org-refile-allow-creating-parent-nodes 'confirm)
  )

I need to learn how to refile better.

;; org-refile:
;; (setq org-refile-targets (quote (("projects.org" :maxlevel . 5)
;;                                  ("archived_projects.org" :maxlevel . 5))))
;; (setq org-outline-path-complete-in-steps nil         ; Refile in a single go
;;       org-refile-use-outline-path t)                  ; Show full paths for refiling

A function to restore layout for weekly review

(defun my-review-layout ()
  (interactive)
  (delete-other-windows) ;; Start with a clean slate
  (set-window-buffer (selected-window) (find-file-noselect "~/org/logbook.org"))
  (split-window-right) ;; Two columns
  (other-window 1)
  (set-window-buffer (selected-window) (find-file-noselect "~/org/journal/2025-phil.org"))
  (evil-window-split)
  (other-window 1)
  (set-window-buffer (selected-window) (find-file-noselect "~/org/journal/2025-neben.org"))
  (evil-window-split)
  (other-window 1)
  (set-window-buffer (selected-window) (find-file-noselect "~/org/journal/2025-privat.org"))
  (other-window 1)
)

Writing in org-mode

Writeroom mode

Distraction-free prose writing. This comes from the :ui zen module.

(setq +zen-text-scale 0.9
      writeroom-extra-line-spacing 0.3
      doom-variable-pitch-font (font-spec :family "Fira Sans" :size 18)
      writeroom-fullscreen-effect t
 )

Tab behaviour

Make TAB insert 4 spaces but do all the smart stuff it does in programming mode.

(after! org
  (setq-local tab-width 4)
  (setq indent-line-function 'indent-relative-maybe)
  (map! :map org-mode-map
        :i [tab] #'indent-for-tab-command
        :i "TAB" #'indent-for-tab-command
        :n [tab] #'indent-for-tab-command
        :n "TAB" #'indent-for-tab-command))

Automatic line-breaks?

I sometimes like automatic line breaks when I write prose:

; (after! org
  ; (add-hook 'org-mode-hook #'auto-fill-mode)
; )
(map!
 :leader
 :desc "toggle auto-fill-mode" "t a" #'auto-fill-mode
 )
(defun my-semantic-linebreaks-in-paragraph ()
  "Modify the current paragraph by removing line breaks and adding line breaks after punctuation etc."
  (interactive)
  (let ((orig-point (point)))
    (save-excursion
      (let ((paragraph-start (progn (backward-paragraph) (point)))
            (paragraph-end (progn (forward-paragraph) (point))))
        (save-restriction
          (narrow-to-region paragraph-start paragraph-end)
          (goto-char (point-min))
          (while (re-search-forward "\\([^[:cntrl:]]\\)\n *" nil :noerror)
            (replace-match "\\1 " nil nil))
          (goto-char (point-min))
          (while (re-search-forward "\\([.,;:]\\|iff\\|that\\) " nil :noerror)
            (replace-match "\\1\n" nil)))))
    (goto-char orig-point)))

Citations

Citation management used to be a mess. Now it’s fairly easy with the new org-internal citation format and the citar package.

(use-package! citar
  :hook
  (LaTeX-mode . citar-capf-setup)
  (org-mode . citar-capf-setup)) ; doesn't work :(
  :config
  (setq! citar-bibliography '("~/notes/literature.bib")
         citar-library-paths '("~/papers/[A-Z]/")
         citar-notes-paths '("~/notes/literature/")
         org-cite-global-bibliography '("~/notes/literature.bib")
         citar-org-roam-note-title-template "${author editor:*%sn} ${date year issued:4} ${title}"
         citar-org-roam-cpature-template-key "l"
         citar-org-roam-subdir "literature"
  )
;; (use-package citar-org-roam
;;   :after (citar org-roam)
;;   :config (citar-org-roam-mode))
(after! oc-csl
  (setq org-cite-csl-styles-dir "~/Zotero/styles"))

Update the references database from zotero

(defun my-update-literature-bib-from-zotero ()
  "export literature.bib from zotero"
  (interactive)
  (call-process-shell-command
   "curl http://127.0.0.1:23119/better-bibtex/export/library?/1/library.bibtex > ~/notes/literature.bib"
   nil 0)
  (sleep-for 1)
)

Convert citations into new org-cite format

This function replaces the content of the current buffer.

(defun my-reformat-citations ()
  (interactive)
  (shell-command-on-region
   ; mark whole buffer:
   (point-min)
   (point-max)
   ; the command:
   "python3 /home/wo/notes/update-cite-format.py"
   ; output:
   (current-buffer)
   ; replace:
   t
   ; name of error buffer:
   "*tex2org Error Buffer*"
   ; show error buffer:
   t))

Insert links to headings

I use links by custom_id to refer to section headings. The following code lets me insert such links from a list of custom_ids currently in the buffer. (This is bound to C-c l.)

(defun matches-in-buffer (regexp &optional buffer)
  "return a list of matches of REGEXP in BUFFER or the current buffer if not given."
  (let ((matches))
    (save-match-data
      (save-excursion
        (with-current-buffer (or buffer (current-buffer))
          (save-restriction
            (widen)
            (goto-char 1)
            (while (search-forward-regexp regexp nil t 1)
              (push (match-string 1) matches)))))
      matches)))

(defun my-insert-custom-id-link ()
  "choose from a CUSTOM_ID in the file and insert link to it"""
  (interactive)
  (let* ((custom-id (completing-read
                     "Custom ID: "
                     (matches-in-buffer "^[ \t]*:CUSTOM_ID:[ \t]+\\(\\S-+\\)[ \t]*$"))))
    (when custom-id
      (org-insert-link nil (concat "#" custom-id) custom-id))))

Add log entries to current org project

This adds a ‘Log’ section to the bottom of the current org file, and inserts a new entry with today’s date.

When working on a larger project, either a paper or a software project, I like to keep a log of what I’ve done, so that, for example, I can skim the log to resume the project after a break, or so that I can remember why I made a certain choice.

(defun insert-log-entry ()
  "Inserts a log entry with today's date at the end of the current org file."
  (interactive)
  (save-excursion
    (goto-char (point-max))
    (unless (org-at-heading-p)
      (org-return))
    (if (re-search-backward "^\\* Log\\b" nil t)
        (progn
          (goto-char (point-max))
          (unless (org-at-heading-p)
            (org-return))
          (insert "** " (format-time-string "[%Y-%m-%d %a]") "\n"))
      (progn
        (insert "* Log\n** " (format-time-string "[%Y-%m-%d %a]") "\n"))))
  (goto-char (point-max))
  (evil-insert-state)
)

(map! :leader
      :desc "Insert Log Entry"
      "i l" #'insert-log-entry)

Functions to submit and update blog posts

I write my blog posts as org-roam notes. This function converts a note to HTML and submits it to my server. If the relevant post already exists, it updates it.

(defun my-post-to-server ()
  (interactive)
  (save-buffer)
  (shell-command
   (format "python3 /home/wo/notes/blog/post_to_server.py %s"
           (shell-quote-argument (buffer-file-name))))
  (revert-buffer t t t)
)

This function only updates the tags associated with the current post.

(defun my-update-tags-on-server ()
  (interactive)
  (save-buffer)
  (shell-command
   (format "python3 /home/wo/notes/blog/update_tags_on_server.py %s"
           (shell-quote-argument (buffer-file-name))))
  (revert-buffer t t t)
)

A function to display org notes with WebPPL blocks in the browser

(defun my-webpplify ()
  (interactive)
  (save-buffer)
  (shell-command
   (format "python3 /home/wo/programming/webpplview/org2html.py %s"
           (shell-quote-argument (buffer-file-name))))
  (revert-buffer t t t)
  (browse-url-firefox "file:///home/wo/programming/webpplview/index.html")
)

Functions to convert org notes into LaTex, PDF, or Word format

Emacs has built-in functions for exporting org documents as LaTeX or pdf. But customising this process is cumbersome. I need a lot of extra preprocessing and postprocessing to make the PDFs come out as I want, so I’ve written a python script that does the conversions (with the help of pandoc).

(defun my-org2latex ()
  (interactive)
  (save-buffer)
  (let ((output-dir (concat (file-name-directory (buffer-file-name))
                            (file-name-base (buffer-file-name)))))
    (async-shell-command
     (format "org2pdf --latex --template ~/notes/papers/.article-template.tex %s %s"
             (shell-quote-argument (buffer-file-name))
             (shell-quote-argument output-dir)))
    (revert-buffer t t t)
  )
)

(defun my-org2pdf ()
        (interactive)
        (save-buffer)
        (let ((vertico-sort-function nil))  ;; Disable Vertico sorting
        (let* ((template-type (completing-read "Choose template: "
                                                '("article (single-run)" "article" "note" "handout")
                                                nil t nil nil "article (single-run)"))
                (template-file (cond
                                ((string-equal template-type "note")
                                (expand-file-name "~/notes/papers/template-note.tex"))
                                ((string-equal template-type "handout")
                                (expand-file-name "~/notes/papers/template-handout.tex"))
                                (t
                                (expand-file-name "~/notes/papers/template-article.tex"))))
                (single-run (not (string-equal template-type "article")))
                (output-dir (concat (file-name-directory (buffer-file-name))
                                (file-name-base (buffer-file-name))))
                (command (format "org2pdf --template %s %s %s%s"
                                (shell-quote-argument template-file)
                                (shell-quote-argument (buffer-file-name))
                                (shell-quote-argument output-dir)
                                (if single-run " --single-run" ""))))
        (async-shell-command command)
        (revert-buffer t t t))))

(defun my-org2docx ()
  (interactive)
  (save-buffer)
  (let ((output-dir (concat (file-name-directory (buffer-file-name))
                            (file-name-base (buffer-file-name)))))
    (async-shell-command
     (format "org2docx %s %s"
             (shell-quote-argument (buffer-file-name))
             (shell-quote-argument output-dir)))
    (revert-buffer t t t)
  )
)
(defun my-org2html ()
  (interactive)
  (save-buffer)
  (async-shell-command
   (format "org2html %s"
           (shell-quote-argument (buffer-file-name))))
  (revert-buffer t t t)
)

Zettel (org-roam)

Basic config

(setq org-roam-v2-ack t)
(setq org-roam-directory (file-truename "/home/wo/notes/"))
(after! org-roam
  (add-hook 'after-init-hook 'org-roam-mode)
)
;(use-package! org-roam-bibtex
;  :after org-roam
;  :load-path "~/notes/literature.bib"
;  :hook (org-roam-mode . org-roam-bibtex-mode)
;  :config
;  (require 'org-ref)
;)

Use timestamps as ids

(setq org-id-method 'ts)
; don't include nanoseconds in the timestamp:
(setq org-id-ts-format "%Y%m%dT%H%M%S")

Note templates

(after! org-roam

  ;; (setq orb-preformat-keywords
  ;;       '("citekey" "title" "year" "author-or-editor" "file")
  ;;       orb-process-file-keyword t
  ;;       orb-file-field-extensions '("pdf"))

  (setq org-roam-capture-templates
        (list
         '("n" "default note" plain "%?"
           :if-new (file+head "%<%Y%m%d>-${slug}.org"
                    "#+TITLE: ${title}\n\n")
           :unnarrowed t)
         '("b" "blog post" plain "%?"
           :if-new (file+head "blog/%<%Y%m%d>-${slug}.org"
                    "#+TITLE: ${title}\n\n")
           :unnarrowed t)
         '("p" "new paper" plain "%?"
           :if-new (file+head "papers/%<%Y>-${slug}.org"
                    "#+TITLE: ${title}\n\n")
           :unnarrowed t)
         '("l" "literature note" plain "%?"
           :if-new (file+head "literature/${citekey}.org"
                    "#+TITLE: ${author-or-editor} ${year} ${title}\n")
           :unnarrowed t)
        )
  )
)

Tags

Emulate subdirectories-as-tags behaviour from v1:

(cl-defmethod org-roam-node-directories ((node org-roam-node))
  (if-let ((dirs (file-name-directory (file-relative-name (org-roam-node-file node) org-roam-directory))))
      (format "(%s)" (string-join (f-split dirs) "/"))
    ""))

(setq org-roam-node-display-template "${directories:10} ${title:*} ${tags:10}")

Search

(defun my-org-roam-search ()
 "Search org-roam directory using consult-ripgrep. With live-preview."
 (interactive)
 (let ((consult-ripgrep-command "rg --null --ignore-case --type org --line-buffered --color=always --max-columns=500 --no-heading --line-number . -e ARG OPTS"))
   (consult-ripgrep org-roam-directory)))

A function to change the note title

This updates the buffer name, filename, and links. (From the org-roam discourse group.)

(defun my-org-roam-change-title ()
  "Modify title of org-roam current node and update all backlinks in roam database."
  (interactive)
  (unless (org-roam-buffer-p) (error "Not in an org-roam buffer."))
  (save-some-buffers t)
  (let* ((old-title (org-roam-get-keyword "title"))
         (ID (org-entry-get (point) "ID"))
         (new-title (read-string "Enter new title: " old-title)))
    (org-roam-set-keyword "title" new-title)
    (save-buffer)
    (let* ((new-slug (org-roam-node-slug (org-roam-node-at-point)))
           (new-file-name (replace-regexp-in-string "-.*\\.org" (format "-%s.org" new-slug) (buffer-file-name)))
           (new-buffer-name (file-name-nondirectory new-file-name)))
      (rename-buffer new-buffer-name)
      (rename-file (buffer-file-name) new-file-name 1)
      (set-visited-file-name new-file-name))
    (save-buffer)
    ;; Rename backlinks in the rest of the Org-roam database.
    (let* ((search (format "[[id:%s][%s]]" ID old-title))
           (replace (format "[[id:%s][%s]]" ID new-title))
           (rg-command (format "rg -t org -lF %s ~/Org/roam/" search))
           (file-list (split-string (shell-command-to-string rg-command))))
      (dolist (file file-list)
        (let ((file-open (get-file-buffer file)))
          (find-file file)
          (beginning-of-buffer)
          (while (search-forward search nil t)
            (replace-match replace))
          (save-buffer)
          (unless file-open
            (kill-buffer)))))))

A function to convert LaTeX notes to org format

This function replaces the content of the current buffer.

(defun my-tex2org ()
  (interactive)
  (shell-command-on-region
   ; mark whole buffer:
   (point-min)
   (point-max)
   ; the command:
   "python3 /home/wo/notes/tex2org.py"
   ; output:
   (current-buffer)
   ; replace:
   t
   ; name of error buffer:
   "*tex2org Error Buffer*"
   ; show error buffer:
   t))

Key bindings

Close other windows with SPC w 1

(map!
 :map evil-window-map
  :desc "close other windows"  "1" 'delete-other-windows
  )

Insert inactive timestamps in edit mode

I often insert inactive timestamps to document when an event/conversation took place, and I don’t want to enter normal mode and press SPC m d T each time.

(map! :after org
      :map org-mode-map
      "C-c ," nil
)
(map!
   :desc "insert inactive timestamp" "C-c ," #'org-time-stamp-inactive
)

Restore some standard Meta-X commands

(map! :map org-mode-map
      "M-y" #'yank-pop
      "M-q" #'org-fill-paragraph
      )

Call org-capture

I use org-capture all the time to enter todo items or update logbook.org.

(map!
 :leader
 :desc "org-capture" "x" #'org-capture
 )

General org-roam functions

(after! org-roam
  (map! :leader
        :prefix "n"
        :desc "org-roam-buffer-toggle" "r" #'org-roam-buffer-toggle
        :desc "org-roam-node-insert" "i" #'org-roam-node-insert
        :desc "org-roam-node-find" "f" #'org-roam-node-find
        :desc "org-roam-show-graph" "g" #'org-roam-show-graph
        :desc "org-roam-capture" "c" #'org-roam-capture
        :desc "my-org-roam-search" "d" #'my-org-roam-search
        )
  )

Open/create org-roam literature note

(map!
 :leader
 :desc "open org note for literature item" "n p" #'citar-open-notes
 )

Add and remove org-roam tags

(map!
 :leader
 :prefix "n"
 :desc "add org-roam tag" "t" #'org-roam-tag-add
 :desc "remove org-roam tag" "T" #'org-roam-tag-remove
)

Insert yas snippet in edit mode

(map! :desc "insert snippet" "C-s" #'yas-insert-snippet)

Insert citation in edit mode

(map!
 :desc "insert citation" "C-c c" #'citar-insert-citation
 )

Insert link to another org-roam note in edit mode

I don’t want to enter normal mode just to insert a link to another note:

(defun my-insert-link-to-note ()
  "insert link to org node and prompt for link text"
  (interactive)
  (org-roam-node-insert)
  (call-interactively #'org-insert-link)
  )
(after! org-roam
  (map!
   :desc "insert link to node" "C-c i" #'my-insert-link-to-note
   )
)

Insert link to heading in edit mode

(map!
 :desc "link to heading" "C-c l" #'my-insert-custom-id-link
 )

Insert footnote in edit mode

(map!
 :desc "footnote action" "C-c f" #'org-footnote-action
 )

Delete footnote

(defun my-access-footnote-menu ()
  (interactive)
  (org-footnote-action t)
  )

Send and fetch mail

Mu4e’s built-in sync command takes to long, blocking emacs.

(defun my-mail-fetch ()
  (interactive)
  (save-buffer)
  (call-process-shell-command "/usr/local/bin/mbsync --pull -a&" nil 0)
)
(defun my-mail-send
  (interactive) ()
  (save-buffer)
  (call-process-shell-command "/usr/local/bin/mbsync --push -a" nil 0)
)
(map! :leader
      :prefix "m"
      :desc "fetch mail" "y" #'my-mail-fetch
      :desc "send mail" "z" #'my-mail-send
)

BibTeX

(setq reftex-default-bibliography '("/home/wo/notes/literature.bib"))

Entry format in bibtex files:

(setq bibtex-align-at-equal-sign t ; fields aligned at equal sign
      bibtex-autokey-name-year-separator ""
      bibtex-autokey-year-title-separator ""
      bibtex-autokey-titleword-first-ignore '("the" "a" "if" "and" "an")
      bibtex-autokey-year-length 2
      bibtex-autokey-titlewords 1
      bibtex-autokey-titlewords-stretch 1
      bibtex-autokey-titleword-length 20
      ; additional default fields:
      ;bibtex-user-optional-fields '("summary", "comments")
      ; reformat/realign entry on C-c C-c:
      bibtex-entry-format t
      )

LaTeX

Use XeLaTeX

(after! latex
      (add-to-list 'TeX-command-list '("XeLaTeX" "%`xelatex --synctex=1%(mode)%' %t" TeX-run-TeX nil t)))

(setq org-latex-pdf-process
      '("latexmk -f -pdf -xelatex -shell-escape -interaction=nonstopmode -output-directory=%o %f"))

Center LaTeX documents

(add-hook 'LaTeX-mode-hook #'olivetti-mode)

Programming

Python

(setq python-fill-docstring-style 'symmetric)
(setq python-shell-interpreter "python3")

HTML

Don’t insert template into new html pages:

(set-file-template! "\\.html$" nil)

Javascript

Activate javascript mode for webppl files:

(add-to-list 'auto-mode-alist '("\\.wppl\\'" . js2-mode))

Copilot

Accept code completion from copilot; fallback to company:

(use-package! copilot
  :hook (prog-mode . copilot-mode)
  ;; :hook (org-mode . copilot-mode)
  :bind (:map copilot-completion-map
              ("<tab>" . 'copilot-accept-completion)
              ("TAB" . 'copilot-accept-completion)
              ("C-TAB" . 'copilot-accept-completion-by-word)
              ("C-<tab>" . 'copilot-accept-completion-by-word)
              ("C-p" . 'copilot-previous-completion)
              ("C-n" . 'copilot-next-completion)
             ))
  :config
  (setq copilot-max-char -1)
  (setq copilot-indent-offset-warning-disable t)

; Need Node.js v18+.
(setq copilot-node-executable "/home/wo/.nvm/versions/node/v18.20.2/bin/node")

(map!
:leader
:desc "toggle copilot" "t p" #'copilot-mode
)

Prevent copilot warnings in org mode:

(after! copilot
  (add-to-list
   'copilot-indentation-alist
   '(org-mode 4))
)

Other functionality

Update course pages on my homepage

(defun my-update-course-pages()
  "update course pages on wolfgangschwarz.net"
  (interactive)
  (shell-command
   "cd /home/wo/programming/wolfgangschwarz.net && python3 createpages.py -c")
)

GPT

I use the gptel package.

(load-file "~/.doom.d/gptel-api-key.el")
(setq gptel-default-mode 'org-mode)

A function to start a new session:

(defun my-gptel-new ()
  (interactive)
  (delete-region (point-min) (point-max))
  (insert "*** "))

Email

I use mu4e for email. Mails are synchronised with mbsync into a local ~/.mail folder. The mbsync configuration resides in ~/.mbsyncrc.

General mu4e settings

I manually installed a newer version of mu/mu4e manually, which doom doesn’t find without assistance:

(add-to-list 'load-path "/usr/local/share/emacs/site-lisp/mu4e")

General settings:

(setq
    ;; mu4e-index-cleanup nil ; speed up indexing
    ;; mu4e-index-lazy-check t ; speed up indexing
    mu4e-update-interval nil ; refresh index every n seconds
    mu4e-headers-show-threads t ; Keep non-threaded by default 'P' to change
    mu4e-view-show-images t ; show images inline
    mu4e-compose-format-flowed t) ; no hard linebreaks in composed emails

Mail accounts

Now let’s configure my mail accounts. First my Uni Edinburgh account:

(set-email-account! "UoE"
  '((mu4e-sent-folder       . "/UoE/Sent Mail")
    (mu4e-drafts-folder     . "/UoE/Drafts")
    (mu4e-trash-folder      . "/UoE/Trash")
    (mu4e-refile-folder     . "/UoE/Archive")
    (smtpmail-smtp-server   . "outlook.office365.com")
    (smtpmail-smtp-service  . 587)
    (smtpmail-smtp-user     . "wschwarz@ed.ac.uk")
    ;; (mu4e-compose-signature . "\nBest,\nWolfgang")
    )
  t)

Next my Gmail account:

(set-email-account! "wo@umsu"
  '((mu4e-sent-folder       . "/wo@umsu/Sent Mail")
    (mu4e-drafts-folder     . "/wo@umsu/Drafts")
    (mu4e-trash-folder      . "/wo@umsu/Bin")
    (mu4e-refile-folder     . "/wo@umsu/All Mail")
    (smtpmail-default-smtp-server . "smtp.gmail.com")
    (smtpmail-smtp-server   . "smtp.gmail.com")
    (smtpmail-smtp-service  . 587)
    (smtpmail-debug-info    . t)
    (smtpmail-debug-verbose . t)
    (smtpmail-smtp-user     . "wo@umsu.de")
    ;; (mu4e-compose-signature . "\nBest,\nWolfgang"))
    )
  t)
;; (auth-source-pass-enable)
;; (setq auth-sources '(password-store))
;; (setq auth-source-debug t)
;; (setq auth-source-do-cache nil)

I need to tell doom that this a gmail account so that deleting, archiving, etc. works properly:

(setq +mu4e-gmail-accounts '(("wo@umsu.de" . "/wo@umsu")))

mu4e bookmarks

A bookmark for the combined inbox of all accounts:

(after! mu4e
    (add-to-list 'mu4e-bookmarks '("m:/wo@umsu/Inbox or m:/UoE/Inbox" "Inbox" ?i)))

Improve display of html mails

Improve display of html mails in dark mode (from reddit):

(after! mu4e
    (setq mu4e-html2text-command 'mu4e-shr2text)
    (setq shr-color-visible-luminance-min 60)
    (setq shr-color-visible-distance-min 5)
    (setq shr-use-colors nil)
    (advice-add #'shr-colorize-region :around (defun shr-no-colourise-region (&rest ignore))))

‘ab’ opens mail in firefox

(setq
    browse-url-browser-function 'browse-url-generic
    browse-url-generic-program "firefox")
(after! mu4e
    (add-to-list 'mu4e-view-actions '("browser" . mu4e-action-view-in-browser)))

Only auto-complete addresses seen in the last year

(setq mu4e-compose-complete-only-after (format-time-string
                                        "%Y-%m-%d"
                                        (time-subtract (current-time) (days-to-time 350))))

About

My doom emacs config

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published