Spacemacs tangled user configuration

Table of Contents

1. Introduction

This is a org file where its code snippets will be read by spacemacs for user-init and user-config. It is inspired by spacemacs.org.

2. user-init

All code snippets under this section will be added to dotspacemacs//user-init function, which is

Initialization function for user code.

It is called immediately after dotspacemacs/init, before layer configuration executes. This function is mostly useful for variables that need to be set before packages are loaded. If you are unsure, you should try in setting them in `dotspacemacs/user-config' first.

2.1. Locale

(setq system-time-locale "C")

2.2. Theme

(setq-default dotspacemacs-themes '(
                                    doom-one
                                    doom-monokai-pro
                                    spacemacs-dark
                                    doom-zenburn))

2.3. ROS

(defun spacemacs/update-ros-envs ()
  "Update all environment variables in `spacemacs-ignored-environment-variables'
from their values currently sourced in the shell environment (e.g. .bashrc)"
  (interactive)
  (setq exec-path-from-shell-check-startup-files nil)
  (exec-path-from-shell-copy-envs spacemacs-ignored-environment-variables)
  (message "ROS environment copied successfully from shell"))


;; Ignore any ROS environment variables since they might change depending
;; on which catkin workspace is used. When a new catkin workspace is chosen
;; call `spacemacs/update-ros-envs' to update theses envs accordingly
(setq-default spacemacs-ignored-environment-variables '("ROS_IP"
                                                        "PYTHONPATH"
                                                        "CMAKE_PREFIX_PATH"
                                                        "ROS_MASTER_URI"
                                                        "ROS_PACKAGE_PATH"
                                                        "ROSLISP_PACKAGE_DIRECTORIES"
                                                        "PKG_CONFIG_PATH"
                                                        "LD_LIBRARY_PATH"))

2.4. Shell

Set shell to be bash explicitly because my default shell fish does not work along with spacemacs.

(setq-default shell-file-name "/bin/bash")

2.5. Layers

2.5.1. groovy

(setq default-groovy-lsp-jar-path "~/.spacemacs.d/groovy-language-server-all.jar")
(if (file-exists-p default-groovy-lsp-jar-path)
    (setq groovy-lsp-jar-path default-groovy-lsp-jar-path)
  (message (concat default-groovy-lsp-jar-path " does not exist")))

2.6. Workarounds

2.6.1. Workaround for unsigned packages

(setq gnutls-algorithm-priority "NORMAL:-VERS-TLS1.3")

2.6.2. Acknowledge org-roam-v2

(setq org-roam-v2-ack t)

3. user-config

All code snippets under this section will be added to dotspacemacs//user-config function, which is

Configuration function for user code.

This function is called at the very end of Spacemacs initialization after layers configuration. You are free to put any user code.

3.1. Performance

3.1.1. gcmh (Garbage Collection Magic Hack)

Use idle-time garbage collection instead of a static threshold. Collects garbage when Emacs is idle, keeping GC threshold high during active use.

(use-package gcmh
  :config
  (setq gcmh-idle-delay 'auto       ; default: auto (based on `gcmh-auto-idle-delay-factor')
        gcmh-auto-idle-delay-factor 10
        gcmh-high-cons-threshold (* 128 1024 1024))  ; 128MB during active use
  (gcmh-mode 1))

3.2. AI assistant

3.2.1. Aider

