Setting Up Doom Emacs Emacs Configuration File
- Resources
- https://github.com/elken/doom
Meta
Debugging Configuration Issues
(setq debug-counter 0) (setq debug-counter (+ debug-counter 1))
User details
Place your private configuration here! Remember, you do not need to run 'doom sync' after modifying this file!
Some functionality uses this to identify you, e.g. GPG configuration, email clients, file templates and snippets. It is optional.
(setq user-full-name "Adithya Bhat" user-mail-address "dth.bht@gmail.com")
Key binding to toggle debug on error
(map! :leader :desc "Toggle debug on error" "t d" #'toggle-debug-on-error)
Doom Specific Configuration
Visuals
Theme
;; In all of the following, WEIGHT is a symbol such as `semibold', ;; `light', `bold', or anything mentioned in `modus-themes-weights'. (setq modus-themes-italic-constructs t modus-themes-bold-constructs nil modus-themes-mixed-fonts t modus-themes-variable-pitch-ui nil ;; Options for `modus-themes-prompts' are either nil (the ;; default), or a list of properties that may include any of those ;; symbols: `italic', `WEIGHT' modus-themes-prompts '(italic bold) ;; The `modus-themes-completions' is an alist that reads two ;; keys: `matches', `selection'. Each accepts a nil value (or ;; empty list) or a list of properties that can include any of ;; the following (for WEIGHT read further below): ;; ;; `matches' :: `underline', `italic', `WEIGHT' ;; `selection' :: `underline', `italic', `WEIGHT' modus-themes-completions '((matches . (extrabold)) (selection . (semibold italic text-also))) modus-themes-org-blocks 'tinted-background ; {nil,'gray-background,'tinted-background} ;; The `modus-themes-headings' is an alist: read the manual's ;; node about it or its doc string. Basically, it supports ;; per-level configurations for the optional use of ;; `variable-pitch' typography, a height value as a multiple of ;; the base font size (e.g. 1.5), and a `WEIGHT'. modus-themes-headings '((0 . (background overline rainbow 1.3)) (1 . (background overline rainbow 1.4)) (2 . (background overline rainbow 1.2)) (3 . (overline 1.1)) (t . (1.0))) modus-themes-org-agenda '((header-block . (semibold 1.3)) (header-date . (1.2)) (event . (accented italic varied)) (scheduled . (rainbow))) modus-themes-hl-line '(underline accented) ;; Options for `modus-themes-paren-match' are either nil (the ;; default), or a list of properties that may include any of those ;; symbols: `bold', `intense', `underline' modus-themes-paren-match '(bold intense) modus-themes-mixed-fonts t modus-themes-mode-line '(accented 3d)) ;; Remember that more (MUCH MORE) can be done with overrides, which we ;; document extensively in this manual. (setq doom-theme 'modus-vivendi) ;; (use-package nano-theme ;; :config ;; (setq doom-theme 'nano-light) ; or 'nano-dark ;; ;; Optional: use nano-mode for the full experience ;; (nano-mode) ;; )
Font
Doom exposes five (optional) variables for controlling fonts in Doom:
- `doom-font' – the primary font to use
- `doom-variable-pitch-font' – a non-monospace font (where applicable)
- `doom-big-font' – used for `doom-big-font-mode'; use this for presentations or streaming.
- `doom-symbol-font' – for unicode glyphs
- `doom-serif-font' – for the `fixed-pitch-serif' face
(setq doom-font (font-spec :family "Iosevka Nerd Font Mono" :size 15) doom-big-font (font-spec :family "Iosevka Nerd Font Mono" :size 24) doom-variable-pitch-font (font-spec :family "Iosevka Aile" :size 15) doom-symbol-font (font-spec :family "Iosevka Nerd Font")) ;; (setq ;; doom-font (font-spec :family "Iosevka Nerd Font Mono" :size 15) ;; doom-big-font (font-spec :family "Iosevka Nerd Font Mono" :size 24) ;; doom-variable-pitch-font (font-spec :family "Inter" :size 15) ;; doom-symbol-font (font-spec :family "Symbols Nerd Font Mono")) ;; (setq ;; doom-font (font-spec :family "JetBrainsMono Nerd Font Mono" :size 14) ;; doom-big-font (font-spec :family "JetBrainsMono Nerd Font Mono" :size 22) ;; doom-variable-pitch-font (font-spec :family "Inter" :size 15) ;; doom-symbol-font (font-spec :family "Symbols Nerd Font Mono")) (setq-default line-spacing 0.15)
Modeline
(setopt doom-modeline-height 30)
Splash screen
(setq fancy-splash-image (file-name-concat doom-private-dir "splash.png"))
Line number
This determines the style of line numbers in effect. If set to `nil', it disables all the line numbers. For relative line numbers, set this to `relative'.
(setq display-line-numbers-type t)
Keybinding to toggle day/night theme for modus themes
(map! :leader :desc "Toggle Modus Themes" "t T" #'modus-themes-toggle)
Window maximization key binding
(map! :leader :desc "maximize" "w m" #'doom/window-maximize-buffer)
Toggle Visual Line Column Mode
(map! :leader :desc "Toggle visual line fill column mode" "t y" #'visual-line-fill-column-mode)
Tab Mode
I like to quickly switch between the last tab back and forth
(map! :map tab-prefix-map "z" #'tab-recent)
Popup buffer on the right
(defun my/popup-buffer-right () "Display current buffer as a popup on the right side." (interactive) (let ((buffer (current-buffer))) (bury-buffer) (when-let* ((window (display-buffer-in-side-window buffer '((side . right) (window-width . 0.3) (window-parameters (quit . t) (select . t) (ttl . 5)))))) (+popup--init window) (select-window window))))
Resize window to percentage
(defun my/resize-window () "Resize windows when exactly 2 windows are present. Asks for a percentage and resizes the left/top window to occupy that percentage." (interactive) (if (/= (count-windows) 2) (message "wrong number of windows, need only 2") (let* ((percentage (read-number "Percentage for left/top window: " 50)) (root (frame-root-window)) (horizontal (window-left-child root)) (first-win (if horizontal (window-left-child root) (window-top-child root))) (total-size (if horizontal (window-total-width root) (window-total-height root))) (target-size (round (* total-size (/ percentage 100.0)))) (current-size (if horizontal (window-total-width first-win) (window-total-height first-win))) (delta (- target-size current-size))) (window-resize first-win delta horizontal))))
Fix org mode heading fontification problem
(defun my/fix-org-verbatim () (interactive) (set-face-attribute 'org-verbatim nil :inherit nil :foreground (face-attribute 'modus-themes-prose-verbatim :foreground))) (with-eval-after-load 'org (with-eval-after-load 'modus-themes ;; Copy the foreground/background/decoration from modus-themes-markup-verbatim ;; but use the current heading's font and size (my/fix-org-verbatim)))
Org mode completed checkbox face
;; 1. Define the custom face (defface org-checkbox-done-text '((t nil)) "Face for the text following a checked org-mode checkbox.") ;; 2. Define the function (defun my-org-checkbox-done-face () (when (fboundp 'modus-themes-get-color-value) (let ((fg-dim (modus-themes-get-color-value 'fg-dim))) (custom-set-faces `(org-checkbox-done-text ((t :foreground ,fg-dim :strike-through t))))))) ;; 3. Apply after everything is loaded, and on theme switch (add-hook 'doom-after-init-hook #'my-org-checkbox-done-face) (add-hook 'modus-themes-after-load-theme-hook #'my-org-checkbox-done-face) ;; 4. Apply the face via font-lock (font-lock-add-keywords 'org-mode '(("^[ \t]*\\(?:[-+*]\\|[0-9]+[.)]\\) \\[X\\] \\(.*\\)$" 1 'org-checkbox-done-text prepend) ("^[ \t]*\\(?:[-+*]\\|[0-9]+[.)]\\) \\(.*\\) \\[\\([0-9]+\\)/\\2\\]$" 1 'org-checkbox-done-text prepend)) 'append)
Fix Checklist symbol rendering
(with-eval-after-load 'org-modern (setq org-modern-checkbox '((?X . " ✓ ") (?- . " ◐ ") (?\s . " ☐ "))))
Editor
Default directory
(setq default-directory "~")
Disable mouse
Sometimes the mouse gets in the way.
Especially in org-agenda.
(setq mouse-avoidance-mode 'banish)
Full size window on startup
(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) (add-to-list 'default-frame-alist '(ns-appearance . dark))
Auto-revert files that changed on the disk
(global-auto-revert-mode t)
Spelling
Dictionary
Resources
(setq ispell-dictionary "english")
Disable spelling suggestions from Company
;; (setq debug-on-error t) (set-company-backend! 'text-mode '(company-dabbrev company-yasnippet :separate))
Navigate in page
(map! "C-." #'avy-goto-char-timer)
Quick-access directories
(with-eval-after-load 'dirvish (setopt dirvish-quick-access-entries `(("h" "~/" "Home") ("e" ,user-emacs-directory "Emacs user directory") ("g" "~/Github/" "Github") ("d" "~/Downloads/" "Downloads") ("t" "~/.local/share/Trash/files/" "Trash"))))
Lang
Org Mode
Root Org Directory
Point to an iCloud directory.
(setq org-directory "~/iCloud/Research Notes/" org-startup-numerated t) (add-to-list 'safe-local-variable-directories org-directory)
Beautify Org Mode
(with-eval-after-load 'org (setq org-ellipsis " ⤷" org-hide-emphasis-markers t)) (add-hook! 'org-mode-hook #'variable-pitch-mode)
Setup org-modern
Sweet font rendering for org buffers.
(use-package org-modern :hook (org-mode . global-org-modern-mode))
Add ACM Latex classes to Org Export
Add ACM articles to the list of org-latex-classes.
(with-eval-after-load 'ox-latex :config (add-to-list 'org-latex-classes '("acmart" "\\documentclass{acmart}" ("\\section{%s}" . "\\section*{%s}") ("\\subsection{%s}" . "\\subsection*{%s}") ("\\subsubsection{%s}" . "\\subsubsection*{%s}"))))
Do not inherit statistics from tree
(with-eval-after-load 'org :config (setq org-hierarchical-todo-statistics nil))
Reset sub-tasks
To reset sub-tasks on completion. I have repeating projects like laundry, cleaning my car, etc. which on completion needs to reset with all the sub-tasks.
(defun org-reset-subtask-state-subtree () "Reset all subtasks in an entry subtree." (interactive "*") (if (org-before-first-heading-p) (error "Not inside a tree") (save-excursion (save-restriction (org-narrow-to-subtree) (org-fold-show-subtree) (goto-char (point-min)) (beginning-of-line 2) (narrow-to-region (point) (point-max)) (org-map-entries '(when (member (org-get-todo-state) org-done-keywords) (if (org-entry-get (point) "REPEAT_TO_STATE") (org-todo (org-entry-get (point) "REPEAT_TO_STATE")) (org-todo (car org-todo-keywords))))))))) (defun org-reset-subtask-state-maybe () "Reset all subtasks in an entry if the `RESET_SUBTASKS' property is set" (interactive "*") (if (org-entry-get (point) "RESET_SUBTASKS") (org-reset-subtask-state-subtree))) (defun org-subtask-reset () (when (member org-state org-done-keywords) ;; org-state dynamically bound in org.el/org-todo (org-reset-subtask-state-maybe) (org-update-statistics-cookies t))) (with-eval-after-load 'org :custom (setq org-default-properties (cons "RESET_SUBTASKS" org-default-properties)) (add-hook 'org-after-todo-state-change-hook 'org-subtask-reset) (provide 'org-subtask-reset))
Org Modules
Get
org-checklistso that we can reset check boxes in a to-do item.;; https://orgmode.org/worg/org-contrib/org-checklist.html (with-eval-after-load 'org (require 'org-checklist))
Get
org-ednafor advanced TODO dependencies (replaces org-depend);; https://www.nongnu.org/org-edna-el/ (use-package org-edna :after org :config (org-edna-mode 1))
Capture templates
(setq +org-capture-todo-file "todo.org" +org-capture-notes-file "notes.org") (defun my/org-capture-to-chosen-file () "This function finds the `project notes` folder in `denote-directory` and " (let ((chosen-file (completing-read "Choose a note file: " (directory-files (file-name-concat denote-directory "project notes") t "\.org$")))) (when (and chosen-file (not (string= chosen-file ""))) (set-buffer (org-capture-target-buffer chosen-file)) (with-current-buffer (find-file-noselect chosen-file) (goto-char (point-max)) (save-buffer))))) (with-eval-after-load 'org (setq org-capture-templates '(("a" "Packages" entry (file+headline +org-capture-todo-file "Packages") ;; personal.org "* WAIT 📦 %?\n SCHEDULED: <%<%Y-%m-%d %a>>\nCreated: %u\n%a\n%i") ("c" "CV Todo" entry (file+headline "~/Github/personal-website/CV.org" "02-CV Tasks") "* TODO 📝 %?\nCreated: %u\n") ("e" "Emacs Todo" entry (file+headline "personal.org" "💻 Emacs") "* SOMEDAY 👨🏻💻 %?\nCreated: %u\n") ("t" "Task" entry (file+headline +org-capture-todo-file "00-Inbox") ;; personal.org "* TODO 📋 %?\nSCHEDULED: <%<%Y-%m-%d %a>>\nCreated: %u\n%a\n%i" :prepend t) ("x" "Project Task" entry (function my/org-capture-to-chosen-file) "* TODO 📋 %?\nSCHEDULED: <%<%Y-%m-%d %a>>\nCreated: %u\n%a\n%i" :empty-lines 1) ("n" "Capture Notes") ("nd" "Dist. Sys. Protocol" plain (file denote-last-path) (function (lambda () (let ((denote-use-directory (expand-file-name "protocols" denote-directory)) (denote-prompts '(title keywords))) (denote-org-capture))))) ("nf" "Fleeting notes" plain (file denote-last-path) (function (lambda () (let ((denote-use-directory (expand-file-name "temporary notes" denote-directory)) (denote-prompts '(title))) (denote-org-capture))))) ("ng" "General notes" plain (file denote-last-path) (function (lambda () (let ((denote-use-directory (expand-file-name "general notes" denote-directory)) (denote-prompts '(title keywords))) (denote-org-capture))))) ("nm" "Meta notes" plain (file denote-last-path) (function (lambda () (let ((denote-use-directory (expand-file-name "meta notes" denote-directory)) (denote-prompts '(title keywords))) (denote-org-capture))))) ("nn" "New project" plain (file denote-last-path) (function (lambda () (let ((denote-use-directory (expand-file-name "project notes" denote-directory)) (denote-prompts '(title keywords))) (denote-org-capture))))) ("np" "Project Notes" entry (function my/org-capture-to-chosen-file) "* %U %?" :empty-lines 1) ("p" "Templates for projects") ("pt" "Project-local todo" entry (file+headline +org-capture-project-todo-file "Inbox") "* TODO %?\n%a\nCreated: %u\n%i\n" :prepend t) ("pn" "Project-local notes" entry (file+headline +org-capture-project-notes-file "Notes") "* %?\n%a\n%i\nCreated: %u" :prepend t) ("pc" "Project-local changelog" entry (file+headline +org-capture-project-changelog-file "Unreleased") "* %U %?\n%a\n%i" :prepend t))))
Add action to mark as done at a custom time in the past
(defun my/org-todo-with-time (arg) "Mark task as DONE using specified time as the completion timestamp. ARG is passed to org-todo functions." (interactive "P") (let* ((time-string (org-read-date t t)) ;; Interactive prompt for date+time (time (org-time-string-to-time time-string)) (org-use-last-clock-out-time-as-effective-time nil) (org-use-effective-time t) (org-extend-today-until 0)) (cl-letf (((symbol-function 'org-current-time) (lambda () time)) ((symbol-function 'current-time) ;; This affects org-today (lambda () time))) (if (eq major-mode 'org-agenda-mode) (org-agenda-todo arg) (org-todo arg))))) ;; Add it to embark maps for both org and org-agenda modes (with-eval-after-load 'embark (map! :map embark-org-heading-map "y" #'my/org-todo-with-time))
Auto-complete Dollar Sign in Org Mode
(with-eval-after-load 'smartparens (sp-local-pair 'org-mode "$" "$"))
Org Attachment flat storage
(with-eval-after-load 'org (setq org-attach-preferred-new-method 'dir org-attach-dir-relative t org-attach-store-link-p 'file)) (setq debug-counter (+ debug-counter 1))
Capture book quotes
(defvar my/book-quote-org-file (file-name-concat org-directory "200-notes" "general notes" "20250219T000809--book-notes__resources_tracking.org")) ;; Function to find or create a heading for the current book (defun my/find-or-create-book-heading (book-title) "Find or create heading with BOOK-TITLE." (goto-char (point-min)) (unless (re-search-forward (format "^\\* %s$" (regexp-quote book-title)) nil t) (goto-char (point-max)) (insert (format "\n* %s" book-title))) (end-of-line)) ;; Command to capture quotes from nov.el (defun my/nov-capture-quote () "Capture a quote from the current nov.el book." (interactive) (if (not (eq major-mode 'nov-mode)) (message "Not in nov-mode") (let ((book-title (nov-book-title)) (selected-text (if (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end)) ""))) ;; Create a dynamic template with the current book (let ((org-capture-templates `(("b" "Book Quote" plain (file+function my/book-quote-org-file (lambda () (my/find-or-create-book-heading ,book-title))) "- %i\n %U" :empty-lines-after 1)))) (org-capture nil "b"))))) (map! :map nov-mode-map :localleader :desc "Capture book note" "n" #'my/nov-capture-quote)
Set Key Binding for imenu
(with-eval-after-load 'org (with-eval-after-load 'consult (map! :map org-mode-map "C-'" #'consult-imenu)))
Increase depth for headings search
(defun +my/org-notes-headlines () "Jump to an Org headline in `org-agenda-files'." (interactive) (let ((full-agenda-files (mapcar (lambda (file) (if (file-name-absolute-p file) file (expand-file-name file org-directory))) org-agenda-files))) (doom-completing-read-org-headings "Jump to org headline: " full-agenda-files :depth 100 :include-files t))) ;; Create a new mapping (map! :leader :desc "Search headings" "n h" #'+my/org-notes-headlines) ;; Unmap old keybinding (map! :leader "n S" nil)
Setup TODO dependency trigger
Set up a dependency where completing the current task (Y) triggers another task (X) to become TODO and scheduled with the completion date.
(defun my/setup-todo-dependency () "Set up a TODO dependency where completing the current task triggers another task. When the current heading (task Y) is completed, task X will become TODO and scheduled with the completion date of Y. Uses org-edna syntax." (interactive) ;; Step 1: Check if in org-mode (unless (derived-mode-p 'org-mode) (user-error "Not in org mode")) ;; Step 2: Get current heading (task Y) (when (org-before-first-heading-p) (user-error "Not on an org heading")) (save-excursion (unless (org-at-heading-p) (org-back-to-heading t)) (let ((task-y-heading (org-get-heading t t t t))) (unless task-y-heading (user-error "No heading at point")) ;; Step 3: Select heading from org-agenda-files (task X) (let* ((full-agenda-files (mapcar (lambda (file) (if (file-name-absolute-p file) file (expand-file-name file org-directory))) org-agenda-files)) (selection (doom-completing-read-org-headings "Select task to trigger on completion: " full-agenda-files :depth 100 :include-files t))) (unless selection (user-error "No task selected")) ;; Parse selection and go to task X (let* ((marker (get-text-property 0 'marker selection)) x-id) (unless marker (user-error "Could not find marker for selected heading")) ;; Step 4: Get or create ID for task X (with-current-buffer (marker-buffer marker) (save-excursion (goto-char (marker-position marker)) ;; Check for existing CUSTOM_ID or ID (setq x-id (or (org-entry-get nil "CUSTOM_ID") (org-entry-get nil "ID"))) (unless x-id ;; Create new CUSTOM_ID: yyyy-mm-dd-hh-mm-ss-<heading-with-hyphens> (let* ((heading (org-get-heading t t t t)) (heading-slug (replace-regexp-in-string "[^a-zA-Z0-9]+" "-" (downcase heading))) (heading-slug (replace-regexp-in-string "^-\\|-$" "" heading-slug)) (timestamp (format-time-string "%Y-%m-%d-%H-%M-%S"))) (setq x-id (concat timestamp "-" heading-slug)) (org-entry-put nil "CUSTOM_ID" x-id))) (save-buffer))) ;; Step 5: Set TRIGGER on task Y using org-edna format ;; scheduled!(".") sets scheduled date to current time when trigger fires (let* ((existing-trigger (org-entry-get nil "TRIGGER")) (new-trigger (format "ids(\"%s\") todo!(TODO) scheduled!(\".\")" x-id)) (final-trigger (if existing-trigger (concat existing-trigger " " new-trigger) new-trigger))) (org-entry-put nil "TRIGGER" final-trigger) (message "Set trigger: completing '%s' will schedule '%s' as TODO" task-y-heading (substring-no-properties selection))))))))
Capture Papers to review
(defvar my-conference-names '("Financial Cryptography" "NDSS" "IEEE S&P" "USENIX Security" "CCS" "Crypto" "Eurocrypt" "Asiacrypt" "AsiaCCS" "EuroSys" "ACNS") "List of conference names for completion.") (defun my-create-paper-review () "Create a paper review file and corresponding inbox task. This function will create a review file in the =Reviews= folder and trigger an org-capture-template to record the review task" (interactive) (let* ((year (read-string "Conference Year: ")) (conference (completing-read "Conference name: " my-conference-names)) (paper-num (read-string "Paper number: ")) ;; Build the directory path (review-dir (expand-file-name (format "Reviews/%s/%s" year conference) org-directory)) ;; Build the file path (review-file (expand-file-name (format "%s.org" paper-num) review-dir))) ;; Create directory if it doesn't exist (unless (file-exists-p review-dir) (make-directory review-dir t)) ;; Create the review file (find-file review-file) (when (= (buffer-size) 0) ; Only insert if file is new (insert (format "#+TITLE: Review - %s Paper %s\n" conference paper-num)) (insert (format "#+DATE: %s\n" (format-time-string "%Y-%m-%d"))) (insert (format "#+CONFERENCE: %s\n" conference)) (insert (format "#+PAPER: %s\n\n" paper-num)) (save-buffer)) ;; Store a link to this review file (org-store-link nil) ;; Trigger the ?t capture template ;; Create inbox task (let* ((link (org-link-make-string (concat "file:" review-file) (format "Review - %s Paper %s" conference paper-num))) (org-capture-initial (format "Review Paper %s\n - %s" paper-num link))) ;; (org-capture nil "t")) (message "Created review file and inbox task for Paper %s" paper-num)))
Org Effort Setup
(setq org-global-properties '(("Effort_ALL". "0 0:10 0:30 1:00 2:00 3:00 4:00")) org-sort-agenda-noeffort-is-high t)
Allow refiling items using embark
(with-eval-after-load 'embark (defun my/org-refile-item-to-heading () "Refile the org list item at point to a heading selected via imenu. The item is appended to the end of any existing list under that heading, or inserted as a new list if none exists. Handles nested sub-items." (interactive) (unless (org-at-item-p) (user-error "Not at an org list item")) (let* ((item-struct (org-list-struct)) (item-begin (org-list-get-item-begin)) (item-end (org-list-get-item-end-before-blank item-begin item-struct)) (item-text (buffer-substring-no-properties item-begin item-end)) (base-indent (save-excursion (goto-char item-begin) (current-indentation))) (item-content (with-temp-buffer (insert item-text) (goto-char (point-min)) (while (not (eobp)) (when (looking-at (format "^[ \t]\\{0,%d\\}" base-indent)) (replace-match "")) (forward-line 1)) (string-trim-right (buffer-string))))) (delete-region item-begin item-end) (when (and (bolp) (looking-at "^$")) (delete-char 1)) (save-excursion (consult-imenu) (org-end-of-meta-data t) (if (org-at-item-p) (progn (goto-char (org-list-get-bottom-point (org-list-struct))) (forward-line -1) (end-of-line) (insert "\n" item-content)) (unless (looking-at "^$") (insert "\n")) (insert item-content "\n"))))) (map! :map embark-org-item-map "r" #'my/org-refile-item-to-heading) ;; Tell embark to ignore the target when calling this action (cl-pushnew 'embark--ignore-target (alist-get 'my/org-refile-item-to-heading embark-target-injection-hooks)))
Org Agenda
Task management keywords
(with-eval-after-load 'org (setq org-todo-keywords '((sequence "TODO(t)" ; Need this task to be done "NEXT(n)" ; This is the next item that needs to be done for this task "|" "DONE(d)" ; Finished the task successfully "KILL(k)") ; Cancelled the task (sequence "WAIT(w)" ; Waiting for an external event to occur to process this "FOLLOWUP" ; I asked someone else to do this and I need to follow up on it "SOMEDAY" ; Tasks that I want to do someday "|" "DONE(d)") (sequence "CURRENT(c)"; A collection of active tasks "PROJ(q)" ; A collection of inactive tasks "|" "DONE(d)" "KILL(k)") (sequence "DRAFT" ; Writing Tasks "|" "DONE(d)" "KILL(k)") (sequence "CODING" "|" "DONE(d)" "KILL(k)") (sequence "READ" "READING" "|" "DONE(d)" "KILL(k)") (sequence "EVENT" "|" "NONATTENDANCE") ;; Basic Todos for Checkboxes TSWD (sequence "[ ](T)" "[-](S)" "[?](W@/!)" "|" "[X](D)")))) (setq debug-counter (+ debug-counter 1))
Org Agenda Configuration
(setq debug-counter (+ debug-counter 1)) (setq org-agenda-custom-commands '(("." . "Custom Commands") (".a" "Day Planning" ((agenda nil ((org-agenda-span 'day) (org-agenda-skip-deadline-prewarning-if-scheduled 't) (org-agenda-overriding-header "Tasks for today") (org-agenda-time-grid nil))) (agenda nil ((org-agenda-span 7) (org-deadline-warning-days 0) (org-agenda-time-grid nil) (org-agenda-deadline-leaders '("Deadline: " "(%3d d) " "(%3d d. ago) ")) (org-agenda-overriding-header "Deadlines") (org-agenda-entry-types '(:deadline)))))) (".w" "Work contexts" ((agenda nil ((org-agenda-span 'day) (org-agenda-skip-deadline-prewarning-if-scheduled 't) (org-agenda-overriding-header "Work Tasks for today")))) ((org-agenda-category-filter-preset '("+Work")))) (".b" "Weekly Review" ((todo "CURRENT" ((org-agenda-overriding-header "Current Projects"))) (todo "PROJ" ((org-agenda-overriding-header "Inactive Projects"))) (todo "DONE|PDONE|KILL|PKILL" ((org-agenda-overriding-header "Finished tasks (Archive)"))) (alltodo "" ((org-agenda-overriding-header "Inbox Tasks") (org-agenda-files '("todo.org" "mobile.org")))) (tags "+{^On.+*}" ((org-agenda-overriding-header "On Tags"))) (todo "WAIT" ((org-agenda-overriding-header "Waiting (Check for Follow-up)"))))) (".c" "Weekly Planning" ((tags "WEEK_PROJECT=\"t\"" ((org-agenda-overriding-header "Focus this week"))) (todo "CURRENT" ((org-agenda-overriding-header "Current Projects"))) (todo "READ|READING" ((org-agenda-overriding-header "Reading Projects"))) (todo "CODING" ((org-agenda-overriding-header "Coding Projects"))) (todo "DRAFT" ((org-agenda-overriding-header "Writing Projects"))))) (".d" "Open Loops" ((todo "CURRENT" ((org-agenda-overriding-header "Open Loops"))))) (".s" "Schedule" ((agenda nil ((org-agenda-span 'day) (org-agenda-files '("schedule.org")))))))) ;; A function to switch to a workspace with name "Agenda" (defun hermitsage/switch-to-agenda-workspace () "Switch to the Agenda workspace, creating it if necessary." (let ((workspace-name "Agenda")) (unless (persp-get-by-name workspace-name) (persp-add-new workspace-name)) (persp-switch workspace-name))) (defun hermitsage/org-daily-view () "Open all my open loops" (interactive) (hermitsage/switch-to-agenda-workspace) (org-agenda nil ".a")) (defun hermitsage/org-weekly-review () "Open all the tasks" (interactive) (hermitsage/switch-to-agenda-workspace) (org-agenda nil ".b")) (defun hermitsage/org-week-planning () "Open planning related tasks" (interactive) (hermitsage/switch-to-agenda-workspace) (org-agenda nil ".c")) (defun hermitsage/org-open-loops () "Open all Open Loops" (interactive) (hermitsage/switch-to-agenda-workspace) (org-agenda nil ".d")) (defun hermitsage/org-schedule () "Open all my open loops" (interactive) (hermitsage/switch-to-agenda-workspace) (org-agenda nil ".s")) (defun hermitsage/org-work-view () "Open all my work tasks" (interactive) (hermitsage/switch-to-agenda-workspace) (org-agenda nil ".w")) (map! :leader :prefix ("z" . "agenda") :desc "Daily View" "a" #'hermitsage/org-daily-view :desc "Weekly Review" "b" #'hermitsage/org-weekly-review :desc "Planning View" "c" #'hermitsage/org-week-planning :desc "Open Loops" "d" #'hermitsage/org-open-loops :desc "Schedule" "s" #'hermitsage/org-schedule :desc "Work View" "w" #'hermitsage/org-work-view :desc "Default Agenda" "e" #'org-agenda-list :desc "Export Markdown" "p" #'hermitsage/org-agenda-export-markdown) (setq debug-counter (+ debug-counter 1)) (defun my/days-until-next-sunday () "Calculate number of days from today until the Sunday ending next week. If today is Sunday, returns 7 (next Sunday). If today is Monday, returns 13 (this week + next week). If today is Saturday, returns 8 (tomorrow + full next week)." (let* ((day-of-week (string-to-number (format-time-string "%w"))) ; 0=Sun, 6=Sat (days-to-this-sunday (if (= day-of-week 0) 0 (- 7 day-of-week)))) (+ days-to-this-sunday 7))) (defun hermitsage/org-agenda-export-markdown () "Export agenda from today until the next Sunday as Markdown. The span covers from today through the end of next week." (interactive) (let* ((span (my/days-until-next-sunday)) (date-str (format-time-string "%Y-%m-%d")) (default-filename (format "agenda-%s.md" date-str)) (output-file (read-file-name "Export agenda to Markdown: " "~/" nil nil default-filename)) (org-agenda-span span) (org-agenda-start-day nil) (org-agenda-start-on-weekday nil)) (org-agenda-list) (with-current-buffer org-agenda-buffer-name (let ((content (buffer-substring-no-properties (point-min) (point-max)))) (with-temp-file output-file (insert (format "# Agenda: %s\n\n" date-str)) (insert "```\n") (insert content) (insert "```\n")))) (message "Agenda exported to %s (span: %d days)" output-file span)))
Weekly Focus Tasks
Some configuration to mark tasks to focus on for the week
(defun mark-heading-for-week () "Mark the current heading as a project to work on this week." (interactive) (org-entry-put nil "WEEK_PROJECT" "t") (message "Marked heading as a project for this week")) (defun unmark-heading-for-week () "Remove the weekly project mark from the current heading." (interactive) (org-entry-delete nil "WEEK_PROJECT") (message "Removed weekly project mark")) (defun toggle-heading-for-week () "Toggle the WEEK_PROJECT property on the current heading." (interactive) (if (org-entry-get nil "WEEK_PROJECT") (unmark-heading-for-week) (mark-heading-for-week))) ;; Create a hook to automatically unmark a project when all tasks are done (defun check-and-unmark-completed-week-project () "If all tasks in a week project are completed, unmark the project." (when (member org-state org-done-keywords) (save-excursion (when (org-up-heading-safe) (when (org-entry-get nil "WEEK_PROJECT") ;; Check if all subheadings with TODO keywords are DONE (let ((has-incomplete-tasks nil)) (org-map-entries (lambda () (when (and (org-get-todo-state) (not (member (org-get-todo-state) org-done-keywords))) (setq has-incomplete-tasks t))) nil 'tree) (unless has-incomplete-tasks (unmark-heading-for-week)))))))) ;; Add this to the todo state change hook (add-hook 'org-after-todo-state-change-hook 'check-and-unmark-completed-week-project) ;; Key binding for toggling weekly project mark (map! :map org-mode-map :localleader :desc "Toggle weekly project" "w" #'toggle-heading-for-week) (defun toggle-agenda-item-week-project () "Toggle WEEK_PROJECT property on the item at point in agenda." (interactive) (org-agenda-check-type t 'agenda 'todo 'tags 'search) (org-agenda-check-no-diary) (let* ((marker (or (org-get-at-bol 'org-marker) (org-agenda-error))) (buffer (marker-buffer marker)) (pos (marker-position marker))) (with-current-buffer buffer (save-excursion (goto-char pos) ;; Use your existing toggle function (toggle-heading-for-week))) (org-agenda-redo))) ;; Add direct keybinding in org-agenda-mode (map! :map org-agenda-mode-map :after org-agenda "W" #'toggle-agenda-item-week-project) ;; Embark action for org headings (defun embark-toggle-heading-week-project (heading-pos) "Toggle WEEK_PROJECT property on the heading at HEADING-POS." (interactive "d") (org-with-point-at heading-pos (toggle-heading-for-week))) ;; Add the embark action to org-heading map (with-eval-after-load 'embark (map! :map embark-org-heading-map "w" #'embark-toggle-heading-week-project))
Setting up sunrise and sunset times in org-agenda
;; Set org agenda sunrise, sunset times (setq calendar-latitude 40.425869 calendar-longitude -86.908066)
Refresh org-agenda files at runtime and startup
(defun my/org-roam-refresh-agenda-list () (interactive) (setq org-agenda-files (append (list "mobile.org" "work.org" "todo.org" "personal.org" "ideas.org" "visa.org" "harshitha.org" "calendar-beorg.org" "wedding-planning.org") (directory-files (file-name-concat org-directory "200-notes" "project notes") t "\\.org$")))) ;; Build the agenda list the first time for the session (my/org-roam-refresh-agenda-list) (with-eval-after-load 'org (setq org-todo-repeat-to-state t org-agenda-skip-scheduled-if-done t org-agenda-skip-deadline-if-done t org-log-done 'time org-export-with-toc nil org-log-into-drawer t))
Start org-agenda from current day
Setting org-agenda-start-on-weekday to nil means that the agenda view – even in the 7-days-at-a-time view – will always begin on the current day. This is important, since while using org-mode as a day planner, you never want to think of days gone past. That’s something you do in other ways, such as when reviewing completed tasks.
(with-eval-after-load 'org (setq org-agenda-start-on-weekday nil) (setq org-agenda-start-day "-0d"))
Org Roam
Setup org-roam
(setq org-roam-directory (file-name-concat org-directory "200-notes" "permanent notes")) (use-package org-roam :init (setq org-roam-v2-ack t) :custom (setq org-roam-completion-everywhere t) (setq org-roam-capture-templates '(("d" "default" plain "%?" :if-new (file+head "%<%Y%m%d%H%M%S>-${slug}.org" "#+title: ${title}\n") :unnarrowed t))))
Setup org-roam-ui
(use-package websocket :after org-roam) (use-package org-roam-ui :after org-roam ;; or :after org ;; normally we'd recommend hooking orui after org-roam, but since org-roam does not have ;; a hookable mode anymore, you're advised to pick something yourself ;; if you don't care about startup time, use ;; :hook (after-init . org-roam-ui-mode) :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))
Latex
Fix latex mode on startup
;; (add-to-list 'auto-mode-alist '("\\.tex\\'" . LaTeX-mode))
Fontify parhead and italhead
(defun my-fold-parhead (arg) (propertize (concat arg ".") 'face 'bold)) (defun my-fold-italhead (arg) (propertize (concat arg ".") 'face 'italic)) (with-eval-after-load 'tex-fold (add-to-list 'TeX-fold-macro-spec-list '(my-fold-parhead ("parhead"))) (add-to-list 'TeX-fold-macro-spec-list '(my-fold-italhead ("italhead"))) (add-to-list 'TeX-fold-macro-spec-list '("[{1}]" ("label"))) (add-to-list 'TeX-fold-macro-spec-list '("[{1}]" ("cref" "Cref" "vref" "Vref" "cpageref"))) )
Prevent LaTeX compiler from stealing focus
Tweaked the code from https://github.com/doomemacs/doomemacs/blob/master/modules/lang/latex/%2Bviewers.el so that Skim uses the -g option which prevents Skim from stealing the focus.
(with-eval-after-load 'tex :config (dolist (viewer (reverse +latex-viewers)) (pcase viewer (`skim (when-let (app-path (and IS-MAC (file-exists-p! (or "/Applications/Skim.app" "~/Applications/Skim.app")))) (add-to-list 'TeX-view-program-selection '(output-pdf "Skim")) (add-to-list 'TeX-view-program-list (list "Skim" (format "%s/Contents/SharedSupport/displayline -r -b -g %%n %%o %%b" app-path))))))) )
Remove Superscript and subscript specialization
(setq tex-fontify-script nil) (setq TeX-electric-sub-and-superscript t)
Remove spellcheck in latex mode
(add-hook! 'TeX-mode-hook
;;(hl-todo-mode) ; TODO
;; Flycheck with both chktex and lacheck both just bother me with
;; worthless advice all the time. when my paper doesn't compile I'll re-
;; enable them.
(flycheck-mode -1))
CDLaTeX Configuration
(use-package cdlatex :bind (:map cdlatex-mode-map ("TAB" . cdlatex-tab)) :config (setq cdlatex-simplify-sub-super-scripts nil) ;; I do not want $x_{i}TAB$ to get converted into $x_i$. :hook (cdlatex-tab . yas-expand)) (defun yas-next-field-or-cdlatex nil (interactive) "Jump to the next Yas field correctly with cdlatex active." (if (or (bound-and-true-p cdlatex-mode) (bound-and-true-p org-cdlatex-mode)) (cdlatex-tab) (yas-next-field-or-maybe-expand)))
Ask for master file
(with-eval-after-load 'tex (setq-default TeX-master nil))
Use LatexMk by default
(with-eval-after-load 'latex (setq TeX-command-default "LatexMk"))
Paper project setup
A function to initialize a LaTeX paper project with clean build configuration.
(defun my/paper-latex-setup () "Set up a LaTeX paper project with clean build configuration. Creates: - .latexmkrc with output to build/ - build/ directory - .dir-locals.el with TeX-output-dir" (interactive) (let* ((project-root (or (projectile-project-root) (vc-root-dir) default-directory)) (latexmkrc-file (expand-file-name ".latexmkrc" project-root)) (dir-locals-file (expand-file-name ".dir-locals.el" project-root)) (build-dir (expand-file-name "build" project-root))) ;; Create build directory (unless (file-exists-p build-dir) (make-directory build-dir t) (message "Created build/ directory")) ;; Create .latexmkrc (unless (file-exists-p latexmkrc-file) (with-temp-file latexmkrc-file (insert "# LaTeXmk configuration file\n") (insert "$out_dir = 'build';\n") (insert "$pdf_mode = 1;\n") (insert "$pdflatex = 'pdflatex --synctex=1 --interaction=nonstopmode --file-line-error';\n")) (message "Created .latexmkrc")) ;; Create or update .dir-locals.el (let ((dir-locals-content '((LaTeX-mode . ((TeX-output-dir . "build")))))) (if (file-exists-p dir-locals-file) (message ".dir-locals.el already exists - please add TeX-output-dir manually if needed") (with-temp-file dir-locals-file (insert ";; -*- mode: emacs-lisp -*-\n") (insert (pp-to-string dir-locals-content))) (message "Created .dir-locals.el"))) ;; Reload dir-locals for current buffer (hack-dir-local-variables-non-file-buffer) (message "Paper project setup complete in %s" project-root)))
Add keybinding for TeX-font
(map! :map LaTeX-mode-map :leader :desc "fontification" "C-;" #'TeX-font)
Add keybinding for citar-insert-keys
(map! :map LaTeX-mode-map :localleader :desc "Insert cite keys" "k" #'citar-insert-keys)
FIXME Configure cdlatex-math-symbol-alist
;; Add oplus in the first level of (with-eval-after-load 'cdlatex ;; Configure symbols (setq cdlatex-math-symbol-alist (cons '(?+ ("\\cup" "\\oplus" "\\bigoplus")) (cons '(?* ("\\times" "\\otimes")) (assq-delete-all ?+ (assq-delete-all ?* cdlatex-math-symbol-alist))))) ;; Add mathbb modify for character 'a' (setq cdlatex-math-modify-alist '((?a "\\mathbb" nil t nil nil))) ;; Recompute tables (cdlatex-compute-tables))
Change viewer
(setq +latex-viewers '(pdf-tools))
Commenting shortcuts
(map! "M-/" #'comment-or-uncomment-region)
MacOS specific setup
LaTeX Binaries
To ensure that the LaTeX related buffers function properly.
(setenv "PATH" (concat (getenv "PATH") ":/Library/TeX/texbin")) (setq exec-path (append exec-path '("/Library/TeX/texbin"))) (cond (IS-MAC (setq mac-command-modifier 'super mac-option-modifier 'meta mac-right-option-modifier 'meta)))
Configure MacOS Swift Module
;; Fix this path (setq macos-module-install-dir (file-name-concat doom-local-dir "straight" "repos" "EmacsMacOSModule")) ;; (use-package macos ;; :config (macos-load-module)) ;; When this fails, do the following ;; Navigate to the folder ;; Run the "macos-rebuild-module-and-reload" command.
Fix Consult Locate Command
The default did not work. So, I am using this patch.
(setopt consult-locate-args "mdfind -name ")
Packages
Bibliography
Setup Citar
(use-package citar :after oc :custom (citar-bibliography '("~/Overleaf/References.bib")) (citar-notes-paths (list (file-name-concat org-directory "200-notes" "literature notes"))) (citar-templates '((main . "${author editor:30%sn} ${date year issued:4} ${title:48}") (suffix . " ${=key= id:15} ${=type=:12} ${tags keywords keywords:*}") (preview . "${author editor:%etal} (${year issued date}) ${title}, \ ${journal journaltitle publisher container-title collection-title}.\n") (note . "${title}"))) :config (setq org-cite-global-bibliography citar-bibliography) :hook (LaTeX-mode . citar-capf-setup) (org-mode . citar-capf-setup))
Change Citar Note template
(defun my-custom-citarorg-format-note-template (orig-fun key entry) "Custom template for Citar notes, including a pipe placeholder for easy navigation." (let* ((filepath (expand-file-name (concat key ".org") (car citar-notes-paths))) (buffer (find-file filepath)) (template (citar--get-template 'note)) (note-meta (when template (citar-format--entry template entry))) (title (or note-meta (citar-format--entry "${title}" entry)))) ;; Fallback if note-meta is nil (with-current-buffer buffer ;; (From the original function) This just overrides other template insertion. (erase-buffer) ;; Insert custom content (insert ":PROPERTIES:\n:DIR: images/\n:END:\n") (insert (format "#+title: %s\n\n" title)) (insert (format "Original paper: [cite:@%s]\n\n" key)) (insert "|\n\n* References\n#+print_bibliography:\n") ;; Move the point to the placeholder (search-backward "|") (delete-char 1) ;; Remove the pipe character (when (fboundp 'evil-insert) (evil-insert 1))))) (advice-add 'citar-org-format-note-default :around #'my-custom-citarorg-format-note-template)
Open Bibliographic Notes
(when (modulep! :completion vertico) (map! :leader :desc "Edit notes" "n e" #'citar-open-notes) (map! :leader :desc "Open Reference Files" "n b" #'citar-open-files))
Sort bibliography entries by year
(defun my/sort-by-year (candidates) "Sort candidates by year ascending, entries without a year last." (seq-sort (lambda (a b) (let ((ya (if (string-match "\\b\\([0-9]\\{4\\}\\)\\b" a) (string-to-number (match-string 1 a)) most-positive-fixnum)) (yb (if (string-match "\\b\\([0-9]\\{4\\}\\)\\b" b) (string-to-number (match-string 1 b)) most-positive-fixnum))) (< ya yb))) candidates)) (add-to-list 'vertico-multiform-categories '(citar-candidate (vertico-sort-function . my/sort-by-year)))
Searching
Configure isearch
(defun isearch-exit-at-front () "always exit isearch, at the front of search match." (interactive) (isearch-exit) (when isearch-forward (goto-char isearch-other-end))) (defun isearch-exit-at-end () "Always exit isearch, at the end of search match." (interactive) (isearch-exit) (when (not isearch-forward) (goto-char isearch-other-end))) (use-package emacs :bind (:map isearch-mode-map ("<return>" . isearch-exit-at-front) ("C-<return>" . isearch-exit-at-end)) :config (setq case-fold-search t isearch-lazy-count t))
Consult
Fix wgrep problems for long lined projects
(with-eval-after-load 'consult (setq consult-grep-max-columns nil))
Denote
Basic settings
(use-package denote :init (setq denote-directory (file-name-concat org-directory "200-notes")) :config (setq denote-file-name-components-order '(signature identifier title keywords) denote-known-keywords '("distsys" "crypto" "blockchain" "visa" "purdue")) (add-to-list 'safe-local-variable-directories denote-directory) (add-to-list 'safe-local-variable-directories (file-name-concat denote-directory "project notes")))
Projectile
Configuration
(setq projectile-project-search-path (list "~/Overleaf" "~/Github"))
Project local snippets
Inspired from here: https://www.reddit.com/r/emacs/comments/57i41t/projectlocal_snippets/
(defun crshd/set-projectile-yas-dir () "Append a projectile-local YAS snippet dir to yas-snippet-dirs." (interactive) (let ((local-yas-dir (concat (projectile-project-root) ".snippets"))) (when (file-directory-p local-yas-dir) (add-to-list 'yas-snippet-dirs local-yas-dir) (yas-reload-all)))) (add-hook 'projectile-find-file-hook 'crshd/set-projectile-yas-dir)
Novel
Visual Configuration
(defun my-nov-font-setup () "Setup fonts, centering, width, etc." (face-remap-add-relative 'variable-pitch :family "Iosevka Etoile" :height 1.2) (setq-local visual-fill-column-center-text t) (visual-line-fill-column-mode) (variable-pitch-mode 1) ;; Enable variable-pitch-mode irrespective of the previous state (setq-local line-spacing 0.2) (message "Successfully configured Nov Fonts.")) (use-package nov :mode ("\\.epub\\'" . nov-mode) :hook (nov-mode . my-nov-font-setup) :config (setq nov-save-place-file (concat doom-cache-dir "nov-places") nov-text-width t))
Mode line configuration
(defvar-local nov-chapter-chars nil "Buffer-local variable to store chapter character counts.") (defun nov-book-title () "Return the title of the current book from `nov-metadata`." (if (and (boundp 'nov-metadata) nov-metadata) (cdr (assq 'title nov-metadata)) (error "nov-metadata is not available"))) (defun nov-compute-chapter-chars () "Compute character count for each chapter in the current nov-mode buffer." (interactive) (when (eq major-mode 'nov-mode) (message "Computing chapter sizes...") (let ((chars-list nil) (current-index nov-documents-index) (current-point (point))) ;; Save the current position in history to restore it later (push (list current-index current-point) nov-history) ;; Process each document in nov-documents vector, skipping non-content entries (dotimes (i (length nov-documents)) (let* ((doc (aref nov-documents i)) (doc-id (car doc))) ;; Skip non-content entries like 'ncx' and 'titlepage' (unless (memq doc-id '(ncx titlepage)) (let ((temp-buffer (generate-new-buffer " *nov-temp*"))) (condition-case nil (progn ;; Use a temporary buffer to avoid changing point (with-current-buffer temp-buffer (insert-file-contents (cdr doc)) (let ((char-count (- (point-max) (point-min)))) (when (> char-count 0) (push (cons i char-count) chars-list)))) (kill-buffer temp-buffer)) (error (kill-buffer temp-buffer))))))) ;; Restore original position (nov-goto-document current-index) (goto-char current-point) (when chars-list (setq-local nov-chapter-chars (nreverse chars-list)) (message "Done computing chapter sizes."))))) (add-hook 'nov-mode-hook 'nov-compute-chapter-chars) (defun nov-get-percentage () "Get percentage of ebook completed in nov-mode." (interactive) (when (eq major-mode 'nov-mode) (unless nov-chapter-chars (nov-compute-chapter-chars)) (if (null nov-chapter-chars) (message "No chapter data available. Try running nov-compute-chapter-chars first.") (let* ((total-chars 0) (chars-before-current 0) (current-chapter-chars 0) (current-chapter-read 0)) ;; Calculate total characters (dolist (chapter nov-chapter-chars) (setq total-chars (+ total-chars (cdr chapter)))) ;; Calculate characters in chapters before current (dolist (chapter nov-chapter-chars) (when (< (car chapter) nov-documents-index) (setq chars-before-current (+ chars-before-current (cdr chapter))))) ;; Get current chapter's character count (setq current-chapter-chars (or (cdr (assoc nov-documents-index nov-chapter-chars)) 0)) ;; Calculate how much of current chapter is read (when (> current-chapter-chars 0) (setq current-chapter-read (* (/ (float (- (point) (point-min))) (float (- (point-max) (point-min)))) current-chapter-chars))) ;; Calculate and display percentage (if (= total-chars 0) (message "Total book size is zero characters.") (let ((percentage (* 100 (/ (float (+ chars-before-current current-chapter-read)) (float total-chars))))) percentage)))))) (defun nov-get-chapter-percentage () "Get percentage of current chapter completed in nov-mode." (interactive) (when (eq major-mode 'nov-mode) (let* ((current-pos (- (point) (point-min))) (chapter-size (- (point-max) (point-min))) (percentage (if (> chapter-size 0) (* 100 (/ (float current-pos) (float chapter-size))) 0))) (if (called-interactively-p 'any) (message "Chapter progress: %.2f%%" percentage) percentage)))) (with-eval-after-load 'doom-modeline ;; Define your custom doom-modeline segments (doom-modeline-def-segment book-name "Show current book title in nov-mode." (when (eq major-mode 'nov-mode) (let ((sep (doom-modeline-spc))) (concat sep (propertize (nov-book-title) 'face 'doom-modeline-buffer-file) sep)))) (doom-modeline-def-segment book-percentage "Show current position as percentage in nov-mode." (when (eq major-mode 'nov-mode) (let ((sep (doom-modeline-spc)) (vsep (doom-modeline-vspc))) (concat sep ;; Add the book/reading icon (doom-modeline-icon 'mdicon "nf-md-book_open_page_variant" "📖" "%" :face 'doom-modeline-info :v-adjust 0.15) vsep (propertize (format "Book: %.2f%%%%" (nov-get-percentage)) 'face 'doom-modeline-info) sep)))) (doom-modeline-def-segment book-chapter-percentage "Show current chapter position as percentage in nov-mode." (when (eq major-mode 'nov-mode) (let ((sep (doom-modeline-spc)) (vsep (doom-modeline-vspc))) (concat sep (doom-modeline-icon 'octicon "nf-oct-bookmark" "🔖 " "Ch" :face 'doom-modeline-warning :v-adjust 0.05) vsep (propertize (format "Chapter %d/%d: %.2f%%%%" (1+ nov-documents-index) (length nov-documents) (nov-get-chapter-percentage)) 'face 'doom-modeline-warning) sep)))) (doom-modeline-def-modeline 'my-nov-modeline '(bar workspace-name window-number modals matches follow book-name book-chapter-percentage book-percentage selection-info) '(misc-info major-mode process vcs check time)) ;; Configure other mode-lines based on major modes (add-to-list 'doom-modeline-mode-alist '(nov-mode . my-nov-modeline)))
Go to TOC, keep point at current chapter
(defun my/nov-goto-toc-and-chapter () "Go to the TOC and move point to the current chapter entry." (interactive) (let* ((current-doc-path (cdr (aref nov-documents nov-documents-index))) (current-basename (file-name-nondirectory current-doc-path))) ;; First go to the TOC (nov-goto-toc) ;; Now find the link to the current chapter and move to it (let ((pos (point-min)) (found nil)) (while (and (not found) (setq pos (next-single-property-change pos 'shr-url nil (point-max)))) (let ((url (get-text-property pos 'shr-url))) (when url (let* ((filename-target (nov-url-filename-and-target url)) (filename (car filename-target))) (when filename (let ((url-basename (file-name-nondirectory filename))) ;; Compare file basenames to find a match (when (equal url-basename current-basename) (setq found t) (goto-char pos) (message "Moved to chapter %s" url-basename)))))))) (unless found (message "Chapter not found in TOC"))))) ;; Replace the original keybinding (map! :map nov-mode-map :desc "Go to TOC and find current chapter" "t" #'my/nov-goto-toc-and-chapter)
Move all Novel buffers to Reading workspace
;; Define a constant for the target workspace name (defconst my/reading-workspace-name "Reading" "The designated workspace name for reading activities (e.g., nov.el buffers).") ;; Configure the nov package (use-package nov :config ;; Define the helper function within the package's config (defun my/ensure-reading-workspace-for-nov () "Switch to the workspace defined by `my/reading-workspace-name` if the current buffer is in nov-mode and we aren't already in that workspace. Creates the workspace if it doesn't exist." (let ((current-ws-name (+workspace-current-name))) (unless (string= current-ws-name my/reading-workspace-name) (message "Ensuring '%s' workspace for nov.el buffer..." my/reading-workspace-name) (+workspace-switch my/reading-workspace-name t)))) ;; Add the function to the hook (add-hook 'nov-mode-hook #'my/ensure-reading-workspace-for-nov))
Logview
(use-package logview)
Github Copilot
;; accept completion from copilot and fallback to company (use-package copilot :hook (prog-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)) :config (add-to-list 'copilot-indentation-alist '(prog-mode 2)) (add-to-list 'copilot-indentation-alist '(org-mode 2)) (add-to-list 'copilot-indentation-alist '(text-mode 2)) (add-to-list 'copilot-indentation-alist '(closure-mode 2)))
Document Viewer
I find the default resolution pixelated.
(setq doc-view-resolution 1000)
(setq +mu4e-backend nil) (with-eval-after-load 'mu4e ;; General settings (setq mu4e-update-interval 300 mu4e-compose-format-flowed t mu4e-view-show-images t mu4e-change-filenames-when-moving t mu4e-sent-messages-behavior 'trash) ;; Changed from 'delete' to 'trash' ;; Configure multiple accounts (setq mu4e-contexts (list ;; Gmail Account (make-mu4e-context :name "gmail" :match-func (lambda (msg) (when msg (string-prefix-p "/gmail" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "dth.bht@gmail.com") (user-full-name . "Adithya Bhat") (mu4e-sent-folder . "/gmail/[Gmail].Sent Mail") (mu4e-drafts-folder . "/gmail/[Gmail].Drafts") (mu4e-trash-folder . "/gmail/[Gmail].Trash") (mu4e-refile-folder . "/gmail/[Gmail].All Mail") (smtpmail-smtp-user . "dth.bht@gmail.com") (smtpmail-smtp-server . "smtp.gmail.com") (smtpmail-smtp-service . 587) (smtpmail-stream-type . starttls))) ;; Research Account (make-mu4e-context :name "research" :match-func (lambda (msg) (when msg (string-prefix-p "/research" (mu4e-message-field msg :maildir)))) :vars '((user-mail-address . "haxolotl.research@gmail.com") (user-full-name . "Adithya Bhat") (mu4e-sent-folder . "/research/[Gmail].Sent Mail") (mu4e-drafts-folder . "/research/[Gmail].Drafts") (mu4e-trash-folder . "/research/[Gmail].Trash") (mu4e-refile-folder . "/research/[Gmail].All Mail") (smtpmail-smtp-user . "haxolotl.research@gmail.com") (smtpmail-smtp-server . "smtp.gmail.com") (smtpmail-smtp-service . 587) (smtpmail-stream-type . starttls))))) ;; Set default context (setq mu4e-context-policy 'pick-first) (setq mu4e-compose-context-policy 'ask) ;; Use your wrapper alias (setq mu4e-get-mail-command "mbsync-wrapper -a"))
Avy
(defun avy-action-dictionary (pt) (unwind-protect (save-excursion (goto-char pt) (let ((word (thing-at-point 'word))) (when word (+lookup/dictionary-definition word))))) t) (with-eval-after-load 'avy (setf (alist-get ?p avy-dispatch-alist) 'avy-action-dictionary))
PDF View
(with-eval-after-load 'pdf-view ;; Bind to link (map! :map pdf-view-mode-map "C-." #'pdf-links-isearch-link "c" #'pdf-view-center-in-window))
Combination
Configure interaction between multiple packages here. For example, avy and embark
Avy and Embark
(defun avy-action-embark (pt) (unwind-protect (save-excursion (goto-char pt) (embark-act)) (select-window (cdr (ring-ref avy-ring 0)))) t) (with-eval-after-load 'embark (with-eval-after-load 'avy (setf (alist-get ?. avy-dispatch-alist) 'avy-action-embark)))
Fixes
Temporary patches that should solve themselves during doom upgrades. So revisit them when upgrading and track the issues correspondingly.
Patch Org Snippet Expansion
(map! :map org-mode-map :after yasnippet [tab] yas-maybe-expand ;; Optionally, bind other keys for snippet navigation "C-c n" #'yas-next-field "C-c p" #'yas-prev-field)
(insert-file-contents "~/.config/doom/config.org")