#+TITLE: rogs DOOM Emacs config #+AUTHOR: Roger Gonzalez (rogs) #+DESCRIPTION: rogs personal Doom Emacs config. * Introduction :PROPERTIES: :ID: 4e8ec984-b517-4e34-b018-7464650b2b9f :END: This configuration represents my personal Doom Emacs setup, focused on productivity, organization with Org-mode, and programming. The file is organized into logical sections, starting with basic settings, then appearance, followed by major functionality areas like Org-mode and programming language support. Key principles in this configuration: - Prioritize keyboard-driven workflows - Integrate personal productivity systems through Org-mode - Optimize for both writing and coding tasks This document serves both as configuration and as documentation. Each section explains not just what the code does, but why it's important to my workflow. If you're new to Doom Emacs, you can use this as a reference for setting up your own configuration. To navigate this file efficiently: - Use =SPC n s= to search for specific settings - Use =TAB= on headings to expand/collapse sections - Look for the explanatory text before each code block * Basic configuration :PROPERTIES: :ID: 4e8ec984-b517-4e34-b018-7464650b2b9f :END: This section contains fundamental identity settings that are used by various Emacs packages, particularly for version control systems like Git and for email clients. These settings ensure that my contributions are properly attributed. First off, set my user and email #+begin_src emacs-lisp (setq user-full-name "Roger Gonzalez" user-mail-address "roger@rogs.me") #+end_src * Look and feel :PROPERTIES: :ID: 0b198a7a-c736-4dd4-84a3-0ea21bcdc4fb :END: This section configures the visual aspects of my Emacs experience. I prefer a clean, distraction-free interface with a dark theme that's easy on the eyes during long coding sessions. Font choices prioritize readability and support for programming ligatures. ** Fonts :PROPERTIES: :ID: b4df4ef4-d0ca-4047-90b3-f4128425aa9f :END: This works only in Linux. On MacOS we keep the default fonts. #+begin_src emacs-lisp (if (not (eq system-type 'darwin)) (progn (setq doom-font (font-spec :family "MesloLGS Nerd Font" :size 14) doom-variable-pitch-font (font-spec :family "sans") doom-big-font (font-spec :family "MesloLGS Nerd Font" :size 24)))) #+end_src ** Theme :PROPERTIES: :ID: 3bae130e-3336-4bc7-9378-82c315e2aea6 :END: #+begin_src emacs-lisp (after! doom-themes (setq doom-themes-enable-bold t doom-themes-enable-italic t)) (custom-set-faces! '(font-lock-comment-face :slant italic) '(font-lock-keyword-face :slant italic)) (setq doom-theme 'doom-badger) (setq fancy-splash-image "~/.config/doom/logo.png") #+end_src ** Misc :PROPERTIES: :ID: cf3c202b-7610-4038-8e15-654a95a9d1dc :END: *** Display relative numbers on the buffer :PROPERTIES: :ID: 6a510691-0b78-44b8-ab92-518971051d8a :END: #+begin_src emacs-lisp (setq display-line-numbers-type 'relative) #+end_src * Org Mode + Org Roam :PROPERTIES: :ID: 96b93a81-3272-4f7a-a667-8a8783849d64 :END: The cornerstone of my productivity system. My entire life is managed through Org Mode and Org Roam, from task management and note-taking to project planning and knowledge management. This extensive configuration reflects years of refinement to match my personal workflow. The setup includes custom agenda views, capture templates, and todo states that implement a GTD-inspired system with my own modifications for different types of tasks and projects. Key components of this system: - Custom TODO states that reflect my workflow (NEXT, WAITING, SOMEDAY, etc.) - Visual styling to quickly identify task states and priorities - Capture templates for different types of information - Custom agenda views for different perspectives on my tasks - Recurring task handling with org-recur - ID-based linking for creating a personal knowledge graph This section is organized into logical subsections, each focusing on a specific aspect of the Org Mode configuration. The most frequently used features are placed earlier in the file. ** Set the directory :PROPERTIES: :ID: 99cbc04c-604c-4427-94fc-aa0603c78809 :END: #+begin_src emacs-lisp (setq org-directory "~/org/") (setq org-roam-directory "~/roam/") #+end_src ** Basic Org Setup :PROPERTIES: :ID: 37915445-e875-4da0-bab0-3f8f8b8e89f5 :END: This section contains the foundational settings for Org mode, including directory paths and basic behavior. #+begin_src emacs-lisp (after! org ;; Include diary (setq org-agenda-include-diary t) ;; Enforce ordered tasks (setq org-enforce-todo-dependencies t) (setq org-enforce-todo-checkbox-dependencies t) (setq org-track-ordered-property-with-tag t) ;; Text formatting (add-hook 'org-mode-hook #'auto-fill-mode) (setq-default fill-column 105) ;; Save all org buffers on each save (add-hook 'auto-save-hook 'org-save-all-org-buffers) (add-hook 'after-save-hook 'org-save-all-org-buffers) (require 'org-download) (add-hook 'dired-mode-hook 'org-download-enable) (add-hook 'org-mode-hook 'org-auto-tangle-mode)) #+end_src ** Task Management :PROPERTIES: :ID: 37915445-e875-4da0-bab0-3f8f8b8e89f6 :END: Configuration for task tracking, including TODO keywords, priorities, and logging behavior. This implements a GTD-inspired workflow with custom states to track different stages of task completion. The TODO keywords are carefully chosen to represent distinct states in my workflow: - REPEAT: Tasks that recur on a schedule - NEXT: The immediate next actions I should focus on - DELEGATED: Tasks I've assigned to someone else - TODO: Standard tasks that need to be done - WAITING: Tasks blocked by external factors - SOMEDAY: Ideas or tasks for future consideration - PROJ: Project containers that group related tasks Priorities follow a simple decision matrix: - A: Do it now (urgent and important) - B: Decide when to do it (important but not urgent) - C: Delegate it (urgent but not important) - D: Just an idea (neither urgent nor important) The logging configuration ensures I maintain a history of state changes, completion times, and notes about rescheduling. #+begin_src emacs-lisp (after! org ;; Logs (setq org-log-state-notes-insert-after-drawers nil org-log-into-drawer "LOGBOOK" org-log-done 'time org-log-repeat 'time org-log-redeadline 'note org-log-reschedule 'note) ;; TODO keywords and states (setq-default org-todo-keywords '((sequence "REPEAT(r)" "NEXT(n@/!)" "DELEGATED(e@/!)" "TODO(t@/!)" "WAITING(w@/!)" "SOMEDAY(s@/!)" "PROJ(p)" "|" "DONE(d@)" "CANCELLED(c@/!)" "FORWARDED(f@)"))) ;; Priorities configuration ;; A: Do it now ;; B: Decide when to do it ;; C: Delegate it ;; D: Just an idea (setq org-highest-priority ?A) (setq org-lowest-priority ?D) (setq org-default-priority ?B)) #+end_src ** Visual Styling :PROPERTIES: :ID: 37915445-e875-4da0-bab0-3f8f8b8e89f7 :END: Visual appearance settings for Org mode, including fonts, colors, and formatting. #+begin_src emacs-lisp (after! org ;; TODO keyword faces (setq-default org-todo-keyword-faces '(("REPEAT" . (:foreground "white" :background "indigo" :weight bold)) ("NEXT" . (:foreground "red" :background "orange" :weight bold)) ("DELEGATED" . (:foreground "white" :background "blue" :weight bold)) ("TODO" . (:foreground "white" :background "violet" :weight bold)) ("WAITING" (:foreground "white" :background "#A9BE00" :weight bold)) ("SOMEDAY" . (:foreground "white" :background "#00807E" :weight bold)) ("PROJ" . (:foreground "white" :background "deeppink3" :weight bold)) ("DONE" . (:foreground "white" :background "forest green" :weight bold)) ("CANCELLED" . (:foreground "light gray" :slant italic)) ("FORWARDED" . (:foreground "light gray" :slant italic)))) ;; Priority faces (setq org-priority-faces '((?A . (:foreground "white" :background "dark red" :weight bold)) (?B . (:foreground "white" :background "dark green" :weight bold)) (?C . (:foreground "yellow")) (?D . (:foreground "gray")))) ;; Headline styling (setq org-fontify-done-headline t) (setq org-fontify-todo-headline t) ;; Org bullets for prettier headings (require 'org-bullets) (add-hook 'org-mode-hook (lambda () (org-bullets-mode 1)))) #+end_src ** Capture Templates :PROPERTIES: :ID: 37915445-e875-4da0-bab0-3f8f8b8e89f8 :END: Templates for quickly capturing various types of information into Org mode. #+begin_src emacs-lisp (after! org (setq org-capture-templates (quote (("G" "Define a goal" entry (file+headline "~/org/capture.org" "Capture") (file "~/org/templates/goal.org") :empty-lines-after 1) ("R" "REPEAT entry" entry (file+headline "~/org/capture.org" "Capture") (file "~/org/templates/repeat.org") :empty-lines-before 1) ("N" "NEXT entry" entry (file+headline "~/org/capture.org" "Capture") (file "~/org/templates/next.org") :empty-lines-before 1) ("T" "TODO entry" entry (file+headline "~/org/capture.org" "Capture") (file "~/org/templates/todo.org") :empty-lines-before 1) ("W" "WAITING entry" entry (file+headline "~/org/capture.org" "Capture") (file "~/org/templates/waiting.org") :empty-lines-before 1) ("S" "SOMEDAY entry" entry (file+headline "~/org/capture.org" "Capture") (file "~/org/templates/someday.org") :empty-lines-before 1) ("P" "PROJ entry" entry (file+headline "~/org/capture.org" "Capture") (file "~/org/templates/proj.org") :empty-lines-before 1) ("B" "Book on the to-read-list" entry (file+headline "~/org/private.org" "Libros para leer") (file "~/org/templates/book.org") :empty-lines-after 2) ("p" "Create a daily plan") ("pP" "Daily plan private" plain (file+olp+datetree "~/org/plan-free.org") (file "~/org/templates/dailyplan.org") :immediate-finish t :jump-to-captured t) ("pL" "Daily plan Lazer" plain (file+olp+datetree "~/org/plan-lazer.org") (file "~/org/templates/dailyplan.org") :immediate-finish t :jump-to-captured t) ("j" "Journal entry") ("jP" "Journal entry private" entry (file+olp+datetree "~/org/journal-private.org") "** %U - %^{Heading}") ("jL" "Journal entry Lazer" entry (file+olp+datetree "~/org/journal-lazer.org") "** %U - %^{Heading}"))))) #+end_src ** Agenda Views :PROPERTIES: :ID: 37915445-e875-4da0-bab0-3f8f8b8e89f9 :END: Custom agenda views for different perspectives on tasks and events. #+begin_src emacs-lisp (after! org (setq org-agenda-custom-commands (quote (("A" . "Agendas") ("AT" "Daily overview" ((tags-todo "URGENT" ((org-agenda-overriding-header "Urgent Tasks"))) (tags-todo "RADAR" ((org-agenda-overriding-header "On my radar"))) (tags-todo "PHONE+TODO=\"NEXT\"" ((org-agenda-overriding-header "Phone Calls"))) (tags-todo "COMPANY" ((org-agenda-overriding-header "Cuquitoni"))) (tags-todo "SHOPPING" ((org-agenda-overriding-header "Shopping"))) (tags-todo "Depth=\"Deep\"/NEXT" ((org-agenda-overriding-header "Next Actions requiring deep work"))) (agenda "" ((org-agenda-overriding-header "Today") (org-agenda-span 1) (org-agenda-start-day "1d") (org-agenda-sorting-strategy (quote (time-up priority-down))))) nil nil)) ("AW" "Weekly overview" agenda "" ((org-agenda-overriding-header "Weekly overview"))) ("AM" "Monthly overview" agenda "" ((org-agenda-overriding-header "Monthly overview")) (org-agenda-span (quote month)) (org-deadline-warning-days 0) (org-agenda-sorting-strategy (quote (time-up priority-down tag-up)))) ("W" . "Weekly Review Helper") ("Wn" "New tasks" tags "NEW" ((org-agenda-overriding-header "NEW Tasks"))) ("Wd" "Check DELEGATED tasks" todo "DELEGATED" ((org-agenda-overriding-header "DELEGATED tasks"))) ("Ww" "Check WAITING tasks" todo "WAITING" ((org-agenda-overriding-header "WAITING tasks"))) ("Ws" "Check SOMEDAY tasks" todo "SOMEDAY" ((org-agenda-overriding-header "SOMEDAY tasks"))) ("Wf" "Check finished tasks" todo "DONE|CANCELLED|FORWARDED" ((org-agenda-overriding-header "Finished tasks"))) ("WP" "Planing ToDos (unscheduled) only" todo "TODO|NEXT" ((org-agenda-overriding-header "To plan") (org-agenda-skip-function (quote (org-agenda-skip-entry-if (quote scheduled) (quote deadline)))))))))) #+end_src ** Org Recur :PROPERTIES: :ID: 37915445-e875-4da0-bab0-3f8f8b8e8910 :END: Configuration for handling recurring tasks with org-recur. #+begin_src emacs-lisp (after! org ;; Load org-recur (require 'org-recur) (after! org-recur (add-hook 'org-mode-hook #'org-recur-mode) (add-hook 'org-agenda-mode-hook #'org-recur-agenda-mode) (map! :map org-recur-mode-map "C-c d" #'org-recur-finish) (map! :map org-recur-agenda-mode-map "C-c d" #'org-recur-finish "C-c 0" #'org-recur-schedule-today) (setq org-recur-finish-done t org-recur-finish-archive t))) #+end_src ** Custom Org Functions :PROPERTIES: :ID: 37915445-e875-4da0-bab0-3f8f8b8e8911 :END: Custom functions to enhance Org mode functionality. These functions extend Org mode's capabilities to better support my specific workflow needs, including: - Automatic agenda refreshing after rescheduling tasks - Focus functions to filter agenda views by context - Automatic ID generation for reliable linking between notes - Clipboard functions for quick creation of Org links - Checkbox reset functionality for recurring tasks - Streamlined node insertion for Org-roam These functions represent solutions to friction points I've encountered in my daily use of Org mode, making the system more efficient and tailored to my needs. #+begin_src emacs-lisp (after! org ;; Refresh org-agenda after rescheduling a task (defun org-agenda-refresh () "Refresh all `org-agenda' buffers more efficiently." (let ((agenda-buffers (seq-filter (lambda (buf) (with-current-buffer buf (derived-mode-p 'org-agenda-mode))) (buffer-list)))) (dolist (buffer agenda-buffers) (with-current-buffer buffer (org-agenda-maybe-redo))))) (defadvice org-schedule (after refresh-agenda activate) "Refresh org-agenda." (org-agenda-refresh)) ;; Focus functions (defun org-focus (files msg) "Set focus on specific org FILES with notification MSG." (setq org-agenda-files files) (message msg)) (defun org-focus-private () "Set focus on private things." (interactive) (org-focus '("~/org/private.org") "Focusing on private Org files")) (defun org-focus-lazer () "Set focus on Lazer things." (interactive) (org-focus '("~/org/lazer.org") "Focusing on Lazer Org files")) (defun org-focus-all () "Set focus on all things." (interactive) (org-focus '("~/org/") "Focusing on all Org files")) ;; ID management (defun my/org-add-ids-to-headlines-in-file () "Add ID properties to all headlines in the current file which do not already have one." (interactive) (org-map-entries 'org-id-get-create)) (add-hook 'org-mode-hook (lambda () (add-hook 'before-save-hook 'my/org-add-ids-to-headlines-in-file nil 'local))) (defun my/copy-idlink-to-clipboard () "Copy an ID link with the headline to killring. If no ID exists, create a new unique ID. This function works only in org-mode or org-agenda buffers. The purpose of this function is to easily construct id:-links to org-mode items. If its assigned to a key it saves you marking the text and copying to the killring. This function is a cornerstone of my note-linking workflow. It creates and copies an org-mode ID link to the current heading, making it easy to reference content across my knowledge base. I use this constantly when creating connections between related notes or tasks." (interactive) (when (eq major-mode 'org-agenda-mode) ;if we are in agenda mode we switch to orgmode (org-agenda-show) (org-agenda-goto)) (when (eq major-mode 'org-mode) ; do this only in org-mode buffers (let* ((heading (nth 4 (org-heading-components))) (id (org-id-get-create)) (link (format "[[id:%s][%s]]" id heading))) (kill-new link) (message "Copied %s to killring (clipboard)" link)))) (global-set-key (kbd "") 'my/copy-idlink-to-clipboard) ;; Checkbox handling (defun org-reset-checkbox-state-maybe () "Reset all checkboxes in an entry if the `RESET_CHECK_BOXES' property is set." (interactive "*") (when (org-entry-get (point) "RESET_CHECK_BOXES") (org-reset-checkbox-state-subtree))) (defun org-checklist () (when (member org-state org-done-keywords) ;; org-state dynamically bound in org.el/org-todo (org-reset-checkbox-state-maybe))) (add-hook 'org-after-todo-state-change-hook 'org-checklist) ;; Org-roam functions (defun org-roam-node-insert-immediate (arg &rest args) "Insert a node immediately without the capture process." (interactive "P") (let ((args (cons arg args)) (org-roam-capture-templates (list (append (car org-roam-capture-templates) '(:immediate-finish t))))) (apply #'org-roam-node-insert args)))) #+end_src * Email (mu4e) :PROPERTIES: :ID: aecdbef2-af4b-4f9b-9aa4-94393b66395f :END: This section configures my email client, =mu4e= (mu for Emacs), integrating email directly into my Emacs workflow. Managing email within Emacs allows for seamless integration with Org mode for task management derived from emails, leverages powerful text editing capabilities for composing messages, and maintains a consistent keyboard-driven interface. Key components of this setup: - Backend Tools: Uses =mbsync= (from =isync=) for fetching mail via IMAP and =msmtp= for sending mail via SMTP. These are external tools that =mu4e= interfaces with. - Mail Storage: Mail is stored locally in the Maildir format specified by =mu4e-maildir= (=~/.mail=), enabling offline access and fast searching via the =mu= indexer. - Configuration Structure: The settings are broken down into logical blocks below: Basic Setup, Composing, Viewing, Sending, Account Specific, and Custom Marks. *Note:* This setup requires the =mu= command-line tool and its Emacs interface, =mu4e=. If =mu4e= is installed system-wide (e.g., via a package manager) instead of through Doom's =:email mu4e= module, you might need to explicitly add its directory (often =/usr/share/emacs/site-lisp/mu4e=) to Emacs' =load-path=, as shown in the first code block below. If Doom manages =mu4e=, that line might be redundant. #+begin_src emacs-lisp (add-to-list 'load-path "/usr/share/emacs/site-lisp/mu4e") #+end_src *** Basic Setup (Maildir, Fetching) :PROPERTIES: :ID: 503999c4-db77-42fa-8686-df4839bf618b :END: This block sets the root mail directory, the command used to fetch mail (=mbsync=), the interval for checking new mail, and ensures compatibility with =mbsync= when moving files. #+begin_src emacs-lisp (after! mu4e (setq mu4e-maildir "~/.mail" mu4e-get-mail-command "mbsync -a" mu4e-update-interval 150 mu4e-change-filenames-when-moving t)) (after! mu4e (setq mu4e-headers-skip-duplicates t mu4e-headers-include-related nil mu4e-headers-include-trash nil mu4e-headers-visible-fields '(:date :flags :from :subject))) (after! mu4e (setq mu4e-bookmarks '(("maildir:/roger@rogs.me/Inbox AND NOT maildir:/roger@rogs.me/Trash" "Inbox" ?i) ("flag:unread AND NOT maildir:/roger@rogs.me/Trash" "Unread" ?u) ("date:today..now AND NOT maildir:/roger@rogs.me/Trash" "Today" ?t) ("date:7d..now AND NOT maildir:/roger@rogs.me/Trash" "Last 7 days" ?w)))) #+end_src *** Composing Settings :PROPERTIES: :ID: e1b5f5af-7c44-4539-a9b6-96e1c0f6e1e8 :END: These settings control aspects of writing emails, such as using =format=flowed= for plain text, disabling Org mode for composing by default, and preventing automatic signature insertion. #+begin_src emacs-lisp (after! mu4e (setq mu4e-compose-format-flowed t mu4e-compose-org-mode nil mu4e-compose-html-format-flowed nil mu4e-compose-signature-auto-include nil)) #+end_src *** Viewing Settings :PROPERTIES: :ID: fd5baefb-ac40-40e5-bfff-f5f6cd58a392 :END: Configure how emails are displayed, enabling inline images and setting =shr= as the HTML renderer. #+begin_src emacs-lisp (after! mu4e (setq mu4e-view-show-images t mm-text-html-renderer 'shr)) #+end_src *** Sending Settings (General) :PROPERTIES: :ID: 4d47f993-be0f-4156-a31b-eab5331fbe1e :END: This block configures the general mechanism for sending mail, specifying the external program (=msmtp=), the source for authentication credentials (=~/.authinfo.gpg=), and the function Emacs uses to initiate sending. It also ensures the compose buffer closes after sending and passes necessary arguments to =msmtp=. Specific server details are handled per-account. #+begin_src emacs-lisp (after! mu4e (setq message-kill-buffer-on-exit t auth-sources '("~/.authinfo.gpg") sendmail-program (executable-find "msmtp") message-sendmail-f-is-evil t message-sendmail-extra-arguments '("--read-envelope-from") message-send-mail-function #'message-send-mail-with-sendmail)) #+end_src *** Account Specific Configuration :PROPERTIES: :ID: 62543551-4678-4851-bbe4-fc1c4f67ca5a :END: The =set-email-account!= helper manages settings specific to the "roger@rogs.me" account, including standard mail folder locations (Sent, Drafts, Trash, Archive/Refile), the SMTP username, SMTP server details (address, port, encryption), the default 'From' address, and the multi-line signature. The final =t= argument designates this as the default account. #+begin_src emacs-lisp (after! mu4e (set-email-account! "roger@rogs.me" `((mu4e-sent-folder . "/roger@rogs.me/Sent") (mu4e-drafts-folder . "/roger@rogs.me/Drafts") (mu4e-trash-folder . "/roger@rogs.me/Trash") (mu4e-refile-folder . "/roger@rogs.me/Archive") (smtpmail-smtp-user . "roger@rogs.me") (smtpmail-smtp-server . "127.0.0.1") (smtpmail-smtp-service . 1025) (smtpmail-stream-type . starttls) (user-mail-address . "roger@rogs.me") (mu4e-compose-signature . ,(string-join '("Roger González" "Senior Python Developer / DevOps engineer" "" "E: roger@rogs.me" "P: +59899563410" "W: https://rogs.me" "PGP: ADDF BCB7 8B86 8D93 FC4E 3224 C7EC E9C6 C36E C2E6") "\n"))) t)) #+end_src * Programming languages :PROPERTIES: :ID: fcb176c9-c9e5-42f6-b31d-3dafe8d0f64b :END: This section configures language-specific settings for the programming languages I use regularly. Each language has its own requirements for linting, formatting, and IDE-like features, which are handled through LSP where possible. My development workflow relies on having consistent tooling across different languages, with features like: - Code completion and documentation - Syntax checking and linting - Formatting according to language standards - Navigation and refactoring tools LSP (Language Server Protocol) provides most of these features in a standardized way across languages, while language-specific configurations handle unique requirements for each language ecosystem. ** LSP :PROPERTIES: :ID: 84836840-8642-46ad-8068-dc07086708f3 :END: #+begin_src emacs-lisp (after! lsp-mode (setq lsp-headerline-breadcrumb-enable t) (setq lsp-headerline-breadcrumb-icons-enable t)) #+end_src ** Python :PROPERTIES: :ID: 8f3279cf-53e2-4fe5-b30b-724d2d081cbe :END: #+begin_src emacs-lisp (after! python :init (require 'auto-virtualenv) (setq auto-virtualenv-global-dirs '("~/.virtualenvs/" "~/.pyenv/versions/" "~/.envs/" "~/.conda/" "~/.conda/envs/" "./.venv")) (add-hook 'python-mode-hook 'auto-virtualenv-setup) (setq enable-local-variables :all) (setq poetry-tracking-strategy 'projectile) (setq cov-coverage-mode t) (add-hook 'python-mode-hook 'cov-mode)) #+end_src ** Groovy :PROPERTIES: :ID: 8f8956c2-a7a3-4508-8f30-dc7a2f5e105b :END: #+begin_src emacs-lisp (after! groovy-mode (define-key groovy-mode-map (kbd "") 'my/jenkins-verify)) #+end_src ** Go :PROPERTIES: :ID: ee0c0fc1-7801-45ba-9302-73a78ce3d329 :END: #+begin_src emacs-lisp (setq lsp-go-analyses '((shadow . t) (simplifycompositelit . :json-false))) #+end_src ** RestClient :PROPERTIES: :ID: cf97ccd8-7023-48f0-8273-a1a64fad3fd0 :END: #+begin_src emacs-lisp (setq restclient-same-buffer-response nil) #+end_src * Custom :PROPERTIES: :ID: cd8a28bd-d91f-4ba8-b637-cb542ff5cca4 :END: Here's where custom functionalities get configured. ** Custom packages :PROPERTIES: :ID: 483ed79c-9eba-4544-8333-dda0139e9a08 :END: These are additional packages that aren't part of the standard Doom modules but that I find essential for my workflow: | Package name | Description | URL | |-----------------+--------------------------------------------------------------------------------------+-----------------------------------------| | ~screenshot.el~ | Good for taking screenshots directly in Emacs. | https://github.com/tecosaur/screenshot | | ~private.el~ | This is a file for private values and API keys that shouldn't be in version control. | ~./custom-packages/private.el.example~. | #+begin_src emacs-lisp (add-to-list 'load-path "~/.config/doom/custom-packages") (require 'screenshot) (require 'private) #+end_src ** Custom functions :PROPERTIES: :ID: 0888b2db-9a0d-463d-89ad-371fcbfa0473 :END: *** Update DOOM Emacs init.el file :PROPERTIES: :ID: af485cc4-be52-4bb4-889d-7de8bea1ed66 :END: This function brings up a comparison between the current ~init.el~ file and the example file (~templates/init.example.el~). Very useful for upgrading manually. More info here: https://github.com/doomemacs/doomemacs/issues/581#issuecomment-645448095 #+begin_src emacs-lisp (defun my/ediff-init-and-example () "Compare init.el with the example init file." (interactive) (let ((init-file (concat doom-user-dir "init.el")) (example-file (concat doom-emacs-dir "static/init.example.el"))) (if (and (file-exists-p init-file) (file-exists-p example-file)) (ediff-files init-file example-file) (message "Cannot find init.el or example file")))) (define-key! help-map "di" #'my/ediff-init-and-example) #+end_src *** HTTP Statuses :PROPERTIES: :ID: 3fa9d843-f163-4f04-8129-918fb57603a4 :END: This is a custom helm command that displays all the HTTP status codes with their descriptions. As a developer working with web APIs, I frequently need to reference these codes. This function provides a quick, searchable reference without leaving Emacs or disrupting my workflow. Usage: =M-x helm-httpstatus= or through the applications menu with =SPC a h= #+begin_src emacs-lisp (defvar helm-httpstatus-source '((name . "HTTP STATUS") (candidates . (("100 Continue") ("101 Switching Protocols") ("102 Processing") ("200 OK") ("201 Created") ("202 Accepted") ("203 Non-Authoritative Information") ("204 No Content") ("205 Reset Content") ("206 Partial Content") ("207 Multi-Status") ("208 Already Reported") ("300 Multiple Choices") ("301 Moved Permanently") ("302 Found") ("303 See Other") ("304 Not Modified") ("305 Use Proxy") ("307 Temporary Redirect") ("400 Bad Request") ("401 Unauthorized") ("402 Payment Required") ("403 Forbidden") ("404 Not Found") ("405 Method Not Allowed") ("406 Not Acceptable") ("407 Proxy Authentication Required") ("408 Request Timeout") ("409 Conflict") ("410 Gone") ("411 Length Required") ("412 Precondition Failed") ("413 Request Entity Too Large") ("414 Request-URI Too Large") ("415 Unsupported Media Type") ("416 Request Range Not Satisfiable") ("417 Expectation Failed") ("418 I'm a teapot") ("421 Misdirected Request") ("422 Unprocessable Entity") ("423 Locked") ("424 Failed Dependency") ("425 No code") ("426 Upgrade Required") ("428 Precondition Required") ("429 Too Many Requests") ("431 Request Header Fields Too Large") ("449 Retry with") ("500 Internal Server Error") ("501 Not Implemented") ("502 Bad Gateway") ("503 Service Unavailable") ("504 Gateway Timeout") ("505 HTTP Version Not Supported") ("506 Variant Also Negotiates") ("507 Insufficient Storage") ("509 Bandwidth Limit Exceeded") ("510 Not Extended") ("511 Network Authentication Required"))) (action . message))) (defun helm-httpstatus () (interactive) (helm-other-buffer '(helm-httpstatus-source) "*helm httpstatus*")) #+end_src *** Convert HTML to org :PROPERTIES: :ID: b81dff7f-9bc5-4601-97fe-6c2b9e78366c :END: This function converts clipboard contents from HTML to Org format and then pastes (yanks) the result. It's extremely useful when researching online and wanting to capture formatted content directly into my org notes without losing structure. Dependencies: - ~pandoc~ for the format conversion - ~xclip~ for clipboard access Usage: Press F4 in any org-mode buffer to convert and paste HTML from clipboard #+begin_src emacs-lisp (defun my/html2org-clipboard () "Convert HTML in clipboard to Org format and paste it." (interactive) (condition-case err (progn (kill-new (shell-command-to-string "timeout 1 xclip -selection clipboard -o -t text/html | pandoc -f html -t json | pandoc -f json -t org --wrap=none")) (yank) (message "Pasted HTML in org")) (error (message "Error converting HTML to Org: %s" (error-message-string err))))) (after! org (define-key org-mode-map (kbd "") 'my/html2org-clipboard)) #+end_src *** My own menu :PROPERTIES: :ID: 60a0316f-8bb8-40fe-af45-e42cdb6da60a :END: This is a custom menu for my own functions #+begin_src emacs-lisp (map! :leader (:prefix-map ("a" . "applications") :desc "HTTP Status cheatsheet" "h" #'helm-httpstatus) (:prefix-map ("ao" . "org") :desc "Org focus Lazer" "l" #'org-focus-lazer :desc "Org focus private" "p" #'org-focus-private :desc "Org focus all" "a" #'org-focus-all )) #+end_src * Misc :PROPERTIES: :ID: b57fe5fe-18ce-4215-ba94-8deee3a2b64f :END: ** Clipmon :PROPERTIES: :ID: 7b6776af-f357-4f87-9850-4eae4f8daa76 :END: Clipmon serves as my clipboard manager within Emacs. I chose it over alternatives like ~helm-clipboard~ because it offers better integration with my workflow and provides automatic monitoring of clipboard changes. This allows me to maintain a history of copied text without manual intervention. The configuration below sets up Clipmon to check the clipboard every second and makes the kill ring accessible through M-y with helm integration. #+begin_src emacs-lisp (require 'clipmon) (after! clipmon (global-set-key (kbd "M-y") 'helm-show-kill-ring) (add-to-list 'after-init-hook 'clipmon-mode-start) (defadvice clipmon--on-clipboard-change (around stop-clipboard-parsing activate) (let ((interprogram-cut-function nil)) ad-do-it)) (setq clipmon-timer-interval 1)) #+end_src ** Git :PROPERTIES: :ID: 51176440-f985-4c90-94a7-bed48286272c :END: *** Set ~delta~ as the default magit diff :PROPERTIES: :ID: fa6dc3cb-50d7-49cd-96cb-e91a122b1316 :END: #+begin_src emacs-lisp (add-hook 'magit-mode-hook (lambda () (magit-delta-mode +1))) #+end_src *** Accept pre-commit messages when creating git commits with magit-gptcommit mode :PROPERTIES: :ID: 2c8ead63-0929-4f52-9816-85d8e24b8123 :END: #+begin_src emacs-lisp (defun my/magit-gptcommit-commit-accept-wrapper (orig-fun &rest args) "Wrapper for magit-gptcommit-commit-accept to preserve original message." (when-let ((buf (magit-commit-message-buffer))) (with-current-buffer buf (let ((orig-message (string-trim-right (or (git-commit-buffer-message) "") "\n$"))) (apply orig-fun args) (unless (string-empty-p orig-message) (save-excursion (goto-char (point-min)) (insert orig-message))))))) (advice-add 'magit-gptcommit-commit-accept :around #'my/magit-gptcommit-commit-accept-wrapper) #+end_src ** LLM :PROPERTIES: :ID: 0a32d2a9-2156-42a3-90f7-419ac1a25496 :END: This section configures various AI assistants and Large Language Model integrations. These tools augment my workflow by providing code suggestions, helping with documentation, and automating repetitive tasks like writing commit messages. I use a combination of local models (via Ollama) and cloud services (OpenAI, Anthropic) depending on the task requirements and privacy considerations: - GitHub Copilot: For real-time code suggestions while typing - ChatGPT Shell: For general programming assistance and problem-solving - Magit GPT: For automatically generating meaningful commit messages - Forge LLM: For generating PR descriptions - Aider: For more complex code generation and refactoring tasks Each tool has specific strengths, and I've configured them to complement each other in my development workflow. The API keys are stored in a separate private.el file for security. *** LLM Menu :PROPERTIES: :ID: 3edab1be-80d2-418a-9b1f-8fba752093d3 :END: This section defines a convenient LLM command menu bound to =SPC l= (for "LLMs") using Doom Emacs' =map!= macro. The idea is to make it super fast to launch the most common AI-powered tools I use without resorting to =M-x= or remembering long command names. Each submenu entry corresponds to a different assistant or LLM feature: - =SPC l a=: Opens the Aidermacs transient menu. This is where I do more involved interactions like editing files or running multi-step workflows with Aider. - =SPC l c=: Opens the ChatGPT Shell transient. Great for quick conversations, answering questions, or doing ad-hoc debugging. - =SPC l f=: Calls =my/set-forge-llm-provider= to switch the active provider for Forge LLM. Useful when I want to use a different model (e.g. Gemini vs Claude) for generating pull request descriptions. The menu is intentionally kept short and memorable, focused on the LLM features I use most often. I can always expand it later if new tools join the lineup. #+begin_src emacs-lisp (map! :leader (:prefix-map ("l" . "LLMs") :desc "Aidermacs" "a" #'aidermacs-transient-menu :desc "ChatGPT Shell" "c" #'chatgpt-shell-transient :desc "Set Forge LLM Provider" "f" #'my/set-forge-llm-provider)) #+end_src *** ChatGPT Shell :PROPERTIES: :ID: 9bdfbd96-deec-4335-8d2c-77fff0283708 :END: This config block sets up =chatgpt-shell=, a shell-like interface to interact with various LLM providers (OpenAI, Gemini, Anthropic) directly inside Emacs. It’s great for general-purpose LLM queries, quick code snippets, debugging help, or asking questions in natural language. Key settings: - =chatgpt-shell-model-version=: Selects the default model. In this case, it's set to =gemini-2.5-pro-exp=. - =chatgpt-shell-streaming=: Enables real-time streaming of responses (instead of waiting for the full message). - =chatgpt-shell-system-prompt=: Sets the tone and expertise of the assistant. I define it to act like a senior developer who knows every language. - API keys: Each service (OpenAI, Gemini, Anthropic) is configured with its respective variable. These are defined in =private.el= for security and loaded into this config. This setup gives me flexibility to work with multiple providers while maintaining a consistent shell-style UX inside Emacs. #+begin_src emacs-lisp (setq chatgpt-shell-model-version "gemini-2.5-pro-exp") (setq chatgpt-shell-streaming "t") (setq chatgpt-shell-system-prompt "You are a senior developer knowledgeable in every programming language") (setq chatgpt-shell-openai-key openai-key) (setq chatgpt-shell-google-key gemini-key) (setq chatgpt-shell-anthropic-key anthropic-key) (setq dall-e-shell-openai-key openai-key) #+end_src *** Magit GPT :PROPERTIES: :ID: 3f720f16-b7a3-4127-81e9-87d849827639 :END: This section configures =magit-gptcommit=, an integration that uses an LLM to automatically generate well-written Git commit messages based on the current file diffs. I’m using a local model via Ollama (in this case, =gemma3:12b=) to keep everything offline and fast. The key part here is the custom prompt. It's designed to enforce a consistent commit message format, following the Linux kernel commit style. The prompt instructs the model to: - Generate a one-line summary in imperative tense (no prefixes or trailing periods). - Add bullet-style developer comments below the summary, each on a separate line. - Avoid markdown formatting or code fences. This gives me consistent, clean, and useful commit messages without having to write them myself. #+begin_src emacs-lisp (require 'llm-ollama) (setq magit-gptcommit-llm-provider (make-llm-ollama :scheme "http" :host "192.168.0.122" :chat-model "gemma3:12b")) (setq llm-warn-on-nonfree nil) (after! magit (magit-gptcommit-mode 1) (setq magit-gptcommit-prompt "You are an expert programmer crafting a Git commit message. Carefully review the following file diffs as if you had read each line. Your goal is to generate a commit message that follows the kernel Git commit style guide. SUMMARY INSTRUCTIONS: - Write a one-line summary of the change, no more than 50 characters. - Use the imperative tense (for example, use 'Improve logging output' instead of 'Improved logging' or 'Improves logging'). - Do not include prefixes like Fix:, Feat:, or Chore: at the beginning of the summary. - The summary must not end with a period. - Ensure the summary reflects a single, specific, and cohesive purpose. COMMENT INSTRUCTIONS: - After the summary, write concise developer-facing comments explaining the commit. - Each comment must be on its own line and prefixed with '-'. - Each comment must end with a period. - Do not include any paragraphs, introductions, or extra explanations. - Do not use backticks (`) anywhere in the summary or comments. - Do not use Markdown formatting (e.g., *, **, #, _, or inline code). THE FILE DIFFS: %s Now, write the commit message in this exact format: - comment1 - comment2 - commentN") (magit-gptcommit-status-buffer-setup)) #+end_src *** Forge LLM :PROPERTIES: :ID: 51b0f8e3-68b3-46af-91d4-a9b87b1e6b94 :END: This section sets up Forge LLM to work with multiple providers (Gemini, Claude, and Qwen via OpenRouter), allowing me to dynamically choose which model to use with ~M-x my/set-forge-llm-provider~ or ~SPC l f~. This gives me the flexibility to pick the most appropriate model depending on the context. For instance, using Claude for more coherent long-form reasoning or Gemini because it's free. The default provider is set to Gemini, but I can call ~my/set-forge-llm-provider~ at any time to switch providers on the fly. The selected LLM is then used by Forge LLM when I run the pull request generation. The ~forge-llm-max-diff-size~ is set to ~nil~ to avoid truncating large diffs, ensuring I get full context when generating PR descriptions. #+begin_src emacs-lisp (require 'forge-llm) (require 'llm-gemini) (require 'llm-claude) (require 'llm-openai) (defun my/set-forge-llm-provider (provider) "Set the Forge LLM provider dynamically." (interactive (list (completing-read "Choose LLM: " '("Gemini" "Claude" "Qwen")))) (setq forge-llm-llm-provider (pcase provider ("Gemini" (make-llm-gemini :key gemini-key :chat-model "gemini-2.5-pro-exp-03-25")) ("Claude" (make-llm-claude :key anthropic-key :chat-model "claude-3-7-sonnet-latest")) ("Qwen" (make-llm-openai-compatible :url "https://openrouter.ai/api/v1" :chat-model "qwen/qwen3-235b-a22b" :key openrouter-api-key)))) (message "Forge LLM provider set to %s" provider)) (setq forge-llm-llm-provider (make-llm-gemini :key gemini-key :chat-model "gemini-2.5-pro-exp-03-25")) (forge-llm-setup) (setq forge-llm-max-diff-size nil) #+end_src *** Github Copilot :PROPERTIES: :ID: 7f88ce20-846c-47e4-aeed-d853212c9db5 :END: This section enables GitHub Copilot for real-time AI-assisted code completions inside Emacs. The =prog-mode-hook= ensures Copilot is active in all programming buffers. The keybindings are also customized to improve the editing experience: - == and =TAB=: Accept the current Copilot suggestion. - =C-TAB=: Accept the suggestion word by word (helpful for partial completions or fine-tuning). This setup makes Copilot feel more natural and responsive while coding, without getting in the way of other completions or keybindings. #+begin_src emacs-lisp (require 'copilot) (after! copilot (add-hook 'prog-mode-hook #'copilot-mode) (map! :map copilot-completion-map "" #'copilot-accept-completion "TAB" #'copilot-accept-completion "C-TAB" #'copilot-accept-completion-by-word "C-" #'copilot-accept-completion-by-word)) #+end_src *** Aider :PROPERTIES: :ID: 42318f75-3a25-44ad-bfeb-d83338045385 :END: This section configures Aider (via Aidermacs), a powerful AI assistant focused on code editing, refactoring, and multi-file interactions. It’s perfect for large or complex changes where context and iteration matter more than quick completions. Here’s a breakdown of the config: - API keys are loaded into the environment from variables defined in =private.el=, keeping them secure and out of version control. - =aidermacs-use-architect-mode=: Enables enhanced mode with structured input/output formatting. - =aidermacs-default-model=: Sets Gemini as the default model, but this can be switched easily. - =aidermacs-auto-commits=: Disabled to avoid unintended Git changes during a session. - =aidermacs-backend=: Uses =vterm= as the terminal backend for smoother interaction. - =aidermacs-vterm-multiline-newline-key=: Custom binding to insert newlines in vterm when chatting with the model. - =aidermacs-extra-args=: Adds extra CLI flags for Aider sessions to fine-tune the UX (streaming, dark mode, Vim-style prompts, etc.). This setup gives me a highly customizable and scriptable interface for working on larger edits with LLMs, all from within Emacs. #+begin_src emacs-lisp (after! aidermacs ;; Set API keys (setenv "ANTHROPIC_API_KEY" anthropic-key) (setenv "OPENAI_API_KEY" openai-key) (setenv "GEMINI_API_KEY" gemini-key) (setenv "OLLAMA_API_BASE" ollama-api-base) (setenv "OPENROUTER_API_KEY" openrouter-api-key) ;; General settings (setq aidermacs-use-architect-mode t) (setq aidermacs-default-model "gemini/gemini-2.5-pro-exp-03-25") (setq aidermacs-auto-commits nil) (setq aidermacs-backend 'vterm) (setq aidermacs-vterm-multiline-newline-key "S-") (add-to-list 'aidermacs-extra-args "--no-gitignore --chat-mode ask --no-auto-commits --cache-prompts --dark-mode --pretty --stream --vim --cache-keepalive-pings 2 --no-show-model-warnings")) #+end_src ** Others :PROPERTIES: :ID: ccd2e4f2-d58d-4fd3-8d79-1ccd41719122 :END: *** PlantUML :PROPERTIES: :ID: 87ed3201-3df7-4ee1-a4ce-4fe8312f9d08 :END: #+begin_src emacs-lisp (setq plantuml-executable-path "/usr/bin/plantuml") (setq plantuml-default-exec-mode 'executable) (setq org-plantuml-exec-mode 'plantuml) (setq plantuml-server-url 'nil) (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t))) (add-to-list 'auto-mode-alist '("\\.plantuml\\'" . plantuml-mode)) (setq org-babel-default-header-args:plantuml '((:results . "verbatim") (:exports . "results") (:cache . "no"))) (after! org (add-to-list 'org-src-lang-modes '("plantuml" . plantuml))) #+end_src