(spacemacs/set-leader-keys "aa" 'aidermacs-transient-menu)

(setq aidermacs-default-model "github_copilot/claude-sonnet-4.5")
(setq aidermacs-weak-model "github_copilot/gpt-5-mini")
(setq aidermacs-backend 'vterm)
(setq aidermacs-default-chat-mode 'code)

3.2.2. copilot

(with-eval-after-load 'copilot-chat
  (setq copilot-chat-default-model "gpt-5-mini"))

;; Keybindings (available immediately, autoload copilot-chat when used)
(spacemacs/declare-prefix "ai" "AI")
(spacemacs/set-leader-keys "aic" 'copilot-chat)
(spacemacs/set-leader-keys "aif" 'copilot-chat-fix)
(spacemacs/set-leader-keys "air" 'copilot-chat-review)
(spacemacs/set-leader-keys "aie" 'copilot-chat-explain)
(spacemacs/set-leader-keys "aio" 'copilot-chat-optimize)
(spacemacs/set-leader-keys "aid" 'copilot-chat-doc)
(spacemacs/set-leader-keys "ait" 'copilot-chat-test)
(spacemacs/set-leader-keys "aiq" 'copilot-chat-quotas)

3.3. IDE config

3.3.1. isort, compatible with black

(setq py-isort-options '("--profile" "black" "--filename" buffer-file-name))

3.3.2. Add new line when a file is about to be saved

(setq require-final-newline t)

3.3.3. Apply ANSI Color codes for buffer and region

(defun apply-ansi-color-codes-to-region (beginning end)
  "Apply ANSI color codes to the selected region."
  (interactive "r")
  (ansi-color-apply-on-region beginning end))

(defun apply-ansi-color-codes-to-buffer ()
  "Apply ANSI color codes to the whole buffer."
  (interactive)
  (apply-ansi-color-codes-to-region (point-min) (point-max)))

3.3.4. Disable auto-indent in C/C++ mode after typing ::

;; Disable electric indentation in C and C++ modes
(defun my-disable-electric-indent-mode ()
  (setq-local c-electric-flag nil))

;; Hook this function to C mode and C++ mode
(add-hook 'c-mode-hook 'my-disable-electric-indent-mode)
(add-hook 'c++-mode-hook 'my-disable-electric-indent-mode)

3.3.5. MacOS

; Use Command key as meta in emacs
(setq mac-option-key-is-meta nil
      mac-command-key-is-meta t
      mac-command-modifier 'meta
      mac-option-modifier 'none)

3.3.6. Prevent using UI dialogs for prompts

(setq use-dialog-box nil)

3.3.7. ccls

(with-eval-after-load 'ccls
  (setq ccls-root-files (add-to-list 'ccls-root-files "build/compile_commands.json" t))
  (setq ccls-sem-highlight-method 'font-lock)
  (setq ccls-initialization-options
        (list :cache (list :directory (file-truename (concat (file-name-as-directory spacemacs-cache-directory) ".ccls-cache")))
              :compilationDatabaseDirectory "build"))
  ;; Only set the specific ccls path if it exists
  (when (file-exists-p "~/.spacemacs.d/ccls/Release/ccls")
    (setq ccls-executable (file-truename "~/.spacemacs.d/ccls/Release/ccls"))))

3.3.8. devcontainer

Enable devcontainer-mode globally so that M-x compile (SPC c c) runs inside the devcontainer when the project has a .devcontainer/devcontainer.json. The devcontainer CLI is installed outside the default PATH, so we add its directory to exec-path.

  ;; Make the devcontainer CLI discoverable
  (let ((devcontainer-bin-dir (expand-file-name "~/.devcontainers/bin")))
    (when (file-directory-p devcontainer-bin-dir)
      (add-to-list 'exec-path devcontainer-bin-dir)))

  ;; Enable devcontainer-mode globally — advises `compilation-start'
  ;; to run inside the container when a devcontainer.json is present
  (devcontainer-mode 1)

  ;; Use vterm for devcontainer-term
  (with-eval-after-load 'devcontainer
    (defun devcontainer-vterm (command)
      "Open a vterm buffer running COMMAND inside the devcontainer.
Creates a new terminal each time; buffers are named with incrementing numbers."
      (require 'vterm)
      (let* ((base-name (format "*devcontainer-term: %s*"
                                (project-name (project-current))))
             (buf (generate-new-buffer base-name)))
        (with-current-buffer buf
          (let ((vterm-shell command))
            (vterm-mode)))
        (pop-to-buffer buf)))
    (setq devcontainer-term-function #'devcontainer-vterm)

    ;; Fix: devcontainer.el does not implement ${localEnv:VAR}
    ;; interpolation, which causes errors when remoteEnv references
    ;; host environment variables (e.g. DISPLAY).
    (advice-add 'devcontainer--lookup-variable :around
                (lambda (orig-fn match)
                  "Add ${localEnv:VAR} support to devcontainer variable interpolation."
                  (or (funcall orig-fn match)
                      (save-match-data
                        (when (string-match "\\${localEnv:\\([^}]+\\)}" match)
                          (or (getenv (match-string 1 match)) "")))))))

  ;; Keybindings under SPC d
  (spacemacs/declare-prefix "DD" "devcontainer")
  (spacemacs/set-leader-keys "DDu" 'devcontainer-up)
  (spacemacs/set-leader-keys "DDr" 'devcontainer-restart)
  (spacemacs/set-leader-keys "DDR" 'devcontainer-rebuild-and-restart)
  (spacemacs/set-leader-keys "DDt" 'devcontainer-term)
  (spacemacs/set-leader-keys "DDx" 'devcontainer-execute-command)
  (spacemacs/set-leader-keys "DDk" 'devcontainer-kill-container)

3.3.9. cmake-ide

;; C++ build dir setting
(put 'cmake-ide-dir 'safe-local-variable 'stringp)
(put 'cmake-ide-make-command 'safe-local-variable 'stringp)
(put 'cmake-ide-cmake-args 'safe-local-variable 'stringp)

;; Configure neocmakelsp
(with-eval-after-load 'lsp-mode
  ;; Note: 'stdio' should not have dashes
  (lsp-register-client
   (make-lsp-client :new-connection (lsp-stdio-connection '("neocmakelsp" "stdio"))
                    :activation-fn (lsp-activate-on "cmake")
                    :server-id 'neocmakelsp)))

(add-hook 'cmake-mode-hook #'lsp)

3.3.10. company

(with-eval-after-load 'company
  (define-key company-active-map (kbd "M-n") nil)
  (define-key company-active-map (kbd "M-p") nil)
  (define-key company-active-map (kbd "C-j") 'company-select-next)
  (define-key company-active-map (kbd "C-k") 'company-select-previous))

3.3.11. lsp-ui

(with-eval-after-load 'lsp-ui
  (define-key lsp-ui-peek-mode-map (kbd "C-j") 'lsp-ui-peek--select-next)
  (define-key lsp-ui-peek-mode-map (kbd "j") 'lsp-ui-peek--select-next)
  (define-key lsp-ui-peek-mode-map (kbd "C-k") 'lsp-ui-peek--select-prev)
  (define-key lsp-ui-peek-mode-map (kbd "k") 'lsp-ui-peek--select-prev))

3.3.12. popup

Use evil keybindings for selecting items in popup menus.

(with-eval-after-load 'popup
  (define-key popup-menu-keymap (kbd "C-j") 'popup-next)
  (define-key popup-menu-keymap (kbd "C-k") 'popup-previous))

3.3.13. dap

(add-hook 'dap-stopped-hook
          (lambda (arg) (call-interactively #'dap-hydra)))
(add-hook 'dap-stopped-hook
          (lambda (arg) (call-interactively #'dap-hydra)))

3.3.14. flycheck

(with-eval-after-load 'flycheck
  (setq flycheck-check-syntax-automatically '(save
                                              idle-buffer-switch
                                              mode-enabled)))

3.3.15. Indentation for web development

(defun setup-web-dev-indent (n)
  ;; web development
  (setq coffee-tab-width n) ; coffeescript
  (setq javascript-indent-level n) ; javascript-mode
  (setq js-indent-level n) ; js-mode
  (setq js2-basic-offset n) ; js2-mode, in latest js2-mode, it's alias of js-indent-level
  (setq web-mode-markup-indent-offset n) ; web-mode, html tag in html file
  (setq web-mode-css-indent-offset n) ; web-mode, css in html file
  (setq web-mode-code-indent-offset n) ; web-mode, js code in html file
  (setq css-indent-offset n) ; css-mode
  )
(setup-web-dev-indent 2)

3.3.16. groovy (Jenkinsfile)

(setq groovy-indent-offset 2)

3.3.17. plantuml

(when (fboundp 'plantuml-mode)
  (require 'org-src)
  ;; Enable plantuml-mode for all *.pu files by default
  (add-to-list 'auto-mode-alist '("\\.pu\\'" . plantuml-mode))
  (setq org-plantuml-jar-path plantuml-jar-path)
  (add-to-list 'org-src-lang-modes '("plantuml" . plantuml))
  (org-babel-do-load-languages 'org-babel-load-languages '((plantuml . t)))
  (setq plantuml-svg-background "white")
  (setq plantuml-indent-level 2)
  )

3.3.18. nvm

;; TODO: use the default version instead of hard-coding the specific version
(condition-case err
    (nvm-use "20")
  (error (message "Could not initialize nvm for emacs. %s" (error-message-string err))))

3.3.19. conf-mode

(add-to-list 'auto-mode-alist '("\\.eds\\'" . conf-mode))
(add-to-list 'auto-mode-alist '("\\.dcf\\'" . conf-mode))

3.3.20. Rust

3.3.20.1. Allow user input in the rust-run command
(spacemacs/set-leader-keys-for-major-mode 'rustic-mode "cx" 'rustic-cargo-run-with-user-input)
(defun rustic-cargo-run-with-user-input ()
  "Build and run Rust code."
  (interactive)
  (rustic-cargo-run)
  (let (
        (orig-win (selected-window))
        (run-win (display-buffer (get-buffer "*cargo-run*") nil 'visible))
        )
    (select-window run-win)
    (comint-mode)
    (read-only-mode 0)
    (select-window orig-win)
    )
  )

3.3.21. scad-mode

(use-package scad-mode
  :load-path "~/.spacemacs.d/private/scad-mode")
(add-hook 'scad-mode-hook 'flymake-mode-on)
(with-eval-after-load 'scad-mode
  (define-key scad-preview-mode-map (kbd "C-h") 'scad-preview-rotate-z-)
  (define-key scad-preview-mode-map (kbd "C-l") 'scad-preview-rotate-z+)
  (define-key scad-preview-mode-map (kbd "C-k") 'scad-preview-rotate-x-)
  (define-key scad-preview-mode-map (kbd "C-j") 'scad-preview-rotate-x+)
  (define-key scad-preview-mode-map (kbd "M-h") 'scad-preview-distance-)
  (define-key scad-preview-mode-map (kbd "M-l") 'scad-preview-distance+)
  (define-key scad-preview-mode-map (kbd "M-k") 'scad-preview-translate-z+)
  (define-key scad-preview-mode-map (kbd "M-j") 'scad-preview-translate-z-))

3.3.22. Style for linux kernel development

;; Linux kernel development
(defun c-lineup-arglist-tabs-only (ignored)
  "Line up argument lists by tabs, not spaces"
  (let* ((anchor (c-langelem-pos c-syntactic-element))
         (column (c-langelem-2nd-pos c-syntactic-element))
         (offset (- (1+ column) anchor))
         (steps (floor offset c-basic-offset)))
    (* (max steps 1)
       c-basic-offset)))

(add-hook 'c-mode-common-hook
          (lambda ()
            ;; Add kernel style
            (c-add-style
             "linux-tabs-only"
             '("linux" (c-offsets-alist
                        (arglist-cont-nonempty
                         c-lineup-gcc-asm-reg
                         c-lineup-arglist-tabs-only))))))
(add-hook 'c-mode-hook
          (lambda ()
            (let ((filename (buffer-file-name)))
              ;; Enable kernel mode for the appropriate files
              (when (and filename
                         ;; TODO: avoid the harded coded path
                         (string-match (expand-file-name "~/Dev/kernels")
                                       filename))
                (setq indent-tabs-mode t)
                (setq show-trailing-whitespace t)
                (c-set-style "linux-tabs-only")))))

3.3.23. copilot

(setq copilot-node-executable (executable-find "node"))

(with-eval-after-load 'company
  ;; disable inline previews
  (delq 'company-preview-if-just-one-frontend company-frontends))

;; Keybindings
(with-eval-after-load 'copilot
  (define-key copilot-completion-map (kbd "<tab>") 'copilot-accept-completion)
  (define-key copilot-completion-map (kbd "TAB") 'copilot-accept-completion)
  (define-key copilot-completion-map (kbd "C-TAB") 'copilot-accept-completion-by-word)
  (define-key copilot-completion-map (kbd "C-<tab>") 'copilot-accept-completion-by-word))

;; Enable copilot mode in programming modes, markdown-mode and org-mode
;; Only activate hooks if copilot is installed
(with-eval-after-load 'copilot
  (add-hook 'prog-mode-hook 'copilot-mode)
  (add-hook 'markdown-mode-hook 'copilot-mode)
  (add-hook 'org-mode-hook 'copilot-mode)

  ;; Disable the warning message
  (add-hook 'copilot-mode-hook (lambda ()
                                 (setq-local copilot--indent-warning-printed-p t))))

3.3.24. tramp + clang-format

clang-format does not work properly when editing a file on a remote host or in a docker container with tramp. See the issue here: https://github.com/kljohann/clang-format.el/issues/5

Here upon clang-format-region call, we first check if the file is on a remote host or in a docker container.

  • If not, we call clang-format-region as usual.
  • If yes, we first check if a file with the same path exists on the local disk.
    • If yes, we assume it as the input file name for clang-format-region.
    • If not, we assume the input file is under the $HOME directory.

Depending on where the input file is assumed to be, clang-format will find the .clang-format file for the formatting in a dominant parent directory of the assumed input file path.

(defun tramp-aware-clang-format (orig-fun start end &optional style assume-file-name)
  (unless assume-file-name
    (setq assume-file-name
          (if (file-remote-p buffer-file-name)
              (let ((maybe-existing-local-buffer-file-name (replace-regexp-in-string "/docker:[^:]+:" "" buffer-file-name)))
                ;; If file `maybe-existing-local-buffer-file-name' exists on local disk, use it.
                (if (file-exists-p maybe-existing-local-buffer-file-name)
                    maybe-existing-local-buffer-file-name
                  ;; Otherwise, use `buffer-file-name' as if it is under the $HOME directory.
                  (concat (getenv "HOME") "/" (file-name-nondirectory buffer-file-name))))
            buffer-file-name)))
  (message "assume-file-name: %s" assume-file-name)
  (apply orig-fun (list start end style assume-file-name)))

(advice-add 'clang-format-region :around #'tramp-aware-clang-format)

3.3.25. Get current branch name in magit

(defun magit-add-current-branch-name-to-kill-ring ()
  "Show the current branch in the echo-area and add it to the `kill-ring'."
  (interactive)
  (let ((branch (magit-get-current-branch)))
    (if branch
        (progn (kill-new branch)
               (message "%s" branch))
      (user-error "There is not current branch"))))

; TODO: Move this keybinding from "Checkout" to "Do" section
(with-eval-after-load 'magit
  (transient-insert-suffix 'magit-branch "b"
    '("k" "copy branch name" magit-add-current-branch-name-to-kill-ring)))

3.4. Miscellaneous

3.4.1. popper

Tame ephemeral popup buffers (help, compilation, shells, REPLs). Toggle with C-`, cycle with M-`, promote/demote with C-M-`.

(use-package popper
  :bind (("C-`"   . popper-toggle)
         ("M-`"   . popper-cycle)
         ("C-M-`" . popper-toggle-type))
  :init
  (setq popper-reference-buffers
        '("\\*Messages\\*"
          "\\*Warnings\\*"
          "\\*Backtrace\\*"
          "\\*Compile-Log\\*"
          "Output\\*$"
          "\\*Async Shell Command\\*"
          help-mode
          helpful-mode
          compilation-mode
          flycheck-error-list-mode
          grep-mode
          occur-mode
          "^\\*eshell.*\\*$" eshell-mode
          "^\\*shell.*\\*$"  shell-mode
          "^\\*term.*\\*$"   term-mode
          "^\\*vterm.*\\*$"  vterm-mode))
  (setq popper-group-function #'popper-group-by-projectile)
  (popper-mode +1)
  (popper-echo-mode +1))

3.4.2. which-key-posframe

Show which-key popup as a floating frame centered on screen (GUI only).

(use-package which-key-posframe
  :after which-key
  :config
  (setq which-key-posframe-poshandler 'posframe-poshandler-frame-center
        which-key-posframe-border-width 2)
  (when (display-graphic-p)
    (which-key-posframe-mode 1))
  (add-hook 'after-make-frame-functions
            (lambda (frame)
              (with-selected-frame frame
                (if (display-graphic-p)
                    (which-key-posframe-mode 1)
                  (which-key-posframe-mode -1))))))

3.4.3. ultra-scroll

(use-package ultra-scroll
:init
(setq scroll-conservatively 101 ; important!
      scroll-margin 0)
:config
(ultra-scroll-mode 1))

3.4.4. spacious-padding

Add breathing room around windows, mode lines, and frame borders.

Note: The :right-divider-width 20 setting works together with window-divider-mode. See Window Divider Visibility section for divider color customization.

(use-package spacious-padding
  :config
  (setq spacious-padding-widths
        '( :internal-border-width 10
           :header-line-width 4
           :mode-line-width 4
           :right-divider-width 20
           :scroll-bar-width 8
           :fringe-width 8))
  ;; Only enable in GUI frames
  (when (display-graphic-p)
    (spacious-padding-mode 1))
  ;; Re-check when creating new frames (e.g. emacsclient)
  (add-hook 'after-make-frame-functions
            (lambda (frame)
              (with-selected-frame frame
                (if (display-graphic-p)
                    (spacious-padding-mode 1)
                  (spacious-padding-mode -1))))))

3.4.5. Window Divider Visibility

Window dividers are enabled via window-divider-mode to show pixel-based separators between split windows. By default, spacious-padding makes these invisible (matching background color), but we override this to make them subtly visible.

;; Configure window-divider widths to match spacious-padding
(setq window-divider-default-right-width 20
      window-divider-default-bottom-width 1
      window-divider-default-places 'right-only)

;; Enable window-divider-mode in GUI frames
(when (display-graphic-p)
  (window-divider-mode 1))

;; Make window dividers subtly visible (override spacious-padding's invisible style)
;; Using #3f444a - a subtle gray approximately 10% lighter than doom-one background
(defun my/set-visible-window-dividers ()
  "Set window divider faces to be subtly visible."
  (let ((divider-color "#3f444a"))
    (set-face-foreground 'window-divider divider-color)
    (set-face-foreground 'window-divider-first-pixel divider-color)
    (set-face-foreground 'window-divider-last-pixel divider-color)))

;; Apply after theme loads
(add-hook 'spacemacs-post-theme-change-hook #'my/set-visible-window-dividers)

;; Apply when idle (runs after theme loads at startup)
(run-with-idle-timer 0 nil #'my/set-visible-window-dividers)

;; Re-apply for new frames (emacsclient)
(add-hook 'after-make-frame-functions
          (lambda (frame)
            (with-selected-frame frame
              (when (display-graphic-p)
                (window-divider-mode 1)
                (my/set-visible-window-dividers)))))

3.4.6. No title bar

(add-to-list 'default-frame-alist '(undecorated-round . t))

3.4.7. cursor

; Display Emacs cursor in terminal as it would be in GUI
;; (global-term-cursor-mode)

3.4.8. C-a for increasing number, C-x for descreasing number

(evil-define-key 'normal global-map (kbd "C-a") 'evil-numbers/inc-at-pt)
(evil-define-key 'normal global-map (kbd "C-x") 'evil-numbers/dec-at-pt)

3.4.9. Default python interpreter

(setq python-shell-interpreter (executable-find "python3"))

3.4.10. Disable spacemacs buffer warnings

(setq spacemacs-buffer--warnings nil)

3.4.11. Find this file

Create binding to spacemacs.org file

(defun spacemacs/find-config-file ()
  (interactive)
  (find-file (concat dotspacemacs-directory "/spacemacs.org")))

(spacemacs/set-leader-keys "fec" 'spacemacs/find-config-file)

3.4.12. - for going to the first non-blank position of the previous line

(evil-define-key 'normal global-map (kbd "-") 'evil-previous-line-first-non-blank)

3.4.13. Keybinding for Zoom in / out

(define-key (current-global-map) (kbd "C-+") 'spacemacs/zoom-frm-in)
(define-key (current-global-map) (kbd "C--") 'spacemacs/zoom-frm-out)

3.4.14. Smart quit when pressing SPC q q

If running as a daemon, kill the current frame. Otherwise, prompt to kill Emacs.

(defun my/smart-quit ()
  "Quit Emacs smartly.
If running as a daemon, kill the current frame.
Otherwise, prompt to kill Emacs."
  (interactive)
  (if (daemonp)
      (spacemacs/frame-killer)
    (spacemacs/prompt-kill-emacs)))

(spacemacs/set-leader-keys "qq" 'my/smart-quit)

3.4.15. Make w in vim mode move to end of the word (not stopped by _)

(with-eval-after-load 'evil
  (defalias #'forward-evil-word #'forward-evil-symbol))

3.4.16. Smooth scrolling

;; Scroll one line at a time (less "jumpy" than defaults)
(when (display-graphic-p)
  (setq mouse-wheel-scroll-amount '(1 ((shift) . 1))
        mouse-wheel-progressive-speed nil))
(setq scroll-step 1
      scroll-margin 0
      scroll-conservatively 100000)

3.4.17. Transparency settings

(spacemacs/set-leader-keys "tt" 'spacemacs/toggle-transparency)
(add-hook 'after-make-frame-functions 'spacemacs/enable-transparency)

3.4.18. Turn on xclip-mode

(use-package xclip
  :config (xclip-mode t))

3.4.19. Use windows key as meta key

It is meant to avoid conflicts with i3wm, where I use alt as the meta key.

(setq x-super-keysym 'meta)

3.4.20. Visiting a file uses its truename as the visited-file name

E.g. when visiting a soft/hard link.

(setq find-file-visit-truename t)

3.4.21. Do not autosave undo history

(with-eval-after-load 'undo-tree
  (setq undo-tree-auto-save-history nil))

3.4.22. Native compilation

(when (and (fboundp 'native-comp-available-p)
           (native-comp-available-p))
  (message "Native compilation is available")
  (setq native-comp-async-report-warnings-errors nil))

3.5. org-mode

3.5.1. Do not hide the macro markers

(with-eval-after-load 'org
  (setq org-hide-macro-markers nil))

3.5.2. org-modern

Modern visual styling for org-mode: clean TODO keywords, tables, timestamps, tags, priorities, and heading bullets.

(use-package org-modern
  :hook (org-mode . org-modern-mode)
  :hook (org-agenda-finalize . org-modern-agenda)
  :custom
  ;; Disable org-modern TODO styling to preserve our custom keyword faces
  (org-modern-todo nil)
  (org-modern-done nil)
  ;; Clean table styling
  (org-modern-table t)
  ;; Modern timestamps
  (org-modern-timestamp t)
  ;; Modern tag styling
  (org-modern-tag t)
  ;; Modern priority styling
  (org-modern-priority t)
  ;; Use pretty star bullets for headings
  (org-modern-star '("◉" "○" "◈" "◇" "▸"))
  ;; Hide leading stars (org-modern handles the display)
  (org-modern-hide-stars t)
  ;; Block styling
  (org-modern-block-fringe nil)
  ;; Horizontal rule
  (org-modern-horizontal-rule t))

3.5.3. Export code blocks with current theme

(defun my/org-inline-css-hook (exporter)
  "Insert custom inline css to automatically set the
background of code to whatever theme I'm using's background"
  (when (eq exporter 'html)
    (let* ((my-pre-bg (face-background 'default))
           (my-pre-fg (face-foreground 'default)))
      (setq
       org-html-head-extra
       (concat
        org-html-head-extra
        (format "<style type=\"text/css\">\n pre.src {background-color: %s; color: %s;}</style>\n"
                my-pre-bg my-pre-fg))))))

(add-hook 'org-export-before-processing-hook 'my/org-inline-css-hook)

3.5.4. org-ai

(use-package org-ai
  :ensure t
  :commands (org-ai-mode
             org-ai-global-mode)
  :custom (org-ai-openai-api-token (auth-source-pick-first-password :host "api.openai.com"))
  :init
  (add-hook 'org-mode-hook #'org-ai-mode) ; enable org-ai in org-mode
  :config
  (org-ai-global-mode) ; installs global keybindings on C-c M-a
  (setq org-ai-default-chat-model "gpt-4o")
  (org-ai-install-yasnippets)) ; if you are using yasnippet and want `ai` snippets

3.5.5. org-pomodora

;; Lower the volume of the sounds
(setq org-pomodoro-audio-player "play")
(setq org-pomodoro-finished-sound-args "-v 0.01")
(setq org-pomodoro-long-break-sound-args "-v 0.01")
(setq org-pomodoro-short-break-sound-args "-v 0.01")

3.5.6. org-agenda

(defun scan-new-agenda-files ()
  (interactive)
  (let ((org-dir (expand-file-name "~/org/")))
    (if (file-directory-p org-dir)
        (progn
          (message "Scanning new agenda files...")
          (setq org-agenda-files (directory-files-recursively org-dir "\.org$" nil nil t)))
      (message "Warning: ~/org/ directory does not exist. Skipping agenda files scan."))))

(with-eval-after-load 'org-agenda
  (scan-new-agenda-files)
  (define-key org-agenda-mode-map "m" 'org-agenda-month-view)
  (define-key org-agenda-mode-map "y" 'org-agenda-year-view))

(spacemacs/set-leader-keys "aou" 'scan-new-agenda-files)

;; Add category into the ~org-todo-list~ view
(setq org-agenda-prefix-format
      '((agenda . " %i %-16:c%?-12t% s")
        (todo   . " %i %-16:c ") ;; Added a space here
        (tags   . " %i %-16:c")
        (search . " %i %-16:c")))

;; Add custom agenda view for my dashboard
(setq org-agenda-custom-commands
      '(("d" "My Dashboard"
         ((agenda ""
                  ((org-agenda-span 7)
                   ;; This line fixes the column width (%-16c reserves 16 chars)
                   (org-agenda-prefix-format "  %-16:c%?-12t% s")))
          (todo "UNDER_REVIEW"                   ;; Show what's under review
                ((org-agenda-overriding-header "Under Review")))
          (todo "IN_PROGRESS"                    ;; Show what I'm working on NOW
                ((org-agenda-overriding-header "Current Focus")))
          (todo "TODO"                           ;; Show the rest of the list
                ((org-agenda-overriding-header "Inbox / Backlog")))))))


;; Customize the colors of TODO keywords for better visual distinction
(setq org-todo-keyword-faces
      '(;; Standard Flow
        ("TODO"         . (:foreground "goldenrod" :weight bold))
        ("IN_PROGRESS"  . (:foreground "deep sky blue" :weight bold))
        ("UNDER_REVIEW"  . (:foreground "medium purple" :weight bold))

        ;; Bug Tracking Flow
        ("REPORT"       . (:foreground "indian red" :weight bold))
        ("BUG"          . (:foreground "red" :weight bold))
        ("KNOWNCAUSE"   . (:foreground "orange" :weight bold))
        ("FIXED"        . (:foreground "green" :weight bold))

        ;; Terminal / Inactive States (Muted)
        ("CANCELED"     . (:foreground "dark gray" :strike-through t))
        ("REPORTED"     . (:foreground "cadet blue"))))

3.5.7. org-cv

(use-package ox-awesomecv
  :load-path "~/.spacemacs.d/private/org-cv"
  :init (require 'ox-awesomecv))

3.5.8. org-babel

(with-eval-after-load 'org
  (org-babel-do-load-languages
   'org-babel-load-languages
   '((C . t)
     (python . t)
     (shell . t))))

3.5.9. org-latex

(with-eval-after-load 'ox-latex
  (add-to-list 'org-latex-classes
               '("scrartcl"
                 "\\documentclass[a4paper,11pt]{scrartcl}"
                 ("\\section{%s}" . "\\section*{%s}")
                 ("\\subsection{%s}" . "\\subsection*{%s}")
                 ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                 ("\\paragraph{%s}" . "\\paragraph*{%s}")
                 ("\\subparagraph{%s}" . "\\subparagraph*{%s}"))))

3.5.10. org-journal

(with-eval-after-load 'org-journal
  (setq org-journal-dir "~/org/home/roam/journal/")
  (setq org-journal-date-format "%A, %m/%d/%Y")
  (setq org-journal-file-type 'monthly)
  (setq org-journal-file-format "%Y%m%d.org"))

(spacemacs/set-leader-keys
  "aojj" (lambda () (interactive)
           (org-journal-new-entry nil)))
(spacemacs/declare-prefix "aojj" "journal-home")

3.5.11. org-table

(with-eval-after-load 'org-mode
  (define-key org-mode-map (kbd "C-<tab>") 'org-table-previous-field))

3.5.12. org-todo

(with-eval-after-load 'org
  (let ((capture-template  "* TODO %?\n%U\n%i\n%a"))
    (progn
      (setq org-todo-keywords
            '((sequence "TODO(t)" "IN_PROGRESS" "UNDER_REVIEW" "|" "DONE(d)")
              (sequence "REPORT(r)" "BUG(b)" "KNOWNCAUSE(k)" "|" "FIXED(f)")
              (sequence "|" "INACTIVE(i)" "INCOMPLETE(n)" "CANCELED(c)" "REPORTED(R)")))
      (setq org-capture-templates '(
                                    ("t" "Task" entry (file+headline "~/org/home/tasks.org" "Tasks")
                                     "* TODO %?\n%U\n%i\n%a")
                                    ("e" "Email" entry (file+headline "~/org/home/tasks.org" "Tasks")
                                     "* TODO %? [[mu4e:msgid:%l][Email]]\n%U\n%i")
                                    ))
      (setq org-project-capture-capture-template capture-template))))
(spacemacs/set-leader-keys
  "aoh" (lambda () (interactive) (find-file "~/org/home/tasks.org"))
  "aoc" (lambda () (interactive) (org-capture nil "t"))
  )
(spacemacs/declare-prefix "aoh" "org-capture-show-task")
(spacemacs/declare-prefix "aoc" "org-capture-task")

3.5.13. org-hugo

(spacemacs/set-leader-keys-for-major-mode 'org-mode "Th" 'org-hugo-auto-export-mode)

3.5.14. org-roam

3.5.14.1. Automatically sync the database
(org-roam-db-autosync-mode)
3.5.14.2. Migrate the current buffer from org-roam v1 to v2
(defun org-roam-migrate-current-buffer-v1-to-v2 ()
  (interactive)
  (org-roam-migrate-v1-to-v2))

3.5.15. org-gcal

Sync Google Calendar events into Org Agenda (read-only fetch). Credentials are stored in ~/.authinfo and never committed to the repo. OAuth tokens are GPG-encrypted via plstore.

Setup instructions (one-time, manual):

  1. Create a Google Cloud project, enable Calendar API, create a Desktop OAuth 2.0 client.
  2. In Google Calendar settings, share any secondary account's calendar with your primary Gmail address.
  3. Generate a dedicated GPG key: gpg --full-generate-key (RSA 4096, no expiry).
  4. Note the key ID: gpg --list-secret-keys --keyid-format=long
  5. Add to ~/.authinfo:
    • machine org-gcal-client-id password <client-id>.apps.googleusercontent.com
    • machine org-gcal-client-secret password <client-secret>
    • machine org-gcal-calendar-id-personal password <personal@gmail.com>
    • machine org-gcal-calendar-id-work password <shared-work-calendar-id>
    • machine org-gcal-gpg-key-id password <GPG-long-key-id>
  6. On first fetch, approve OAuth access in the browser. Tokens are saved and reused automatically.
(use-package org-gcal
  :defer t
  :init
  ;; Credentials must be set BEFORE org-gcal.el loads, because it calls
  ;; org-gcal-reload-client-id-secret at load time to register the OAuth client.
  (setq org-gcal-client-id     (auth-source-pick-first-password :host "org-gcal-client-id")
        org-gcal-client-secret (auth-source-pick-first-password :host "org-gcal-client-secret"))
  ;; Use asymmetric GPG key for encrypting the OAuth token store.
  ;; Key ID is read from authinfo so it is never hardcoded in this repo.
  (require 'plstore)
  (let ((gpg-key-id (auth-source-pick-first-password :host "org-gcal-gpg-key-id")))
    (when gpg-key-id
      (add-to-list 'plstore-encrypt-to gpg-key-id)))
  ;; Use default pinentry (GNOME3 GUI) now that gpg-agent is configured with pinentry-gnome3.
  (setq epg-pinentry-mode 'default)
  :config
  ;; Map each calendar ID to its dedicated org file.
  ;; Both files live in ~/org/home/ and are auto-discovered by scan-new-agenda-files.
  (setq org-gcal-fetch-file-alist
        `((,(auth-source-pick-first-password :host "org-gcal-calendar-id-personal")
           . "~/org/home/gcal/gcal-personal.org")
          (,(auth-source-pick-first-password :host "org-gcal-calendar-id-work")
           . "~/org/home/gcal/gcal-work.org")))

   ;; Fetch window: 7 days back, 30 days forward.
  (setq org-gcal-up-days   7
        org-gcal-down-days 30)

  ;; Do not auto-archive old events outside the fetch window.
  (setq org-gcal-auto-archive nil)

  ;; Ask before removing events that appear cancelled/missing on Google's side.
  ;; Avoids silent archiving of items that are simply outside the fetch window.
  (setq org-gcal-update-cancelled-events-with-todo t
        org-gcal-remove-api-cancelled-events       'ask)

  ;; Collect recurring event instances under their parent headline.
  (setq org-gcal-recurring-events-mode 'nested)

  ;; Suppress popup notifications.
  (setq org-gcal-notify-p nil))

;; Safe fetch wrapper: clears stale sync lock before fetching.
;; Uses org-gcal-fetch (read-only — never pushes local changes back to Google).
(defun my/org-gcal-fetch-safe ()
  "Fetch Google Calendar events, clearing any stale sync lock first."
  (interactive)
   (require 'org-gcal)
  (when (bound-and-true-p org-gcal--sync-lock)
    (warn "org-gcal: stale sync lock detected, clearing it.")
    (org-gcal--sync-unlock))
  (org-gcal-fetch))

;; Auto-fetch every 30 minutes. Adjust the interval as needed.
(run-at-time "30 min" 1800 #'my/org-gcal-fetch-safe)

;; Manual sync keybinding: SPC a o s
(spacemacs/set-leader-keys "aos" 'my/org-gcal-fetch-safe)

3.5.16. org-clock

3.5.16.1. Save the clock history across Emacs sessions
(setq org-clock-persist 'history)
(org-clock-persistence-insinuate)

3.5.17. Replace selected markdown region to org format

(defun replace-markdown-region-with-org (beginning end)
  "Replace the selected markdown region with its corresponding org-mode format."
  (interactive "r")
  (if (use-region-p)
      (let ((tmp-buffer "Markdown To Org Tmp"))
        (pcase (shell-command-on-region (region-beginning) (region-end)
                                        "pandoc -f markdown -t org" tmp-buffer t)
          (0 (message "Successfully converted to org-mode format."))
          (127 (message "pandoc not found. Install it with 'sudo apt install pandoc'."))
          (_ (message "Failed to convert the selected region to org-mode format."))
          )
        )
    (message "No active region is found.")))
(spacemacs/set-leader-keys-for-major-mode 'org-mode "RR" 'replace-markdown-region-with-org)

3.5.18. ob-lean4

(use-package ob-lean4
  :load-path "~/.spacemacs.d/private/ob-lean4")
(add-to-list 'org-babel-load-languages '(lean4 . t))

3.5.19. ob-cmake

(use-package ob-cmake
  :load-path "~/.spacemacs.d/private/ob-cmake")
(add-to-list 'org-babel-load-languages '(cmake . t))

3.5.20. ob-rust

(require 'ob-rust)
(add-to-list 'org-babel-load-languages '(rust . t))

3.6. Utility

3.6.1. beacon mode

(beacon-mode 1)

3.6.2. Toggle clang-format on save

(defun c-c++-toggle-clang-format-on-save ()
  (interactive)
  (cond
   (c-c++-enable-clang-format-on-save
    (message "[c-c++] disable clang-format on save")
    (setq c-c++-enable-clang-format-on-save nil))
   ((not c-c++-enable-clang-format-on-save)
    (message "[c-c++] enable clang-format on save")
    (setq c-c++-enable-clang-format-on-save t))
   ))

(spacemacs/set-leader-keys-for-major-mode 'c-mode "Tf" 'c-c++-toggle-clang-format-on-save)
(spacemacs/set-leader-keys-for-major-mode 'c++-mode "Tf" 'c-c++-toggle-clang-format-on-save)
(spacemacs/declare-prefix-for-mode 'c-mode "Tf" "toggle-clang-format-on-save")
(spacemacs/declare-prefix-for-mode 'c++-mode "Tf" "toggle-clang-format-on-save")

3.6.3. auto-indent

;; I want to disable pasting with formatting on C/C++ buffers
(add-to-list 'spacemacs-indent-sensitive-modes 'c-mode)
(add-to-list 'spacemacs-indent-sensitive-modes 'c++-mode)

3.6.4. format-all

(add-hook 'sh-mode-hook #'format-all-mode)
(add-hook 'fish-mode-hook #'format-all-mode)
(add-hook 'cmake-mode-hook #'format-all-mode)

3.6.5. glow, the markdown viewer

;; Configure glow viewer
(defun start-glow-viewer ()
  (interactive)
  (start-process "glow-markdown-viewer" nil
                 "/usr/bin/x-terminal-emulator"
                 (file-truename "~/.spacemacs.d/scripts/glow_mk_viewer.sh")
                 (buffer-file-name nil)))

3.6.6. google-search

;; Set google as default search engine and open links with xdg-open or open depending on the OS
(spacemacs/set-leader-keys "ag" 'engine/search-google)
(setq browse-url-browser-function 'browse-url-generic
      engine/browser-function 'browse-url-generic
      browse-url-generic-program (cond ((string-equal system-type "darwin") "open")
                                        ((string-equal system-type "gnu/linux") "xdg-open")))

3.6.7. Kill all buffers

(defun nuke-all-buffers ()
  (interactive)
  (mapcar 'kill-buffer (buffer-list))
  (delete-other-windows))
(global-set-key (kbd "C-x K") 'nuke-all-buffers)

3.6.8. ranger

(with-eval-after-load 'ranger
  (require 'hydra)
  (define-key ranger-mode-map (kbd "M-h") 'ranger-prev-tab)
  (define-key ranger-mode-map (kbd "M-l") 'ranger-next-tab)
  (define-key ranger-mode-map (kbd "M-n") 'ranger-new-tab))
(spacemacs/set-leader-keys "ar" 'ranger)

3.6.9. cheat.sh

The one and only one cheatsheet.

(spacemacs/declare-prefix "ac" "cheat-sh")

;; Prompt to select a topic to show its cheatsheet
(spacemacs/set-leader-keys "acl" 'cheat-sh)

;; Show the help page of cheat.sh
(spacemacs/set-leader-keys "ach" 'cheat-sh-help)

;; Get the cheatsheet for the marked region
(spacemacs/set-leader-keys "acr" 'cheat-sh-region)

;; Get a random page of cheatsheet
(spacemacs/set-leader-keys "acr"
  (lambda ()
    (interactive)
    (cheat-sh ":random")))
(spacemacs/declare-prefix "acr" "cheat.sh/:random")

3.6.10. chatgpt-shell

(use-package chatgpt-shell
  :load-path "~/.spacemacs.d/private/chatgpt-shell")
(setq chatgpt-shell-openai-key
      (auth-source-pick-first-password :host "api.openai.com"))

3.7. mu4e

Email client using mu4e (Spacemacs layer) + mbsync (isync) for IMAP sync. Two Gmail accounts: personal and work. Credentials stored in ~/.authinfo (not committed). Requires: apt install isync maildir-utils mu4e w3m

3.7.1. Setup instructions (one-time)

Run these commands once after installing the system:

# Create maildir structure
mkdir -p ~/.mail/personal/{cur,new,tmp}
mkdir -p ~/.mail/work/{cur,new,tmp}

# Initial mail sync (after ~/.mbsyncrc is configured)
mbsync -a

# Index mail with mu (after initial sync)
mu init --maildir=~/.mail \
    --my-address=$(pass show or cat ~/.authinfo | grep mu4e-email-personal ...) \
    --my-address=$(...)
# Simpler: let mu4e run mu init on first launch via mu4e-update-mail-and-index

3.7.2. Configuration

(with-eval-after-load 'mu4e
  ;; Basic settings
  (setq mu4e-maildir "~/.mail"
        mu4e-get-mail-command "mbsync -a"
        mu4e-update-interval (* 5 60)       ; sync every 5 minutes
        mu4e-compose-signature-auto-include nil
        mu4e-view-show-images t
        mu4e-view-image-max-width 800
        mu4e-headers-date-format "%Y-%m-%d"
        mu4e-headers-time-format "%H:%M"
        mu4e-change-filenames-when-moving t  ; avoid duplicate UID issues
        mu4e-confirm-quit nil)

  ;; Maildir shortcuts shown in the main view
  (setq mu4e-maildir-shortcuts
        '((:maildir "/personal/INBOX"             :key ?i :name "Personal Inbox")
          (:maildir "/personal/[Gmail]/Sent Mail" :key ?s :name "Personal Sent")
          (:maildir "/work/INBOX"                 :key ?I :name "Work Inbox")
          (:maildir "/work/[Gmail]/Sent Mail"     :key ?S :name "Work Sent")))

  ;; HTML rendering with w3m
  (setq mu4e-html2text-command "w3m -T text/html")

  ;; Read account identities from ~/.authinfo
  (let* ((personal-email (auth-source-pick-first-password :host "mu4e-email-personal"))
         (personal-name  (auth-source-pick-first-password :host "mu4e-name-personal"))
         (work-email     (auth-source-pick-first-password :host "mu4e-email-work"))
         (work-name      (auth-source-pick-first-password :host "mu4e-name-work")))

    ;; Contexts: one per account
    (setq mu4e-contexts
          (list
           (make-mu4e-context
            :name "Personal"
            :match-func
            (lambda (msg)
              (when msg
                (string-prefix-p "/personal" (mu4e-message-field msg :maildir))))
            :vars `((user-mail-address       . ,personal-email)
                    (user-full-name          . ,personal-name)
                    (mu4e-sent-folder        . "/personal/[Gmail]/Sent Mail")
                    (mu4e-drafts-folder      . "/personal/[Gmail]/Drafts")
                    (mu4e-trash-folder       . "/personal/[Gmail]/Bin")
                    (mu4e-refile-folder      . "/personal/Archive")
                    (smtpmail-smtp-user      . ,personal-email)
                    (smtpmail-smtp-server    . "smtp.gmail.com")
                    (smtpmail-smtp-service   . 587)
                    (smtpmail-stream-type    . starttls)))
           (make-mu4e-context
            :name "Work"
            :match-func
            (lambda (msg)
              (when msg
                (string-prefix-p "/work" (mu4e-message-field msg :maildir))))
            :vars `((user-mail-address       . ,work-email)
                    (user-full-name          . ,work-name)
                    (mu4e-sent-folder        . "/work/[Gmail]/Sent Mail")
                    (mu4e-drafts-folder      . "/work/[Gmail]/Drafts")
                    (mu4e-trash-folder       . "/work/[Gmail]/Bin")
                    (mu4e-refile-folder      . "/work/Archive")
                    (smtpmail-smtp-user      . ,work-email)
                    (smtpmail-smtp-server    . "smtp.gmail.com")
                    (smtpmail-smtp-service   . 587)
                    (smtpmail-stream-type    . starttls)))))

    ;; Policy: ask which context on ambiguous messages; default to Personal
    (setq mu4e-context-policy 'pick-first
          mu4e-compose-context-policy 'ask-if-none))

  ;; SMTP via smtpmail — passwords come from ~/.authinfo automatically
  (setq send-mail-function         'smtpmail-send-it
        message-send-mail-function 'smtpmail-send-it
        smtpmail-auth-credentials  "~/.authinfo")

  ;; org-mu4e integration: store links to emails (mu4e-org replaces org-mu4e since mu 1.8)
  (require 'mu4e-org)
  (setq org-mu4e-link-query-in-headers-mode nil)

  ;; Use 'Q' to truly quit mu4e
  (define-key mu4e-main-mode-map (kbd "Q") 'mu4e-quit)
  (define-key mu4e-headers-mode-map (kbd "Q") 'mu4e-quit)
  (define-key mu4e-view-mode-map (kbd "Q") 'mu4e-quit)

  ;; Use 'q' to just bury the buffer (background)
  (define-key mu4e-main-mode-map (kbd "q") 'mu4e-view-quit)
  (define-key mu4e-headers-mode-map (kbd "q") 'mu4e-view-quit)
  (define-key mu4e-view-mode-map (kbd "q") 'mu4e-view-quit)
  )

;; Keybinding: open mu4e with SPC a m
(spacemacs/set-leader-keys "am" 'mu4e)
(spacemacs/declare-prefix "am" "mu4e-mail")
(mu4e t)

3.8. Workarounds

3.8.1. Workaround for the bug where company-mode and evil-mode are conflicting

(evil-declare-change-repeat 'company-complete)

3.8.2. doom-modeline icons in GUI vs terminal

;; Enable doom-modeline icons in GUI, disable in terminal
(with-eval-after-load 'doom-modeline
  (setq doom-modeline-icon (display-graphic-p))
  ;; When creating a new frame (e.g. emacsclient), re-check
  (add-hook 'after-make-frame-functions
            (lambda (frame)
              (with-selected-frame frame
                (setq doom-modeline-icon (display-graphic-p))))))

3.8.3. Workaround for spammed false positive warning messages

Ticket: syl20bnr/spacemacs#16575 ‘org-element-at-point’ cannot be used in non-Org buffer

(add-to-list 'warning-suppress-types '(org-element org-element-parser))

Created: 2026-03-04 Wed 17:50

Validate