alexforsale's literate Emacs configuration

Table of Contents

This repository is my personal Emacs configuration. In case you don't know what Emacs is, I won't be the one explaining. To me Emacs is crucial and should exists in all of my machines, including the proprietary ones. In fact, this repository is the first one that I clone into my new machines (with my dotfiles being the second one).

The Goal

I used to blindly copying and pasting every snippets of Emacs configuration I found over the internet. The result usually is me having to mix and match them into one configuration, which is fine until it broke, and I start from scratch again and the cycle repeats.

Keep it simple

If the default configuration for a certain package is enough, don't add more. a certain package is enough, don't add more. So this configuration is constantly changing, sometimes something gets added, only to be removed after a while when I don't use it anymore (or when I don't know how to use it).

Reproducible

This configuration should be usable in many systems. Usually, even in one machine (my main), I have several Emacs configuration running all at once, using chemacs. I use this to compare my main configuration with a stable configuration such as Doom or Spacemacs.

This configuration is for me, not for anyone else

It's good if anyone who stumble into this repository find it useful, but the priority is to make it useful for me now. I'm trying to stop adding configuration that someday would be useful.

Editor first, IDE second

I'm not a programmer, I use Emacs mainly for editing files, tasks management, note-taking. My main focus is org-mode, since this mode is what I use most.

Documented

  • List every external packages needed for this configuration.
  • Explain why a package is configured.
  • If possible, also compare with other package with similar functionality.

Mnemonic keybindings

Try setting a sensible keybindings, especially for external packages. Don't mess with the default keybindings if possible.

  • Evil Mode keybindings

    Evil mode already sets keybindings by default, some of them already changed the vanilla keybindings. For me Evil-mode is a necessary evil, I don't like it, but it's definitely better than the Emacs vanilla keychords (trust me, I tried).

Use org-mode

This way I don't have to separate each configurations into a separate modules. But having all the configuration in one file is also made debugging quite hard, so I'll still separate them into separate files.

  • init.el and config.el

    init.el is for the essential configurations, everything else goes to into separate files.

Try to minimize the use of external packages

More dependencies means more bloats, avoid it when possible.

early-init.el

GC Threshold

A common way of speeding up Emacs startup. The variable gc-cons-threshold is the number of bytes consing between garbage collection. Garbage collection can happen atuomatically once this many bytes have been allocated since the last garbage collection. Here I set this to a variable most-positive-fixnum which value is set automatically.

most-positive-fixnum
2305843009213693951
(setq gc-cons-threshold most-positive-fixnum 
    gc-cons-percentage 0.6)

The gc-cons-percentage default to 0.1, is the portion of the heap used for allocation.

I set the value differently when Emacs is starting up, by setting this variable into emacs-startup-hook that runs after Emacs loaded the init files.

(add-hook 'emacs-startup-hook
        (lambda ()
          (setq gc-cons-threshold (* 16 1024 1024) ; 16mb
                gc-cons-percentage 0.1)))

Language

Set UTF-8 as the default coding system

(set-language-environment "UTF-8")

Set initial frame

Here I disable the menu-bar, tool-bar and vertical scroll bar.

(add-to-list 'default-frame-alist '(menu-bar-lines . 0))
(add-to-list 'initial-frame-alist '(menu-bar-lines . 0))

(add-to-list 'initial-frame-alist '(tool-bar-lines . 0))
(add-to-list 'default-frame-alist '(tool-bar-lines . 0))

(add-to-list 'initial-frame-alist '(vertical-scroll-bars))
(add-to-list 'default-frame-alist '(vertical-scroll-bars))

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

Define the location of custom-file

The custom-file is used to save customization settings for future use.

(customize-set-variable 'custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (file-exists-p custom-file)
  (load custom-file))

Setup package

Now much more simpler since [[https://github.com/jwiegley/use-package][use-package]] is built into Emacs. It's actually not used since I'm using straight.el.

(with-eval-after-load 'package
  ;; Add `melpa` to `package-archives`.
  (add-to-list 'package-archives
             '("melpa" . "https://melpa.org/packages/"))
  ;; gnu-devel
  (add-to-list 'package-archives '("gnu-devel" . "https://elpa.gnu.org/devel/"))
  (add-to-list 'package-archives '("nongnu" . "https://elpa.nongnu.org/nongnu/")))

init.el

straight.el

(customize-set-variable 'straight-use-package-by-default t)

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name
        "straight/repos/straight.el/bootstrap.el"
        (or (bound-and-true-p straight-base-dir)
            user-emacs-directory)))
      (bootstrap-version 7))
  (unless (file-exists-p bootstrap-file)
    (with-current-buffer
        (url-retrieve-synchronously
         "https://raw.githubusercontent.com/radian-software/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

(straight-register-package
 '(org :depth 1))

(straight-use-package 'org)
(straight-use-package 'use-package)

I use straight-register-package for org-mode since full cloning the repository would take too much time.

Tangle this org-file

This file is already added in my git repositories for initial Emacs loading. But I also include it here to make sure there's no changes made.

(require 'ob-tangle)

(when (file-exists-p (expand-file-name "index.org" user-emacs-directory))
  (org-babel-tangle-file (expand-file-name "index.org" user-emacs-directory)))

Variables and helper functions

  • Identity
    (setq user-mail-address "alexforsale@yahoo.com"
          user-full-name "Kristian Alexander P")
    
  • merge-list-to-list function

    This function is just a wrapper to easily merge lists. I don't think I need it anymore.

    ;;; From https://emacs.stackexchange.com/questions/38008/adding-many-items-to-a-list/68048#68048
    (defun merge-list-to-list (dst src)
      "Merges content of the 2nd list with the 1st one"
      (set dst
           (append (eval dst) src)))
    
  • define +config/org-directory variable
    (cond ((file-directory-p (expand-file-name "Sync/org" (getenv "HOME")))
           (customize-set-variable '+config/org-directory (expand-file-name "Sync/org" (getenv "HOME"))))
          ((string-match-p "microsoft" (shell-command-to-string "uname -a"))
           (customize-set-variable '+config/org-directory (expand-file-name "org" (getenv "HOME")))))
    

    I put my org files inside syncthing default directory (~/Sync if on Linux) so it will automatically synced to all my devices. But on windows WSL the location will vary depending on how you install syncthing so I won't even bother set it up.

Load each modular configuration file.

  • evil

    All evil-mode specific configuration and evil related packages goes here.

    (require 'config-evil (expand-file-name "modules/config-evil.el" user-emacs-directory) t)
    
  • general
    (require 'config-general (expand-file-name "modules/config-general.el" user-emacs-directory) t)
    

    All general keybindings are in this file.

  • UI
    (require 'config-ui (expand-file-name "modules/config-ui.el" user-emacs-directory) t)
    

    Configuration for Emacs's looks and feels.

  • builtins
    (require 'config-builtins (expand-file-name "modules/config-builtins.el" user-emacs-directory) t)
    

    The more I understand the meat inside Emacs the less external packages I'll use (hopefully).

  • completion
    (require 'config-completion (expand-file-name "modules/config-completion.el" user-emacs-directory) t)
    

    For now this is for vertico and friends.

  • shells
    (require 'config-shells (expand-file-name "modules/config-shells.el" user-emacs-directory) t)
    

    Mostly vterm configurations. Eshell looks promising, but still hard to configure.

  • editing
    (require 'config-editing (expand-file-name "modules/config-editing.el" user-emacs-directory) t)
    

    Tweaks to make my editing experience nicer.

  • IDE
    (require 'config-ide (expand-file-name "modules/config-ide.el" user-emacs-directory) t)
    

    All of LSP, git, flycheck, and projectile configuration.

  • org
    (require 'config-org (expand-file-name "modules/config-org.el" user-emacs-directory) t)
    

    Most of my changes are in here, the more configurable org-mode, the more unusable to me.

  • tools
    (require 'config-tools (expand-file-name "modules/config-tools.el" user-emacs-directory) t)
    

    Small packages that helps Emacs interacts with its surrounding. I have direnv, exec-path-from-shell for, you guess it, shells. There's also ox-hugo for blogging with hugo.

  • dired
    (require 'config-dired (expand-file-name "modules/config-dired.el" user-emacs-directory) t)
    

    I don't use dired that much, but when I do, I at least know my way around.

  • treemacs
    (require 'config-treemacs (expand-file-name "modules/config-treemacs.el" user-emacs-directory) t)
    

    It's like NERDtree in vim, but in Emacs, I kinda understand why vim need this functionality. C-x C-f with vertico and marginalia already do the same in Emacs. So it's not important to me, but still nice to have.

  • mail
    (require 'config-mail (expand-file-name "modules/config-mail.el" user-emacs-directory) t)
    

    Email in Emacs with the help of notmuch, offlineimap, afew, and davmail for my Exchange mails.

  • programming modes
    (require 'config-progmodes (expand-file-name "modules/config-progmodes.el" user-emacs-directory) t)
    

    Doing stuffs in linux will eventually makes you edit files from various programming languages. This here is just to make me read them comfortly.

  • local overrides
    (require 'config-overrides (expand-file-name "modules/config-overrides.el" user-emacs-directory) t)
    

Modules

Evil

  • configuration

    Mostly copied from doom or the official documentation.

    (use-package evil
      :demand t
      :hook
      (after-change-major-mode . (lambda ()
                                   (setq evil-shift-width tab-width)))
      :preface
      (customize-set-variable 'evil-want-keybinding nil)
      (customize-set-variable 'evil-want-integration t)
      (customize-set-variable 'evil-undo-system 'undo-redo)
      (customize-set-variable 'evil-want-C-i-jump nil) ;; fix TAB in terminal org-mode
      (customize-set-variable 'evil-want-C-u-scroll t) ;; move universal arg to <leader> u
      (customize-set-variable 'evil-want-C-u-delete t) ;; delete back to indentation in insert state
      (customize-set-variable 'evil-want-C-g-bindings t)
      :custom
      (evil-undo-system #'undo-redo)
      (evil-search-module 'evil-search)
      (evil-ex-search-vim-style-regexp t)
      (evil-ex-interactive-search-highlight 'selected-window)
      (evil-kbd-macro-suppress-motion-error t)
      (evil-visual-update-x-selection-p nil)
      :config
      (unless noninteractive
        (setq save-silently t))
      (setq evil-normal-state-cursor 'box
            evil-insert-state-cursor 'bar
            evil-visual-state-cursor 'hollow)
      (evil-select-search-module 'evil-search-module 'isearch)
      (evil-mode 1)
      (with-eval-after-load 'eldoc
        (eldoc-add-command 'evil-normal-state
                           'evil-insert
                           'evil-change
                           'evil-delete
                           'evil-replace))
      ;; from doom
      (defun +evil/window-split-and-follow ()
        "Split current window horizontally, then focus new window.
    If `evil-split-window-below' is non-nil, the new window isn't focused."
        (interactive)
        (let ((evil-split-window-below (not evil-split-window-below)))
          (call-interactively #'evil-window-split)))
      (defun +evil/window-vsplit-and-follow ()
        "Split current window vertically, then focus new window.
    If `evil-vsplit-window-right' is non-nil, the new window isn't focused."
        (interactive)
        (let ((evil-vsplit-window-right (not evil-vsplit-window-right)))
          (call-interactively #'evil-window-vsplit))))
    

    Notes:

    evil-search-modules
    I switched this to isearch since the default evil-search didn't search through collapsed headings in org-mode.
    evil-want-keybindings
    I set this to nil so it won't load evil-keybindings.el.
    evil-want-integration
    Loads evil-integration.el.
    evil-undo-system
    Use undo-redo which is natively available in Emacs 28.
    evil-want-C-i-jump
    Set to nil so C-i will insert a tab character, needed when I'm using Emacs inside a terminal.
    evil-want-C-u-scroll
    Set C-u to scrolls up (like Vim), I'm not using it for prefix arguments since it's already mapped to SPC u in normal mode.
    evil-want-C-u-delete
    The same for insert mode, set C-u to deletes back to indentation in insert state, prefix argument is set to C-c SPC u in insert mode.
    evil-want-C-g-bindings
    Set a consistent bindings for C-g, though it also depends on the packages.
    evil-ex-search-vim-style-regexp
    Enables Vim-style backlash codes in search patterns.
    evil-ex-interactive-search-highlight
    Set the interactive highlighting only to selected-window.
    evil-visual-update-x-selection-p
    Not updating the X PRIMARY selection with the current visual region, not sure how it works under wayland though.

    I've also steal two functions from Doomemacs,

    +evil/window-vsplit-and-follow
    which is bound to SPC w v.
    +evil/window-split-and-follow
    which is bound to SPC w s.

    Why is this the first package in my list? My right hand fingers always land on h, j, k, l. You can't teach old dog new tricks. I've used the vanilla keybindings, it's great but it's faster with /evil-mode.

    • Useful keybindings

      Not a full list of keybindings, just ones that I should remember to use.

      keybindings function description
      normal-state ] b evil-next-buffer Go to the COUNTth next buffer in the buffer list.
      normal-state [ b evil-previous-buffer Go to the COUNTth previous buffer in the buffer list.
      normal-state ] SPC evil-collection-unimpaired-insert-newline-below Insert COUNT blank line(s) below current line.
      normal-state [ SPC evil-collection-unimpaired-insert-newline-above Insert COUNT blank line(s) above current line.
  • evil-collection

    This is a collection of Evil bindings for the parts of Emacs that Evil does not cover properly by default, such as help-mode, M-x calendar, Eshell and more.

    (use-package evil-collection
      :after evil
      :init
      (evil-collection-init)
      :custom
      (evil-collection-setup-minibuffer t)
      (evil-collection-calendar-want-org-bindings t))
    
  • evil-terminal-cursor-changer

    Change cursor shape and color by evil state in terminal.

    (use-package evil-terminal-cursor-changer
      :unless (display-graphic-p)
      :init
      (evil-terminal-cursor-changer-activate)
      :config
      (setq evil-motion-state-cursor 'box)  ; 
      (setq evil-visual-state-cursor 'box)  ; 
      (setq evil-normal-state-cursor 'box)  ; 
      (setq evil-insert-state-cursor 'bar)  ; 
      (setq evil-emacs-state-cursor  'hbar) ; _
      )
    
  • evil-easymotion
    (use-package evil-easymotion
      :after evil
      :demand t
      :commands evilem-create evilem-default-keybindings
      :config
      (evilem-make-motion evilem-motion-search-next #'evil-search-next
                          :bind ((evil-ex-search-highlight-all nil)))
      (evilem-make-motion evilem-motion-search-previous #'evil-search-previous
                          :bind ((evil-ex-search-highlight-all nil)))
      (evilem-make-motion evilem-motion-search-word-forward #'evil-search-word-forward
                          :bind ((evil-ex-search-highlight-all nil)))
      (evilem-make-motion evilem-motion-search-word-backward #'evil-search-word-backward
                          :bind ((evil-ex-search-highlight-all nil)))
      ;; Rebind scope of w/W/e/E/ge/gE evil-easymotion motions to the visible
      ;; buffer, rather than just the current line.
      (put 'visible 'bounds-of-thing-at-point (lambda () (cons (window-start) (window-end))))
      (evilem-make-motion evilem-motion-forward-word-begin #'evil-forward-word-begin :scope 'visible)
      (evilem-make-motion evilem-motion-forward-WORD-begin #'evil-forward-WORD-begin :scope 'visible)
      (evilem-make-motion evilem-motion-forward-word-end #'evil-forward-word-end :scope 'visible)
      (evilem-make-motion evilem-motion-forward-WORD-end #'evil-forward-WORD-end :scope 'visible)
      (evilem-make-motion evilem-motion-backward-word-begin #'evil-backward-word-begin :scope 'visible)
      (evilem-make-motion evilem-motion-backward-WORD-begin #'evil-backward-WORD-begin :scope 'visible)
      (evilem-make-motion evilem-motion-backward-word-end #'evil-backward-word-end :scope 'visible)
      (evilem-make-motion evilem-motion-backward-WORD-end #'evil-backward-WORD-end :scope 'visible)
      (evilem-default-keybindings "z g")
      )
    

    This is a useful package for complementing Evil's motion. With general.el, I set the keymap to SPC g e.

    evilem keybinding evilem key Evil evil key
    evilem-motion-forward-word-begin SPC g e w evil-forward-word-begin w
    evilem-motion-forward-WORD-begin SPC g e W evil-forward-WORD-begin W
    evilem-motion-forward-word-end SPC g e e evil-forward-word-end e
    evilem-motion-forward-WORD-end SPC g e E evil-forward-WORD-end E
    evilem-motion-backward-word-begin SPC g e b evil-forward-word-begin b
    evilem-motion-backward-WORD-begin SPC g e B evil-forward-WORD-begin B
    evilem-motion-backward-word-end SPC g e g e evil-forward-word-end g e
    evilem-motion-backward-WORD-end SPC g e g E evil-forward-WORD-end g E

    These keybindings is also available on visual-mode.

  • evil-surround

    This package emulates surround.vim by Tim Pope. The functionality is wrapped into a minor mode.

    (use-package evil-surround
      :after evil
      :demand t
      :commands (global-evil-surround-mode
                 evil-surround-edit
                 evil-Surround-edit
                 evil-surround-region)
      :hook
      (org-mode . (lambda () (push '(?~ . ("~" . "~")) evil-surround-pairs-alist)))
      (org-mode . (lambda () (push '(?$ . ("\\(" . "\\)")) evil-surround-pairs-alist)))
      :init
      (global-evil-surround-mode 1))
    
    • Usage
      • Add surrounding

        You can surround in visual-state with S<textobject> or gS<textobject>. Or in normal-state with ys<textobject> or yS<textobject>. I'm still using the Shift I in visual mode, and the ys in normal mode conflicts with =which-key-mode.

      • Change surrounding

        You can change a surrounding with cs<old-textobject><new-textobject>.

  • evil-embrace

    This package provides evil integration of embrace.el. Since evil-surround provides a similar set of features as embrace.el, this package aims at adding the goodies of embrace.el to evil-surround and making evil-surround even better.

    (use-package evil-embrace
      :commands embrace-add-pair embrace-add-pair-regexp
      :hook (LaTeX-mode . embrace-LaTeX-mode-hook)
      :hook (LaTeX-mode . +evil-embrace-latex-mode-hook-h)
      :hook (org-mode . embrace-org-mode-hook)
      :hook (ruby-mode . embrace-ruby-mode-hook)
      :hook (emacs-lisp-mode . embrace-emacs-lisp-mode-hook)
      :init
      (with-eval-after-load 'evil-surround
        (evil-embrace-enable-evil-surround-integration))
      :config
      ;; from doom
      (defun +evil--embrace-latex ()
        "LaTeX command support for embrace."
        (cons (format "\\%s{" (read-string "\\")) "}"))
    
      (defun +evil-embrace-latex-mode-hook-h ()
        (dolist (pair '((?\' . ("`" . "\'"))
                        (?\" . ("``" . "\'\'"))))
          (delete (car pair) evil-embrace-evil-surround-keys)
          ;; Avoid `embrace-add-pair' because it would overwrite the default
          ;; rules, which we want for other modes
          (push (cons (car pair) (make-embrace-pair-struct
                                  :key (car pair)
                                  :left (cadr pair)
                                  :right (cddr pair)
                                  :left-regexp (regexp-quote (cadr pair))
                                  :right-regexp (regexp-quote (cddr pair))))
                embrace--pairs-list))
        (embrace-add-pair-regexp ?l "\\[a-z]+{" "}" #'+evil--embrace-latex))
      (defun +evil-embrace-angle-bracket-modes-hook-h ()
        (let ((var (make-local-variable 'evil-embrace-evil-surround-keys)))
          (set var (delq ?< evil-embrace-evil-surround-keys))
          (set var (delq ?> evil-embrace-evil-surround-keys)))
        (embrace-add-pair-regexp ?< "\\_<[a-z0-9-_]+<" ">" #'+evil--embrace-angle-brackets)
        (embrace-add-pair ?> "<" ">")))
    
  • evil-escape

    Customizable key sequence to escape from insert state and everything else in Emacs.

    (use-package evil-escape
      :commands evil-escape
      :after evil
      :hook (evil-after-load . evil-escape-mode)
      :init
      (setq evil-escape-excluded-states '(normal visual multiedit emacs motion)
            evil-escape-excluded-major-modes '(neotree-mode treemacs-mode vterm-mode dired-mode)
            evil-escape-key-sequence "jk"
            evil-escape-delay 0.15)
      (evil-define-key* '(insert replace visual operator) 'global "\C-g" #'evil-escape))
    
  • evil-exchange
    (use-package evil-exchange
      :init
      (evil-exchange-install))
    

    Easy text exchange operator for Evil. This is the port of vim-exchange by Tom McDonald.

  • Default Bindings
    • gx (evil-exchange)

      On the first use, define (and highlight) the first {motion} to exchange. On the second use, define the second {motion} and perform the exchange.

    • gX (evil-exchange-cancel)

      Clear any {motion} pending for exchange.

  • evil-quick-diff

    This is a replacement for the linediff.vim plugin. It's not a faithful port, as it uses ediff for diffing, but the spirit is the same. The default operator key for diffing is god. While goD is used for canceling.

    (use-package evil-quick-diff
      :straight (:host github :repo "rgrinberg/evil-quick-diff")
      :after evil
      :commands (evil-quick-diff evil-quick-diff-cancel)
      :init
      ;; change default key bindings (if you want) HERE
      ;; (setq evil-quick-diff-key (kbd "zx"))
      (evil-quick-diff-install))
    
  • evil-nerd-commenter

    A Nerd Commenter emulation, help you comment code efficiently. For example, you can press 99,ci to comment out 99 lines.

    (use-package evil-nerd-commenter
      :after evil
      :commands (evilnc-comment-operator
                 evilnc-inner-comment
                 evilnc-outer-commenter)
      :bind ([remap comment-line] . evilnc-comment-or-uncomment-lines))
    
  • evil-snipe

    Evil-snipe emulates vim-seek and/or vim-sneak in evil-mode.

    It provides 2-character motions for quickly (and more accurately) jumping around text, compared to evil's built-in f/F/t/T motions, incrementally highlighting candidate targets as you type.

    By default, snipe only binds s (forward) / S (backward) to evil-snipe-s and evil-snipe-S, respectively. In operator mode, snipe is bound to z / Z and x / X (exclusive).

    (use-package evil-snipe
      :commands evil-snipe-local-mode evil-snipe-override-local-mode
      :after evil
      :hook (evil-after-load . (lambda () (evil-snipe-override-mode +1)))
      :hook (evil-after-load . (lambda () (evil-snipe-mode +1)))
      :hook (magit-mode . turn-off-evil-snipe-override-mode)
      :init
      (setq evil-snipe-smart-case t
            evil-snipe-scope 'line
            evil-snipe-repeat-scope 'visible
            evil-snipe-char-fold t))
    
  • evil-textobj-anyblock

    Evil-textobj-anyblock is port of the vim plugin vim-textobj-anyblock. It gives a text object that will select the closest of (), {}, [], <>, '', "", ` `, or “” by default. This can be convenient for operating on the closest block without having to type its symbol.

    In addition to the features provided by vim-textobj-anyblock, anyblock will seek forward in the case that there is not a surrounding match. Also, repeatedly using a text object in visual mode has an expand-region-like functionality by expanding the visual selection to the next block. This is not a primary feature of anyblock but may be nice in some simple cases and is given by evil for free.

    (use-package evil-textobj-anyblock
      :straight (:host github :repo "willghatch/evil-textobj-anyblock")
      :after evil
      :defer t
      :config
      (setq evil-textobj-anyblock-blocks
            '(("(" . ")")
              ("{" . "}")
              ("\\[" . "\\]")
              ("<" . ">"))))
    
  • evil-traces

    evil-traces is a port of traces.vim. It enables visual previews for certain evil-ex commands.

    (use-package evil-traces
      :after evil
      :config
      (evil-traces-mode))
    
  • evil-visualstar

    This is a port of one of the many visual-star plugins for Vim to work with evil-mode.

    (use-package evil-visualstar
      :commands (evil-visualstar/begin-search
                 evil-visualstar/begin-search-forward
                 evil-visualstar/begin-search-backward)
      :init
      (evil-define-key* 'visual 'global
        "*" #'evil-visualstar/begin-search-forward
        "#" #'evil-visualstar/begin-search-backward))
    
  • exato

    This package provides the x text object to manipulate html/xml tag attributes. You can customize the binding.

    Try using dax, vix and gUix.

    (use-package exato
      :after evil
      :commands evil-outer-xml-attr evil-inner-xml-attr)
    
  • evil-vimish-fold

    Integration of vimish-fold with evil

    Adds standard vim keybindings of zf and zd to create and delete folds (via vimish-fold) respectively. Also hooks into evil so the usual vim keybindings for fold toggling (za), opening (zo), closing (zc) etc all work as expected with vimish-fold.

    Finally, also supports navigation between folds using zj and zk.

    This provides a near-complete vim folding experience in evil for Emacs.

    (use-package evil-vimish-fold
      :commands (evil-vimish-fold/next-fold evil-vimish-fold/previous-fold
                                            evil-vimish-fold/delete evil-vimish-fold/delete-all
                                            evil-vimish-fold/create evil-vimish-fold/create-line)
      :config
      (vimish-fold-global-mode +1)
      (with-eval-after-load 'general
        (general-define-key
         [remap evil-toggle-fold] 'vimish-fold-toggle
         [remap evil-close-fold] 'vimish-fold-refold
         [remap evil-open-fold] 'vimish-fold-unfold
         [remap evil-close-folds] 'vimish-fold-refold-all
         [remap evil-open-folds] 'vimish-fold-unfold-all))
      (evil-define-key* 'motion 'global
        "zj" #'vimish-fold-next-fold
        "zk" #'vimish-fold-previous-fold
        "zf" #'evil-vimish-fold/create
        "zF" #'evil-vimish-fold/create-line
        "zd" #'evil-vimish-fold/delete
        "zE" #'evil-vimish-fold/delete-all))
    
  • evil-mc

    Multiple cursors implementation for evil-mode. I'm using the default prefix g ..

    (use-package evil-mc
      :commands (evil-mc-make-cursor-here
                 evil-mc-make-all-cursors
                 evil-mc-undo-all-cursors
                 evil-mc-pause-cursors
                 evil-mc-resume-cursors
                 evil-mc-make-and-goto-first-cursor
                 evil-mc-make-and-goto-last-cursor
                 evil-mc-make-cursor-in-visual-selection-beg
                 evil-mc-make-cursor-in-visual-selection-end
                 evil-mc-make-cursor-move-next-line
                 evil-mc-make-cursor-move-prev-line
                 evil-mc-make-cursor-at-pos
                 evil-mc-has-cursors-p
                 evil-mc-make-and-goto-next-cursor
                 evil-mc-skip-and-goto-next-cursor
                 evil-mc-make-and-goto-prev-cursor
                 evil-mc-skip-and-goto-prev-cursor
                 evil-mc-make-and-goto-next-match
                 evil-mc-skip-and-goto-next-match
                 evil-mc-skip-and-goto-next-match
                 evil-mc-make-and-goto-prev-match
                 evil-mc-skip-and-goto-prev-match)
      :init
      (global-evil-mc-mode  1)
      :config
      ;; (evil-define-key 'visual evil-mc-key-map
      ;;   "A" #'evil-mc-make-cursor-in-visual-selection-end
      ;;   "I" #'evil-mc-make-cursor-in-visual-selection-beg)
      (with-eval-after-load 'general
        (general-define-key
         :states '(normal visual)
         :keymaps 'global
         "gz" '(:ignore t :wk "evil-mc")
         "gzm" 'evil-mc-make-all-cursors
         "gzu" 'evil-mc-undo-all-cursors
         ;; "gzz" '+evil/mc-toggle-cursors
         ;; "gzc" '+evil/mc-make-cursor-here
         "gzn" 'evil-mc-make-and-goto-next-cursor
         "gzp" 'evil-mc-make-and-goto-prev-cursor
         "gzN" 'evil-mc-make-and-goto-last-cursor
         "gzP" 'evil-mc-make-and-goto-first-cursor)
        (general-define-key
         :states '(normal visual)
         :keymaps 'evil-mc-key-map
         "C-n" 'evil-mc-make-and-goto-next-cursor
         "C-N" 'evil-mc-make-and-goto-next-cursor
         "C-p" 'evil-mc-make-and-goto-prev-cursor
         "C-P" 'evil-mc-make-and-goto-first-cursor)))
    
  • evil-multiedit
    (use-package evil-multiedit
      :config
      (defun make-evil-multiedit-case-sensitive (fn &rest args)
        (let ((case-fold-search (not iedit-case-sensitive)))
          (apply fn args)))
      (advice-add #'evil-multiedit-match-and-next :around #'make-evil-multiedit-case-sensitive)
      (evil-multiedit-default-keybinds)
      (evil-define-key 'normal 'global
        (kbd "M-d")   #'evil-multiedit-match-symbol-and-next
        (kbd "M-D")   #'evil-multiedit-match-symbol-and-prev)
      (evil-define-key 'visual 'global
        "R"           #'evil-multiedit-match-all
        (kbd "M-d")   #'evil-multiedit-match-and-next
        (kbd "M-D")   #'evil-multiedit-match-and-prev)
      (evil-define-key '(visual normal) 'global
        (kbd "C-M-d") #'evil-multiedit-restore)
    
      (with-eval-after-load 'evil-mutliedit
        (evil-define-key 'multiedit 'global
          (kbd "M-d")   #'evil-multiedit-match-and-next
          (kbd "M-S-d") #'evil-multiedit-match-and-prev
          (kbd "RET")   #'evil-multiedit-toggle-or-restrict-region)
        (evil-define-key '(multiedit multiedit-insert) 'global
          (kbd "C-n")   #'evil-multiedit-next
          (kbd "C-p")   #'evil-multiedit-prev)))
    

    Hlissner's implementation of multiple-cursor for evil-mode.

  • evil-org-mode

    Supplemental evil-mode keybindings to emacs org-mode.

    (use-package evil-org
      :straight (:type git
                       :repo "Somelauw/evil-org-mode")
      :after org
      :hook (org-mode . (lambda () evil-org-mode))
      :config
      (require 'evil-org-agenda)
      (evil-org-agenda-set-keys))
    
  • evil-goggles

    Display visual hint on evil edit operations.

    (use-package evil-goggles
      :config
      (evil-goggles-mode)
      (evil-goggles-use-diff-faces))
    

general

Keybindings in Emacs is always been my struggle, my brain isn't big enough to memorize all the default keybindings (though over the years my fingers started to memorize some of them). At first I started using hydra, it's a wonderful package for customized keybindings. But configuring them feels like a chore, considering I'm still working my way on finding my own comfort in keybindings.

  • configuration
    (use-package general
      :init
      (general-evil-setup t)
      (general-auto-unbind-keys)
      :config
      (general-override-mode)
      (general-create-definer +config/leader-key
        :keymaps 'override
        :states  '(insert emacs normal hybrid motion visual operator)
        :prefix "SPC"
        :non-normal-prefix "C-c SPC")
      (general-create-definer +config/local-leader
        :keymaps 'override
        :states '(emacs normal hybrid motion visual operator)
        :prefix "SPC m"
        :non-normal-prefix "C-c SPC m"
        "" '(:ignore t :which-key (lambda (arg) `(,(cadr (split-string (car arg) " ")) . ,(replace-regexp-in-string "-mode$" "" (symbol-name major-mode))))))
      ;; useful macro
      (defmacro +config/leader-menu! (name infix-key &rest body)
        "Create a definer NAME `+config/leader-NAME' wrapping `+config/leader-key'.
          Create prefix map: `+config/leader-NAME-map'. Prefix bindings in BODY with INFIX-KEY."
        (declare (indent 2))
        `(progn
           (general-create-definer ,(intern (concat "+config/leader-" name))
             :wrapping +config/leader-key
             :prefix-map (quote ,(intern (concat "+config/leader-" name "-map")))
             :infix ,infix-key
             :wk-full-keys nil
             "" '(:ignore t :which-key ,name))
           (,(intern (concat "+config/leader-" name))
            ,@body))))
    

    What I'm doing before is setting the general keybindings inside their respective use-package declarations. So in org-roam, I set a general keybinding. The problem with this is, if the package is deferred (with the keyword :defer t), the keybindings won't show. I prefer to have the keybindings inside the use-package declaration, this way if I decided not to use the package(s), their custom keybindings will also be removed. The other way for this is to use the :demand t keyword, with the down side of having a longer initialization time.

  • Packages keybindings
    • First level menu
      ;; First level menu
      ;; (+config/leader-menu! "activities" "C-a")
      (+config/leader-menu! "buffer" "b")
      (+config/leader-menu! "files" "f")
      (+config/leader-menu! "go" "g")
      (+config/leader-menu! "insert" "i")
      (+config/leader-menu! "mail" "M-m")
      (+config/leader-menu! "mark" "m")
      (+config/leader-menu! "notes" "n")
      (+config/leader-menu! "open" "o")
      (+config/leader-menu! "quit" "q")
      (+config/leader-menu! "register" "gr")
      (+config/leader-menu! "tree" "t")
      (+config/leader-menu! "tab" "t TAB")
      (+config/leader-menu! "vterm" "v")
      (+config/leader-menu! "window" "w")
      (+config/leader-menu! "search" "/")
      

      This is the immediate menu after pressing SPC in normal and visual mode. and also C-c SPC in insert mode. Mostly inspired by Doomemacs.

    • First level keybinding
      ;; keybindings
      (+config/leader-key
        ";" 'pp-eval-expression
        ":" 'execute-extended-command
        "^" '(subword-capitalize :wk "Capitalize subword")
        "u" 'universal-argument)
      

      These are the commands I used often.

    • buffer
      ;; buffer
      (+config/leader-buffer
        "C-b" 'consult-buffer
        "[" '(previous-buffer :wk "previous buffer")
        "]" '(next-buffer :wk "next buffer")
        "TAB" '((lambda () (interactive) (switch-to-buffer nil)) :wk "other-buffer")
        "b" '(switch-to-buffer :wk "switch to buffer")
        "s" '(basic-save-buffer :wk "save buffer")
        "c" '(clone-indirect-buffer :wk "clone buffer")
        "C" '(clone-indirect-buffer-other-window :wk "clone buffer other window")
        "d" '(kill-current-buffer :wk "kill current buffer")
        "i" 'ibuffer
        "k" '(kill-buffer :wk "kill buffer")
        "l" '(evil-switch-to-windows-last-buffer :wk "Switch to last open buffer")
        "m" '((lambda () (interactive) (switch-to-buffer "*Messages*")) :wk "switch to messages buffer")
        "n" '(next-buffer :wk "next buffer")
        "N" '(evil-buffer-new :wk "New unnamed buffer")
        "p" '(previous-buffer :wk "previous buffer")
        "o" '((lambda () (interactive) (switch-to-buffer nil)) :wk "other-buffer")
        "r" '(revert-buffer-quick :wk "revert buffer")
        "R" '(rename-buffer :wk "rename buffer")
        "x" '((lambda () (interactive) (switch-to-buffer "*scratch*")) :wk "switch to scratch buffer")
        "z" '(bury-buffer :wk "bury buffer"))
      

      Buffer-related keybindings. Some I don't use that much, like ibuffer, it's like dired for buffers.

      There are some keybindings that are bound to functions, like switching to Messages or scratch buffer.

    • consult

      Consult have too many commands it warrants a special heading in this keybindings.

      • global

        This is for temporary, ideally, it should go into separate categories.

        (with-eval-after-load 'consult
          (general-define-key
           [remap Info-search] 'consult-info
           [remap yank-pop] 'consult-yank-pop
           [remap bookmark-jump] 'consult-bookmark
           [remap evil-show-marks] 'consult-mark
           [remap evil-show-registers] 'consult-register
           [remap goto-line] 'consult-goto-line
           [remap imenu] 'consult-imenu
           [remap Info-search] 'consult-info
           [remap locate] 'consult-locate
           [remap load-theme] 'consult-theme
           [remap man] 'consult-man
           [remap recentf-open-files] 'consult-recent-file
           [remap switch-to-buffer] 'consult-buffer
           [remap switch-to-buffer-other-frame] 'consult-buffer-other-frame
           [remap switch-to-buffer-other-window] 'consult-buffer-other-window)
        
          (+config/leader-key
            "M-g" '(:ignore t :wk "consult")
            "M-g M-:" 'consult-mode-command
            "M-g e" 'consult-compile-error
            "M-g G" 'consult-git-grep
            "M-g h" 'consult-history
            "M-g k" 'consult-kmacro
            "M-g m" 'consult-man
            "M-g i" 'consult-info
            "M-g :" 'consult-complex-command
            "M-g b" 'consult-bookmark))
        
      • search

        I'm trying to learn a more complex searching in Emacs, for faster navigation.

        (with-eval-after-load 'consult
          (+config/leader-search
            "c" 'consult-locate
            "d" 'consult-find
            "4" '(:ignore t :wk "other window")
            "4 b" 'consult-buffer-other-window
            "5" '(:ignore t :wk "other frame")
            "5 b" 'consult-buffer-other-frame
            "B" 'consult-project-buffer
            "g" 'consult-goto-line
            "G" 'consult-grep
            "m" '(:ignore t :wk "imenu")
            "m" 'consult-imenu
            "M" 'consult-imenu-multi
            "M-g" 'consult-goto-line
            "o" 'consult-outline
            "l" 'consult-line
            "L" 'consult-line-multi
            "k" 'consult-keep-lines
            "s" '(:ignore t :wk "isearch")
            "e" 'consult-isearch-history
            "u" 'consult-focus-lines)
          (general-define-key
            :keymaps 'minibuffer-local-map
            "M-s" 'consult-history
            "M-r" 'consult-history))
        

        If I happened to have the fd binary, also add this keybindings.

        (when (executable-find "fd")
          (+config/leader-search
            "f" 'consult-fd))
        

        Also if I have ripgrep:

        (when (executable-find "rg")
          (+config/leader-search
            "R" 'consult-ripgrep))
        
        

        In org-mode add this keybindings for navigating through headings.

        (+config/leader-search
          :keymaps 'org-mode-map
          "O" 'consult-org-heading)
        
      • bookmark
        (with-eval-after-load 'consult
          (+config/leader-search
            "b" '(:ignore t :wk "bookmark")
            "bb" 'consult-bookmark
            "bk" 'consult-global-mark
            "bm" 'consult-mark))
        

        Also for the goal of faster text navigation. I think it's a feature I should use more.

      • register
        (with-eval-after-load 'consult
          (+config/leader-search
            "r" '(:ignore t :wk "register")
            "r #" '(consult-register :wk "consult-register") ;; require `consult' package
            ))
        

        Still wrapping my head around the concept of registers. But I do hope to use this more.

      • isearch
        (with-eval-after-load 'consult
          (general-define-key
           :keymaps 'isearch-mode-map
           "M-e" 'consult-isearch-history
           "M-s e" 'consult-isearch-history
           "M-s l" 'consult-line
           "M-s L" 'consult-line-multi))
        

        It's just normal Isearch wrapped in consult.

    • Editing
      • smartparens
        (with-eval-after-load 'smartparens
          (general-nvmap
           :keymaps 'smartparens-mode-map
           "<" 'sp-backward-barf-sexp
           "C-<" 'sp-backward-slurp-sexp
           ">" 'sp-forward-slurp-sexp
           "C->" 'sp-forward-barf-sexp)
          (general-define-key
           :keymaps 'smartparens-mode-map
           "M-DEL" 'sp-unwrap-sexp))
        

        Smartparens is a wonderful package, once you know how to use it.

      • evil-easymotion
        (with-eval-after-load 'general
            (+config/leader-go
              "e" '(:keymap evilem-map :package evil-easymotion :wk "evilem")))
        
    • dired
      (general-nvmap
        'dired-mode-map
        "g$" 'dired-hide-subdir
        "g?" 'dired-summary
        "gG" 'dired-do-chgrp
        "gj" 'dired-next-dirline
        "gk" 'dired-prev-dirline
        "go" 'dired-view-file
        "gO" 'dired-find-file-other-window
        "gy" 'dired-show-file-type)
      

      Is this even needed?

    • files
      ;; files
      (+config/leader-files
        "D" 'dired
        "d" 'dired-jump
        "f" '(find-file :wk "find file")
        "F" '(find-file-other-frame :wk "find file other frame")
        "k" 'delete-frame
        "r" 'recentf
        "S" '(write-file :wk "save file")
        "s" '(save-buffer :wk "save buffer")
        "w" '(find-file-other-window :wk "find file other window"))
      

      Most of this is built-in, but some packages offer a much more better experience, like consult-recent-file:

      (with-eval-after-load 'consult
        (+config/leader-files
          "r" 'consult-recent-file))
      

      Which have a preview-mode when navigating through files.

    • help
      ;; help
      (+config/leader-key
        "h" (general-simulate-key "C-h"
              :state '(normal visual)
              :name general-SPC-h-simulates-C-h
              :docstring "Simulates C-h in normal and visual mode."
              :which-key "Help"))
      

      This command is a wrapper for the actual C-h.

    • insert
      ;; insert
      (+config/leader-insert
        "u" '(insert-char :wk "insert character"))
      

      for inserting special characters or symbols.

    • mark
      ;; mark
      (+config/leader-search
        "bB" '(bookmark-jump-other-window :wk "jump to bookmark other window")
        "b C-c b" '(bookmark-jump-other-frame :wk "jump to bookmark other frame")
        "bc" '(consult-bookmark :wk "consult bookmark") ;; require `consult' package
        "bl" '(bookmark-bmenu-list :wk "list bookmarks")
        "bL" '(bookmark-load :wk "load bookmark")
        "bd" '(bookmark-delete :wk "delete bookmark")
        "bD" '(bookmark-delete-all :wk "delete all bookmarks")
        "bs" '(bookmark-save :wk "save bookmark")
        "br" '(bookmark-rename :wk "rename bookmark"))
      

      Continuation from the bookmark section in consult earlier.

    • open
      ;; open
      (+config/leader-open
        "i" '((lambda () (interactive)
                (if (not chemacs-profile)
                    (find-file user-init-file)
                  (find-file
                   (expand-file-name "init.el" user-emacs-directory)))) :wk "open Emacs configuration file"))
      

      Right now just for opening Emacs init file. And I'm tangling it from an org-file so it needs another addition.

      (+config/leader-open
        "I" '((lambda () (interactive)
                (find-file
                 (expand-file-name "index.org" user-emacs-directory))) :wk "open Emacs configuration file"))
      
    • org-mode

      This is the global keybidings.

      (general-define-key
       :keymaps 'override
       "C-c l" '(org-store-link :wk "Store link")
       "C-c a" '(org-agenda :wk "Org Agenda")
       "C-c c" '(org-capture :wk "Org Capture")
       "C-c C-s" 'org-schedule)
      

      For notes-related keybindings.

      (+config/leader-notes
        "a" 'org-agenda
        "c" 'org-capture
        "C" 'org-capture-goto-last-stored
        "r" 'org-refile-goto-last-stored)
      

      The keybindings under local leader (SPC m), honestly this is too much and I needs to reorganize this.

      (+config/local-leader
        :keymaps 'org-mode-map
        "C-a" 'org-attach
        "C-b" 'org-backward-heading-same-level
        "C-c" 'org-ctrl-c-ctrl-c
        "C-d" 'org-deadline
        "C-e" 'org-export-dispatch
        "C-f" 'org-forward-heading-same-level
        "C-j" 'org-goto
        "C-k" 'org-kill-note-or-show-branches
        "C-l" 'org-insert-link
        "C-n" 'org-next-visible-heading
        "C-o" 'org-open-at-point
        "C-p" 'org-previous-visible-heading
        "C-q" 'org-set-tags-command
        "C-r" 'org-fold-reveal
        "C-s" 'org-schedule
        "C-t" 'org-todo
        "C-w" 'org-refile
        "C-y" 'org-evaluate-time-range
        "C-z" 'org-add-note
        "M-b" 'org-previous-block
        "M-f" 'org-next-block
        "M-l" 'org-insert-last-stored-link
        "M-w" 'org-refile-copy
        "C-^" 'org-up-element
        "C-_" 'org-down-element
        "C-<" 'org-promote-subtree
        "C->" 'org-demote-subtree
        "C-," 'org-insert-structure-template
        "C-*" 'org-list-make-subtree
        "{" 'org-table-toggle-formula-debugger
        "}" 'org-table-toggle-coordinate-overlays
        "`" 'org-table-edit-field
        "\\" 'org-match-sparse-tree
        "^" 'org-sort
        "[" 'org-agenda-file-to-front
        "]" 'org-remove-file
        "@" 'org-mark-subtree
        "?" 'org-table-field-info
        "=" 'org-table-eval-formula
        ">" 'org-goto-calendar
        "+" 'org-table-sum
        "<" 'org-date-from-calendar
        ";" 'org-toggle-comment
        ":" 'org-toggle-fixed-width
        "/" 'org-sparse-tree
        "," 'org-priority
        "." 'org-timestamp
        "*" 'org-ctrl-c-star
        "-" 'org-ctrl-c-minus
        "#" 'org-update-statistics-cookies
        "$" 'org-archive-subtree
        "%" 'org-mark-ring-push
        "'" 'org-edit-special
        "TAB" '(org-clock-in :wk "clock-in")
        "!" '(org-reload :wk "reload org")
        "[" '(org-mark-ring-goto :wk "Jump to previous position in the mark ring")
        "b" '(:ignore t :wk "buffer")
        "bs" '(org-save-all-org-buffers :wk "save all org buffer")
        "f" '(:ignore t :wk "files")
        "fs" '(org-save-all-org-buffers :wk "save all org buffer")
        "s" '(org-schedule :wk "org schedule")
        "i" '(:ignore t :wk "insert")
        "id" '(org-insert-drawer :wk "insert drawer")
        "i@" '(org-cite-insert :wk "insert citation")
        "if" '(org-footnote-action :wk "footnote")
        "i C-f" '(org-emphasize :wk "insert emphasize")
        "il" '(org-insert-link :wk "insert link")
        "iD" '(org-deadline :wk "insert deadline")
        "is" '(org-schedule :wk "insert schedule")
        "ip" '(org-set-property :wk "insert property")
        "it" '(org-insert-time-stamp :wk "insert time-stamp at point"))
      
    • quit

      For quitting Emacs.

      (+config/leader-quit
        "q" '(save-buffers-kill-terminal :wk "quit and save")
        "R" '(restart-emacs :wk "restart Emacs"))
      
    • register

      Not quite register since I moved most of it. Also SPC g r is just too many keystrokes.

      (+config/leader-register
        "M-w" '(copy-rectangle-as-kill :wk "copy region-rectangle and save")
        "c" '(clear-rectangle :wk "blank out region-rectangle")
        "d" '(delete-rectangle :wk "delete region-rectangle")
        "k" '(kill-rectangle :wk "cut rectangle into killed-rectangle")
        "l" '(bookmark-bmenu-list :wk "display existing bookmarks")
        "m" '(bookmark-set :wk "set bookmark")
        "M" '(bookmark-set-no-overwrite :wk "set bookmark no overwrite")
        "N" '(rectangle-number-lines :wk "insert number in front of region-rectangle")
        "o" '(open-rectangle :wk "blank out region-rectangle")
        "r" '(copy-rectangle-to-register :wk "copy rectangle-region to register")
        "t" '(string-rectangle :wk "replace rectangle with string")
        "w" '(window-configuration-to-register :wk "store window configuration to register")
        "y" '(yank-rectangle :wk "yank last killed rectangle with upper left corner at point"))
      
    • window

      For window navigation.

      (+config/leader-window
        "C-o" '(delete-other-windows :wk "delete other windows")
        "[" '(evil-window-left :wk "left window")
        "]" '(evil-window-right :wk "right window")
        "+" '(enlarge-window :wk "enlarge window")
        "-" '(shrink-window :wk "shrink window")
        "}" '(enlarge-window-horizontally :wk "enlarge window horizontally")
        "{" '(shrink-window-horizontally :wk "shrink window horizontally")
        "+" 'evil-window-increase-height
        "-" 'evil-window-decrease-height
        ":" 'evil-ex
        "<" 'evil-window-decrease-width
        "=" 'balance-windows
        ">" 'evil-window-increase-height
        "_" 'evil-window-set-height
        "b" 'evil-window-bottom-right
        "c" 'evil-window-delete
        "d" '(delete-window :wk "delete window")
        "h" 'evil-window-left
        "f" '(ffap-other-window :wk "ffap other window")
        "j" 'evil-window-down
        "k" 'evil-window-up
        "l" 'evil-window-right
        "n" 'evil-window-new
        "p" 'evil-window-mru
        "q" 'evil-quit
        "r" 'evil-window-rotate-downwards
        "R" 'evil-window-rotate-upwards
        "s" '+evil/window-split-and-follow
        "T" '(tear-off-window :wk "tear off window")
        "t" 'evil-window-top-left
        "u" 'winner-undo
        "v" '+evil/window-vsplit-and-follow
        "w" '(other-window :wk "other window")
        "W" 'evil-window-prev
        "x" 'evil-window-exchange
        "|" 'evil-window-set-width
        "<left>" 'evil-window-left
        "<right>" 'evil-window-right
        "<down>" 'evil-window-down
        "<up>" 'evil-win-up)
      
    • search
      (+config/leader-search
        "r" '(:ignore t :wk "register")
        "rf" '(frameset-to-register :wk "store frameset to register")
        "rg" '(insert-register :wk "insert register")
        "ri" '(insert-register :wk "insert register")
        "rj" '(jump-to-register :wk "jump to register")
        "rn" '(number-to-register :wk "store a number in a register")
        "rs" '(copy-to-register :wk "copy region to register")
        "rx" '(copy-to-register :wk "copy region to register")
        "r+" '(increment-register :wk "augment content of register")
        "r C-@" '(point-to-register :wk "store current point to register")
        "r C-SPC" '(point-to-register :wk "store current point to register")
        "r SPC" '(point-to-register :wk "store current point to register")
        )
      

UI

I suppose here is the most external packages used.

  • Doom themes and modelines
    • doom-themes   external

      A megapack of themes for GNU Emacs. I'm not the kind of guy who enjoys ricing, I look every once in a while for a nice colorscheme and then I just stick it everywhere else. Been a solarized fans most of the time, now I'm trying nord.

      (use-package doom-themes
        :demand t
        :config
        ;; Global settings (defaults)
        (setq doom-themes-enable-bold t    ; if nil, bold is universally disabled
              doom-themes-enable-italic t) ; if nil, italics is universally disabled
        (load-theme 'doom-nord t)
      
        ;; Enable flashing mode-line on errors
        (doom-themes-visual-bell-config)
        ;; Enable custom neotree theme (all-the-icons must be installed!)
        ;; (doom-themes-neotree-config)
        ;; or for treemacs users
        (setq doom-themes-treemacs-theme "doom-atom") ; use "doom-colors" for less minimal icon theme
        (doom-themes-treemacs-config)
        ;; Corrects (and improves) org-mode's native fontification.
        (doom-themes-org-config))
      
    • doom-modeline   external

      A fancy and fast mode-line inspired by minimalism design. I'm using their themes, why not the modeline as well? I used mostly the default settings apart from project detection and indent info for showing the current indentation style (useful when editing yaml files).

      (use-package doom-modeline
        :init
        (doom-modeline-mode 1)
        :config
        (with-eval-after-load 'projectile
          (setq doom-modeline-project-detection 'projectile))
        (setq doom-modeline-indent-info t))
      
      
  • ace-window   external
    (use-package posframe)
    (use-package ace-window
      :init
      (when (display-graphic-p)
        (ace-window-posframe-mode t))
      :config
      (setq aw-keys '(?q ?w ?e ?r ?t ?a ?s ?d ?f ?z ?x ?c ?v)
            aw-background nil
            aw-scope 'frame
            aw-frame-size 2)
      (custom-set-faces
       '(aw-leading-char-face
         ((t (:inherit ace-jump-face-foreground :height 4.0)))))
      (with-eval-after-load 'general
        (+config/leader-window
          "S" 'ace-swap-window))
      :bind ([remap other-window] . ace-window))
    

    Setting aw-scope to frame so it doesn't select other frames.

  • all-the-icons   external

    A utility package to collect various Icon Fonts and propertize them within Emacs.

    (use-package all-the-icons
      :if (display-graphic-p))
    

    This needs a manual intervention from the user to install the fonts (by running the all-the-icons-install-fonts command).

  • page-break-lines
    (use-package page-break-lines)
    
  • dashboard   external

    An extensible emacs dashboard.

    emacs-dashboard.png

    Figure 1: Emacs dashboard

    My sole reason for using dashboard is to avoid seeing an empty scratch buffer each time I opened a new Emacs frame.

    (use-package dashboard
      :demand t
      :config
      (setq initial-buffer-choice (lambda () (get-buffer-create dashboard-buffer-name))
                                   dashboard-startup-banner 'logo
                                   dashboard-center-content t
                                   dashboard-vertically-center-content t
                                   dashboard-navigation-cycle t
                                   dashboard-icon-type 'all-the-icons
                                   dashboard-heading-icons '((recents   . "history")
                                                             (bookmarks . "bookmark")
                                                             (agenda    . "calendar")
                                                             (projects  . "rocket")
                                                             (registers . "database"))
                                   dashboard-items '((recents . 3)
                                                     (bookmarks . 2)
                                                     (projects . 5)
                                                     (agenda . 8)
                                                     (registers . 2)))
      (with-eval-after-load 'nerd-icons
        (setq dashboard-icon-type 'nerd-icons))
      (with-eval-after-load 'projectile 
        (setq dashboard-projects-backend 'projectile))
      (with-eval-after-load 'persp-projectile
        (setq dashboard-projects-switch-function 'projectile-persp-switch-project))
      (dashboard-setup-startup-hook))
    
  • which-key   external

    Emacs package that displays available keybindings in popup. This is one of the top Emacs packages that I must have in my configuration.

    (use-package which-key
      :demand t
      :custom
      (which-key-lighter "")
      (which-key-sort-order #'which-key-key-order-alpha)
      (which-key-sort-uppercase-first nil)
      (which-key-add-column-padding 1)
      (which-key-max-display-columns nil)
      (which-key-min-display-lines 6)
      (which-key-compute-remaps t)
      (which-key-side-window-slot -10)
      (which-key-separator " → ")
      (which-key-allow-evil-operators t)
      (which-key-use-C-h-commands t)
      (which-key-show-remaining-keys t)
      (which-key-show-prefix 'bottom)
      (which-key-show-operator-state-maps t)
      :config
      (which-key-mode)
      (which-key-setup-side-window-bottom)
      (which-key-setup-minibuffer)
      (define-key which-key-mode-map (kbd "C-x <f5>") 'which-key-C-h-dispatch))
    
  • Fonts
    (set-face-attribute 'default nil :family "Iosevka Nerd Font Mono" :height 100)
    (set-face-attribute 'variable-pitch nil :family "Iosevka Nerd Font Mono" :height 100)
    

    the :height value is 1/10 pt.

    • font-core
      (use-package font-core
        :straight nil
        :init
        (global-font-lock-mode t))
      
  • rainbow-mode, rainbow-identifiers, and rainbow-delimiters   external

    Making Emacs more colourful.

    (use-package rainbow-mode
      :hook (prog-mode . rainbow-mode)
      :config
      (setq rainbow-html-colors-major-mode-list
            '(prog-mode conf-mode html-mode css-mode php-mode nxml-mode xml-mode)
            rainbow-html-colors t))
    
    (use-package rainbow-identifiers
      :hook (prog-mode . rainbow-identifiers-mode))
    
    (use-package rainbow-delimiters
      :demand t
      :hook (prog-mode . rainbow-delimiters-mode)
      :config
      (setq rainbow-delimiters-max-face-count 4))
    
  • helpful   external

    Since Emacs is a self-documenting editor. It's certainly nice to have a more contextual help buffers.

    helpful_source.png

    Figure 2: Helpful will also show the source code when available

    helpful_docstring.png

    Figure 3: more prettier docstring

    helpful_bindings.png

    Figure 4: helpful will also display any keybindings related

    (use-package helpful
      :bind
      ("C-h f" . helpful-function)
      ([remap describe-symbol] . helpful-symbol)
      ([remap describe-variable] . helpful-variable)
      ([remap describe-command] . helpful-command)
      ([remap describe-key] . helpful-key)
      :custom
      (helpful-max-buffers 2)
      :config
      (with-eval-after-load 'apropos
        (dolist (fun-bt '(apropos-function apropos-macro apropos-command))
          (button-type-put
           fun-bt 'action
           (lambda (button)
             (helpful-callable (button-get button 'apropos-symbol)))))
        (dolist (var-bt '(apropos-variable apropos-user-option))
          (button-type-put
           var-bt 'action
           (lambda (button)
             (helpful-variable (button-get button 'apropos-symbol))))))
      (with-eval-after-load 'general
        (general-define-key
         "C-h F" 'helpful-function
         :keymaps 'prog-mode-map
         "C-c C-d" 'helpful-at-point)
        ;; (general-nvmap
        ;;   :keymaps 'prog-mode-map
        ;;   "K" '(helpful-at-point :wk "helpful at point")
        ;;   "gr" '(helpful-update :wk "update")))
      ))
    
  • hl-line   builtin

    Enable line highlighting in all buffers.

    (use-package hl-line
      :config
      (global-hl-line-mode 1))
    
  • highlight-quoted
    (use-package highlight-quoted
      :hook ((lisp-mode lisp-interaction-mode) . highlight-quoted-mode))
    
  • hs-mode
    (use-package hideshow
      :hook (prog-mode . hs-minor-mode))
    
  • paren   builtin

    This built-in package is necessary when dealing with Emacs lisp.

    (use-package paren
      :config
      (show-paren-mode 1)
      :custom
      (show-paren-style 'mixed)
      (show-paren-delay 0.1)
      (show-paren-highlight-openparen t)
      (show-paren-when-point-inside-paren t)
      (show-paren-when-point-in-periphery t))
    
  • mouse
    (use-package mouse
      :straight nil
      :config
      (setq mouse-yank-at-point t))
    
  • Base UI
    (use-package emacs
      :straight nil
      :hook ((prog-mode text-mode conf-mode) . display-line-numbers-mode)
      :config
      (setq hscroll-margin 2
            hscroll-step 1
            ;; Emacs spends too much effort recentering the screen if you scroll the
            ;; cursor more than N lines past window edges (where N is the settings of
            ;; `scroll-conservatively'). This is especially slow in larger files
            ;; during large-scale scrolling commands. If kept over 100, the window is
            ;; never automatically recentered. The default (0) triggers this too
            ;; aggressively, so I've set it to 10 to recenter if scrolling too far
            ;; off-screen.
            scroll-conservatively 10
            scroll-margin 0
            scroll-preserve-screen-position t
            ;; Reduce cursor lag by a tiny bit by not auto-adjusting `window-vscroll'
            ;; for tall lines.
            auto-window-vscroll nil
            ;; mouse
            mouse-wheel-scroll-amount '(2 ((shift) . hscroll))
            mouse-wheel-scroll-amount-horizontal 2)
      ;; Show current key-sequence in minibuffer ala 'set showcmd' in vim. Any
      ;; feedback after typing is better UX than no feedback at all.
      (setq echo-keystrokes 0.02)
      ;; Expand the minibuffer to fit multi-line text displayed in the echo-area. This
      ;; doesn't look too great with direnv, however...
      (setq resize-mini-windows 'grow-only)
      ;; Try to keep the cursor out of the read-only portions of the minibuffer.
      (setq minibuffer-prompt-properties '(read-only t intangible t cursor-intangible t face minibuffer-prompt))
      (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)
      ;; Show absolute line numbers for narrowed regions to make it easier to tell the
      ;; buffer is narrowed, and where you are, exactly.
      (setq-default display-line-numbers-widen t) )
    
  • frame
    (use-package frame
      :straight nil
      :hook (after-init . window-divider-mode)
      :config
      (blink-cursor-mode -1)
      (setq window-divider-default-places t
            window-divider-default-bottom-width 1
            window-divider-default-right-width 1))
    
  • window
    (use-package window
      :straight nil
      :config
      (setq split-width-threshold 160
          split-height-threshold nil))
    
  • comint
    (use-package comint
      :straight nil
      :config
      (setq comint-prompt-read-only t
            comint-buffer-maximum-size 2048))
    
  • winner
    (use-package winner
      :straight nil
      :init
      (winner-mode +1)
      :config
      (setq winner-boring-buffers '("*Completions*" "*Compile-Log*" "*inferior-lisp*" "*Fuzzy Completions*"
                                    "*Apropos*" "*Help*" "*cvs*" "*Buffer List*" "*Ibuffer*"
                                    "*esh command on file*")))
    
  • visual-regexp
    (use-package visual-regexp
      :config
      (with-eval-after-load 'general
        (+config/leader-search
          "/" '(:ignore t :wk "regex")
          "/m" 'vr/mc-mark
          "/r" 'vr/replace
          "/q" 'vr/query-replace)))
    
  • symbol-overlay

    Highlight symbols with keymap-enabled overlays.

    (use-package symbol-overlay
      :hook
      (prog-mode . (lambda () symbol-overlay-mode +1))
      :config
      (with-eval-after-load 'general
        (+config/leader-search
          :keymaps 'symbol-overlay-map
          "s" '(:ignore t :wk "symbol-overlay")
          "ss" 'symbol-overlay-put
          "sr" 'symbol-overlay-remove-all)))
    
  • solaire-mode

    If only certain buffers could be so grossly incandescent.

    (use-package solaire-mode
      :init
      (solaire-global-mode +1))
    

Builtins

  • base

    Usually configuration that defined at the C source code.

    (use-package emacs
      :straight nil
      :custom
      (read-buffer-completion-ignore-case t)
      (tab-always-indent nil)
      (visible-bell nil)
      (use-short-answers t)
      (use-dialog-box nil)
      (window-resize-pixelwise nil)
      (frame-resize-pixelwise t)
      (ring-bell-function #'ignore)
      (scroll-preserve-screen-position t)
      (scroll-conservatively 101)
      (fast-but-imprecise-scrolling t)
      (truncate-partial-width-windows nil)
      (tab-width 4)
      (fill-column 80)
      (enable-recursive-minibuffers t)
      (use-file-dialog nil)
      (create-lockfiles nil)
      (delete-by-moving-to-trash t)
      (inhibit-startup-screen t)
      :config
      (when (bound-and-true-p tooltip-mode)
    (tooltip-mode -1))
      (setq completion-ignore-case t
            read-file-name-completion-ignore-case t
            read-buffer-completion-ignore-case t
            load-prefer-newer t
            auto-window-vscroll nil
            inhibit-compacting-font-caches t
            redisplay-skip-fontification-on-input t)
      (set-default 'indicate-empty-lines nil)
      (setq-default truncate-lines t)
      (setq-default x-stretch-cursor nil))
    
  • tooltip
    (use-package tooltip
      :straight nil
      :config
      (when (bound-and-true-p tooltip-mode)
      (tooltip-mode -1)))
    
  • compile
    (use-package compile
      :straight nil
      :hook (compilation-filter . ansi-color-compilation-filter)
      :config
      (setq compilation-always-kill t       ; kill compilation process before starting another
            compilation-ask-about-save nil  ; save all buffers on `compile'
            compilation-scroll-output 'first-error)
      ;; Automatically truncate compilation buffers so they don't accumulate too
      ;; much data and bog down the rest of Emacs.
      (autoload 'comint-truncate-buffer "comint" nil t)
      (add-hook 'compilation-filter-hook #'comint-truncate-buffer))
    
  • files

    Usually configurations related to file manipulations.

    (use-package files
      :straight nil
      :config
      (nconc
       auto-mode-alist
       '(("/LICENSE\\'" . text-mode)
         ("\\.log\\'" . text-mode)
         ("rc\\'" . conf-mode)
         ("\\.\\(?:hex\\|nes\\)\\'" . hexl-mode)))
      :hook
      ((prog-mode text-mode) . auto-save-visited-mode)
      :custom
      (auto-save-visited-interval 10)
      (find-file-suppress-same-file-warnings t)
      (confirm-kill-emacs #'yes-or-no-p) ; confirm when exiting
      (confirm-kill-processes nil) ; don't confirm killing processes
      (revert-without-query (list "."))
      (find-file-visit-truename t) ; `find-file' will visit the actual file
      (make-backup-files nil)
      (version-control t)
      (backup-by-copying t)
      (delete-old-versions t)
      (kept-new-versions 6)
      (kept-old-versions 2)
      (auto-save-include-big-deletions t)
      (auto-save-list-file-prefix (expand-file-name ".autosave/" user-emacs-directory))
      (backup-directory-alist `(("." . ,(expand-file-name ".backup" user-emacs-directory))))
      (auto-mode-case-fold nil)
      (require-final-newline t))
    
  • saveplace

    When closing a file (like when exiting Emacs or killing a buffer), the point (or cursor) in that buffer will be saved. I find this a must-have feature.

    (use-package saveplace
      :init
      (save-place-mode 1)
      :custom
      (save-place-file (expand-file-name "places" user-emacs-directory)))
    
  • autorevert

    Here I'm using global-auto-revert-mode to automatically revert all opened buffer.

    (use-package autorevert
      :hook (focus-in . doom-auto-revert-buffers-h)
      :hook (after-save . doom-auto-revert-buffers-h)
      :custom
      (auto-revert-interval 60)
      (auto-revert-use-notify nil)
      (global-auto-revert-non-file-buffers t)
      (auto-revert-verbose t)
      (auto-revert-stop-on-user-input nil)
      (revert-without-query (list "."))
      :config
      (defun doom-visible-buffer-p (buf)
        "Return non-nil if BUF is visible."
        "Return non-nil if BUF is not visible."
        (not (doom-visible-buffer-p buf)))
      (defun doom-visible-buffers (&optional buffer-list all-frames)
    "Return a list of visible buffers (i.e. not buried)."
    (let ((buffers
           (delete-dups
            (cl-loop for frame in (if all-frames (visible-frame-list) (list (selected-frame)))
                     if (window-list frame)
                     nconc (mapcar #'window-buffer it)))))
      (if buffer-list
          (cl-delete-if (lambda (b) (memq b buffer-list))
                        buffers)
        (delete-dups buffers))))
      (defun doom-auto-revert-buffer-h ()
        "Auto revert current buffer, if necessary."
        (unless (or auto-revert-mode (active-minibuffer-window))
          (let ((auto-revert-mode t))
            (auto-revert-handler))))
      (defun doom-auto-revert-buffers-h ()
        "Auto revert stale buffers in visible windows, if necessary."
        (dolist (buf (doom-visible-buffers))
          (with-current-buffer buf
            (doom-auto-revert-buffer-h)))))
    
  • savehist

    Saving the minibuffer history.

    (use-package savehist
      :init
      (savehist-mode 1)
      :custom
      (savehist-file (expand-file-name "history" user-emacs-directory))
      (savehist-save-minibuffer-history t)
      (savehist-autosave-interval nil)
      (savehist-coding-system 'utf-8)
      (savehist-additional-variables
       '(evil-jumps-history
         command-history
         kill-ring
         register-alist
         mark-ring
         global-mark-ring
         search-ring
         regexp-search-ring)))
    
  • recentf
    (use-package recentf
      :bind ("C-c f" . recentf)
      :commands recentf-open-files
      :hook (dired-mode . doom--recentf-add-dired-directory-h)
      :custom
      (recentf-auto-cleanup t)
      (recentf-max-saved-items 250)
      (recentf-max-menu-items 300)
      (recentf-exclude
       `("/elpa/" ;; ignore all files in elpa directory
         "recentf" ;; remove the recentf load file
         ".*?autoloads.el$"
         "treemacs-persist"
         "company-statistics-cache.el" ;; ignore company cache file
         "/intero/" ;; ignore script files generated by intero
         "/journal/" ;; ignore daily journal files
         ".gitignore" ;; ignore `.gitignore' files in projects
         "/tmp/" ;; ignore temporary files
         "NEWS" ;; don't include the NEWS file for recentf
         "bookmarks"  "bmk-bmenu" ;; ignore bookmarks file in .emacs.d
         "loaddefs.el"
         "^/\\(?:ssh\\|su\\|sudo\\)?:" ;; ignore tramp/ssh files
         (concat "^" (regexp-quote (or (getenv "XDG_RUNTIME_DIR")
                                       "/run")))))
      :config
      (defun doom--recentf-file-truename-fn (file)
        (if (or (not (file-remote-p file))
                (equal "sudo" (file-remote-p file 'method)))
            (abbreviate-file-name (file-truename (tramp-file-name-localname file)))
          file))
      ;; Resolve symlinks, strip out the /sudo:X@ prefix in local tramp paths, and
      ;; abbreviate $HOME -> ~ in filepaths (more portable, more readable, & saves
      ;; space)
      (add-to-list 'recentf-filename-handlers #'doom--recentf-file-truename-fn)
      ;; Text properties inflate the size of recentf's files, and there is
      ;; no purpose in persisting them (Must be first in the list!)
      (add-to-list 'recentf-filename-handlers #'substring-no-properties)
      (defun doom--recentf-add-dired-directory-h ()
        "Add dired directories to recentf file list."
        (recentf-add-file default-directory))
      ;; The most sensible time to clean up your recent files list is when you quit
      ;; Emacs (unless this is a long-running daemon session).
      (setq recentf-auto-cleanup (if (daemonp) 300))
      (add-hook 'kill-emacs-hook #'recentf-cleanup)
      )
    

    Even though I use consult-find-file the actual recentf package is still used (I think).

  • server
    (use-package server
      :if (display-graphic-p)
      :defer 1
      :config
      (unless (server-running-p)
        (server-start))
      (require 'org-protocol))
    
  • prog-mode
    (use-package prog-mode
      :straight nil
      :hook ((prog-mode . prettify-symbols-mode)
             (prog-mode . visual-line-mode)
             ;; (prog-mode . (lambda () (electric-pair-mode 1)))
             )
      :config
      (setq prettify-symbols-alist
            '(("|>" . "▷  ")
              ("<|" . "◁  ")
              ("->>" . "↠  ")
              ("->" . "→  ")
              ("<-" . "←  ")
              ("=>" . "⇒  ")
              ("<=" . "≤ ")
              (">=" . "≥ "))))
    

    Basically, in prog-mode, some combination of characters will look different.

  • bookmark
    (use-package bookmark
      :custom
      (bookmark-save-flag 1)
      (bookmark-default-file (expand-file-name ".bookmark" user-emacs-directory)))
    

    The configuration for bookmark.

  • tramp
    (use-package tramp
      :straight nil
      :config
      (setq tramp-remote-path
            (append '("~/.guix-profile/bin" "~/.guix-profile/sbin"
                      "~/.config/guix/current/bin" "~/.config/guix/current/sbin"
                      "/run/current-system/profile/bin"
                      "/run/current-system/profile/sbin")
                    tramp-remote-path))
      (connection-local-set-profile-variables
       'guix-system
       '((tramp-remote-path . (tramp-own-remote-path))))
      :custom
      (tramp-backup-directory-alist backup-directory-alist)
      (tramp-auto-save-directory (expand-file-name ".tramp-autosave/" user-emacs-directory)))
    

    I'm still figuring out how to do a proper TRAMP connection into a guix-system machine.

  • epg-config
    (use-package epg-config
      :straight nil
      :custom
      (epg-pinentry-mode 'loopback))
    

    This is for Gnupg.

  • tabify
    (use-package tabify
      :straight nil
      :custom
      (tabify-regex "^\t* [ \t]+"))
    
  • simple
    (use-package simple
      :straight nil
      :custom
      (blink-matching-paren nil)
      (save-interprogram-paste-before-kill t)
      (shift-select-mode nil)
      (kill-do-not-save-duplicates t)
      (shift-select-mode nil)
      (set-mark-command-repeat-pop t)
      (indent-tabs-mode nil)
      (column-number-mode t)
      (idle-update-delay 1.0)
      :config
      (with-eval-after-load 'evil
        (evil-set-initial-state #'message-mode 'insert)))
    

    The important part is the Evil integration, which is should be handled already by evil-collection.

  • vc-hooks

    By default visiting / opening symlinked file will ask if we want to visit the actual file.

    (use-package vc-hooks
      :straight nil
      :custom
      (vc-follow-symlinks t))
    
  • dabbrev

    This is part of corfu configuration.

    ;; Use Dabbrev with Corfu!
    (use-package dabbrev
      ;; Swap M-/ and C-M-/
      :bind (("M-/" . dabbrev-completion)
             ("C-M-/" . dabbrev-expand))
      :config
      (add-to-list 'dabbrev-ignored-buffer-regexps '("\\` "
                                                     "\\(TAGS\\|tags\\|ETAGS\\|etags\\|GTAGS\\|GRTAGS\\|GPATH\\)\\(<[0-9]+>\\)?"))
      ;; Since 29.1, use `dabbrev-ignored-buffer-regexps' on older.
      (add-to-list 'dabbrev-ignored-buffer-modes 'doc-view-mode)
      (add-to-list 'dabbrev-ignored-buffer-modes 'pdf-view-mode)
      (add-to-list 'dabbrev-ignored-buffer-modes 'tags-table-mode))
    
  • repeat-mode
    (use-package repeat
      :straight nil
      :init
      (repeat-mode +1))
    
  • so-long
    (use-package so-long
      :straight nil
      :init
      (global-so-long-mode)
      :config
      ;; Emacs 29 introduced faster long-line detection, so they can afford a much
      ;; larger `so-long-threshold' and its default `so-long-predicate'.
      (if (fboundp 'buffer-line-statistics)
          (unless (featurep 'native-compile)
            (setq so-long-threshold 5000))
        ;; reduce false positives w/ larger threshold
        (setq so-long-threshold 400))
      (defun doom-buffer-has-long-lines-p ()
        (unless (bound-and-true-p visual-line-mode)
          (let ((so-long-skip-leading-comments
                 ;; HACK Fix #2183: `so-long-detected-long-line-p' calls
                 ;;   `comment-forward' which tries to use comment syntax, which
                 ;;   throws an error if comment state isn't initialized, leading
                 ;;   to a wrong-type-argument: stringp error.
                 ;; DEPRECATED Fixed in Emacs 28.
                 (bound-and-true-p comment-use-syntax)))
            (so-long-detected-long-line-p))))
      (setq so-long-predicate #'doom-buffer-has-long-lines-p))
    
  • ediff
    (use-package ediff
      :straight nil
      :config
      (setq ediff-diff-options "-w" ; turn off whitespace checking
            ediff-split-window-function #'split-window-horizontally
            ediff-window-setup-function #'ediff-setup-windows-plain))
    

Completion

  • Vertico frameworks

    I call this framework since usually these packages are what people use together, though they work fine separately.

    • vertico
      • Basic configuration
        (use-package vertico
          :straight (vertico
                     :files (:defaults "extensions/*.el")
                     :includes (vertico-buffer
                                vertico-directory
                                vertico-flat
                                vertico-grid
                                vertico-indexed
                                vertico-mouse
                                vertico-multiform
                                vertico-quick
                                vertico-reverse
                                vertico-repeat
                                vertico-suspend
                                vertico-unobstrusive
                                ))
          :hook
          (rfn-eshadow-update-overlay . vertico-directory-tidy)
          (minibuffer-setup . vertico-repeat-save)
          :init
          (vertico-mode)
          ;; Different scroll margin
          (setq vertico-scroll-margin 0)
          ;; Show more candidates
          (setq vertico-count 17)
          ;; Grow and shrink the Vertico minibuffer
          (setq vertico-resize nil)
          ;; Optionally enable cycling for `vertico-next' and `vertico-previous'.
          (setq vertico-cycle t)
          :config
          (advice-add #'tmm-add-prompt :after #'minibuffer-hide-completions)
          (with-eval-after-load 'general
            (general-define-key
             :keymaps 'vertico-map
             "?" 'minibuffer-completion-help
             "M-RET" 'minibuffer-force-complete-and-exit
             "M-TAB" 'minibuffer-complete
             "<tab>" 'vertico-insert
             "<escape>" 'minibuffer-keyboard-quit
             "C-M-n" 'vertico-next-group
             "C-M-p" 'vertico-previous-group
             "C-j" 'vertico-next
             "C-k" 'vertico-previous
             "C-M-j" 'vertico-next-group
             "C-M-k" 'vertico-previous-group
             "DEL" 'vertico-directory-delete-char)
            (general-define-key
             :keymaps 'minibuffer-local-map
             "M-h" 'backward-kill-word)))
        
        ;; A few more useful configurations...
        (use-package emacs
          :custom
          ;; Support opening new minibuffers from inside existing minibuffers.
          (enable-recursive-minibuffers t)
          ;; Emacs 28 and newer: Hide commands in M-x which do not work in the current
          ;; mode.  Vertico commands are hidden in normal buffers. This setting is
          ;; useful beyond Vertico.
          (read-extended-command-predicate #'command-completion-default-include-p)
          :init
          ;; Add prompt indicator to `completing-read-multiple'.
          ;; We display [CRM<separator>], e.g., [CRM,] if the separator is a comma.
          (defun crm-indicator (args)
            (cons (format "[CRM%s] %s"
                          (replace-regexp-in-string
                           "\\`\\[.*?]\\*\\|\\[.*?]\\*\\'" ""
                           crm-separator)
                          (car args))
                  (cdr args)))
          (advice-add #'completing-read-multiple :filter-args #'crm-indicator)
        
          ;; Do not allow the cursor in the minibuffer prompt
          (setq minibuffer-prompt-properties
                '(read-only t cursor-intangible t face minibuffer-prompt))
          (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode))
        
      • Extensions
        • vertico-directory
          (use-package vertico-directory
            :after vertico
            :straight nil
            ;; More convenient directory navigation commands
            :bind (:map vertico-map
                        ("RET" . vertico-directory-enter)
                        ("DEL" . vertico-directory-delete-char)
                        ("M-DEL" . vertico-directory-delete-word))
            ;; Tidy shadowed file names
            :hook (rfn-eshadow-update-overlay . vertico-directory-tidy))
          
        • vertico-quick
          (use-package vertico-quick
            :after vertico
            :straight nil
            :bind (:map vertico-map
                        ("M-q" . vertico-quick-insert)
                        ("C-q" . vertico-quick-exit)))
          
        • vertico-multiform
          (use-package vertico-multiform
            :straight nil
            :init
            (vertico-multiform-mode)
            :config
            (setq vertico-multiform-commands
                  `((describe-symbol (vertico-sort-function . vertico-sort-alpha))
                    (consult-outline buffer ,(lambda (_) (text-scale-set -1)))
                    (org-ctrl-c-ctrl-c flat)))
          
            (defun sort-directories-first (files)
              (setq files (vertico-sort-history-length-alpha files))
              (nconc (seq-filter (lambda (x) (string-suffix-p "/" x)) files)
                     (seq-remove (lambda (x) (string-suffix-p "/" x)) files)))
          
            (setq vertico-multiform-categories
                  '((symbol (vertico-sort-function . vertico-sort-alpha))
                    (file (vertico-sort-function . sort-directories-first))))
            )
          
    • marginalia
      ;; Enable rich annotations using the Marginalia package
      (use-package marginalia
        :bind (:map minibuffer-local-map
                    ("M-A" . marginalia-cycle))
        :init
        (marginalia-mode))
      
    • corfu
      • Basic Configuration
        (use-package corfu
          :custom
          (corfu-cycle t)
          (corfu-auto t)
          (corfu-auto-delay 0.18)
          (corfu-auto-prefix 2)
          (corfu-quit-no-match 'separator)
          (corfu-preselect 'prompt)
          (corfu-count 16)
          (corfu-max-width 120)
          (corfu-on-exact-match nil)
          (corfu-quit-no-match corfu-quit-at-boundary)
          (completion-cycle-threshold 3)
          (text-mode-ispell-word-completion nil)
          :config
          (add-to-list 'completion-category-overrides `(lsp-capf (styles ,@completion-styles)))
          (add-to-list 'corfu-auto-commands #'lispy-colon)
          (add-hook 'evil-insert-state-exit-hook #'corfu-quit)
          (with-eval-after-load 'exwm
            (advice-add #'corfu--make-frame :around
                        (defun +corfu--make-frame-a (oldfun &rest args)
                          (cl-letf (((symbol-function #'frame-parent)
                                     (lambda (frame)
                                       (or (frame-parameter frame 'parent-frame)
                                           exwm-workspace--current))))
                            (apply oldfun args))
                          (when exwm--connection
                            (set-frame-parameter corfu--frame 'parent-frame nil))))
        
            (advice-add #'corfu--popup-redirect-focus :override
                        (defun +corfu--popup-redirect-focus-a ()
                          (redirect-frame-focus corfu--frame
                                                (or (frame-parent corfu--frame)
                                                    exwm-workspace--current)))))
          (defun corfu-enable-always-in-minibuffer ()
            "Enable Corfu in the minibuffer if Vertico/Mct are not active."
            (unless (or (bound-and-true-p mct--active)
                        (bound-and-true-p vertico--input)
                        (eq (current-local-map) read-passwd-map))
              ;; (setq-local corfu-auto nil) ;; Enable/disable auto completion
              (setq-local corfu-echo-delay nil ;; Disable automatic echo and popup
                          corfu-popupinfo-delay nil)
              (corfu-mode 1)))
          (add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)
          :init
          (global-corfu-mode))
        

        This is the basic configuration, with the addition of enabling corfu in the minibuffer.

      • corfu-history
        (use-package corfu-history
          :straight nil
          :hook ((corfu-mode . corfu-history-mode))
          :config
          (with-eval-after-load 'savehist
            (add-to-list 'savehist-additional-variables 'corfu-history)))
        
      • corfu-popupinfo
        (use-package corfu-popupinfo
          :straight nil
          :hook ((corfu-mode . corfu-popupinfo-mode))
          :config
          (setq corfu-popupinfo-delay '(0.5 . 1.0)))
        
    • consult
      • Basic Configuration
        (use-package consult
          :hook (completion-list-mode . consult-preview-at-point-mode)
          :init
          ;; Optionally configure the register formatting. This improves the register
          ;; preview for `consult-register', `consult-register-load',
          ;; `consult-register-store' and the Emacs built-ins.
          (setq register-preview-delay 0.5
                register-preview-function #'consult-register-format)
          ;; Optionally tweak the register preview window.
          ;; This adds thin lines, sorting and hides the mode line of the window.
          (advice-add #'register-preview :override #'consult-register-window)
          ;; Use Consult to select xref locations with preview
          (setq xref-show-xrefs-function #'consult-xref
                xref-show-definitions-function #'consult-xref)
          ;; Configure other variables and modes in the :config section,
          ;; after lazily loading the package.
          :config
          ;; Optionally configure preview. The default value
          ;; is 'any, such that any key triggers the preview.
          ;; (setq consult-preview-key 'any)
          ;; (setq consult-preview-key "M-.")
          ;; (setq consult-preview-key '("S-<down>" "S-<up>"))
          ;; For some commands and buffer sources it is useful to configure the
          ;; :preview-key on a per-command basis using the `consult-customize' macro.
          (consult-customize
           consult-theme :preview-key '(:debounce 0.2 any)
           consult-ripgrep consult-git-grep consult-grep
           consult-bookmark consult-recent-file consult-xref
           consult--source-bookmark consult--source-file-register
           consult--source-recent-file consult--source-project-recent-file
           ;; :preview-key "M-."
           :preview-key '(:debounce 0.4 any))
          ;; Optionally configure the narrowing key.
          ;; Both < and C-+ work reasonably well.
          (setq consult-narrow-key "<") ;; "C-+"
          ;; Optionally make narrowing help available in the minibuffer.
          ;; You may want to use `embark-prefix-help-command' or which-key instead.
          ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help)
          ;; By default `consult-project-function' uses `project-root' from project.el.
          ;; Optionally configure a different project root function.
        ;;;; 1. project.el (the default)
          ;; (setq consult-project-function #'consult--default-project--function)
        ;;;; 2. vc.el (vc-root-dir)
          ;; (setq consult-project-function (lambda (_) (vc-root-dir)))
        ;;;; 3. locate-dominating-file
          ;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git")))
        ;;;; 4. projectile.el (projectile-project-root)
          (with-eval-after-load 'projectile
            (autoload 'projectile-project-root "projectile")
            (setq consult-project-function (lambda (_) (projectile-project-root))))
        ;;;; 5. No project support
          ;; (setq consult-project-function nil))
          (setq consult-line-numbers-widen t
                consult-async-min-input 2
                consult-async-refresh-delay  0.15
                consult-async-input-throttle 0.2
                consult-async-input-debounce 0.1
                )
          )
        

        Apart from the consult-projectile-function, this is pretty much the default.

      • consult-dir
        (use-package consult-dir
          :config
          (with-eval-after-load 'general
            (general-define-key
             :keymaps 'vertico-map
             "C-x C-d" 'consult-dir
             "C-x C-j" 'consult-dir-jump-file)))
        
    • orderless
      • Basic Configuration
        (use-package orderless
          :init
          ;; Configure a custom style dispatcher (see the Consult wiki)
          ;; (setq orderless-style-dispatchers '(+orderless-consult-dispatch orderless-affix-dispatch)
          ;;       orderless-component-separator #'orderless-escapable-split-on-space)
          (setq completion-styles '(orderless basic substring partial-completion)
                completion-category-defaults nil
                completion-category-overrides '((file (styles orderless partial-completion)))
                orderless-component-separator #'orderless-escapable-split-on-space)
          (set-face-attribute 'completions-first-difference nil :inherit nil))
        

        partial-completion and flex are built-ins completion-styles.

    • embark
      (use-package embark
        :defer t
        :bind
        (("C-." . embark-act)         ;; pick some comfortable binding
         ("C-;" . embark-dwim)        ;; good alternative: M-.
         ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings'
        :init
        ;; Optionally replace the key help with a completing-read interface
        (setq prefix-help-command #'embark-prefix-help-command)
        ;; Show the Embark target at point via Eldoc. You may adjust the
        ;; Eldoc strategy, if you want to see the documentation from
        ;; multiple providers. Beware that using this can be a little
        ;; jarring since the message shown in the minibuffer can be more
        ;; than one line, causing the modeline to move up and down:
      
        ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target)
        ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly)
      
        :config
        (setq which-key-use-C-h-commands nil
              prefix-help-command #'embark-prefix-help-command)
        (with-eval-after-load 'general
          (general-define-key
           :keymaps 'override
           "C-." 'embark-act
           "C-;" 'embark-dwim
           "C-h B" 'embark-bindings)
          (general-define-key
           :keymaps 'minibuffer-local-map
           "C-;" 'embark-act
           "C-c C-;" 'embark-export
           "C-c C-l" 'embark-collect)
          (+config/leader-key
            "." 'embark-act))
      
        ;; Hide the mode line of the Embark live/completions buffers
        (add-to-list 'display-buffer-alist
                     '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
                       nil
                       (window-parameters (mode-line-format . none)))))
      

      This is a package that I should learn more, but the configuration is overwhealming.

    • embark-consult
      (use-package embark-consult
        :hook
        (embark-collect-mode . consult-preview-at-point-mode))
      

      consult integration for embark.

    • affe

      Asynchronous Fuzzy Finder for Emacs.

      (use-package affe
        :config
        ;; Manual preview key for `affe-grep'
        (consult-customize affe-grep :preview-key "M-.")
        (defun affe-orderless-regexp-compiler (input _type _ignorecase)
          (setq input (cdr (orderless-compile input)))
          (cons input (apply-partially #'orderless--highlight input t)))
        (setq affe-regexp-compiler #'affe-orderless-regexp-compiler)
        (with-eval-after-load 'general
          (+config/leader-search
            "a" 'affe-grep
            "A" 'affe-find)))
      
    • cape
      (use-package cape
        :defer t
        ;; Bind dedicated completion commands
        ;; Alternative prefix keys: C-c p, M-p, M-+, ...
        :bind (("C-c p p" . completion-at-point) ;; capf
               ("C-c p t" . complete-tag)        ;; etags
               ("C-c p d" . cape-dabbrev)        ;; or dabbrev-completion
               ("C-c p h" . cape-history)
               ("C-c p f" . cape-file)
               ("C-c p k" . cape-keyword)
               ("C-c p s" . cape-elisp-symbol)
               ("C-c p e" . cape-elisp-block)
               ("C-c p a" . cape-abbrev)
               ("C-c p l" . cape-line)
               ("C-c p w" . cape-dict)
               ("C-c p :" . cape-emoji)
               ("C-c p \\" . cape-tex)
               ("C-c p _" . cape-tex)
               ("C-c p ^" . cape-tex)
               ("C-c p &" . cape-sgml)
               ("C-c p r" . cape-rfc1345))
        :hook
        (prog-mode . +corfu-add-cape-file-h)
        ((org-mode markdown-mode) . +corfu-add-cape-elisp-block-h)
        :init
        ;; Add to the global default value of `completion-at-point-functions' which is
        ;; used by `completion-at-point'.  The order of the functions matters, the
        ;; first function returning a result wins.  Note that the list of buffer-local
        ;; completion functions takes precedence over the global list.
        (add-hook 'completion-at-point-functions #'cape-dabbrev)
        (add-hook 'completion-at-point-functions #'cape-file)
        (add-hook 'completion-at-point-functions #'cape-elisp-block)
        (add-hook 'completion-at-point-functions #'cape-history)
        (add-hook 'completion-at-point-functions #'cape-keyword)
        ;;(add-hook 'completion-at-point-functions #'cape-tex)
        ;;(add-hook 'completion-at-point-functions #'cape-sgml)
        ;;(add-hook 'completion-at-point-functions #'cape-rfc1345)
        ;;(add-hook 'completion-at-point-functions #'cape-abbrev)
        (add-hook 'completion-at-point-functions #'cape-dict)
        ;;(add-hook 'completion-at-point-functions #'cape-elisp-symbol)
        ;; (add-hook 'completion-at-point-functions #'cape-line)
        :config
        (setq cape-dabbrev-check-other-buffers t)
        (defun +corfu-add-cape-file-h ()
          (add-hook 'completion-at-point-functions #'cape-file -10 t))
        (defun +corfu-add-cape-elisp-block-h ()
          (add-hook 'completion-at-point-functions #'cape-elisp-block 0 t))
        (advice-add #'lsp-completion-at-point :around #'cape-wrap-noninterruptible)
        (advice-add #'lsp-completion-at-point :around #'cape-wrap-nonexclusive)
        (advice-add #'comint-completion-at-point :around #'cape-wrap-nonexclusive)
        (advice-add #'eglot-completion-at-point :around #'cape-wrap-nonexclusive)
        (advice-add #'pcomplete-completions-at-point :around #'cape-wrap-nonexclusive)
         ;; From the `cape' readme. Without this, Eshell autocompletion is broken on
        ;; Emacs28.
        (when (< emacs-major-version 29)
          (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-silent)
          (advice-add 'pcomplete-completions-at-point :around #'cape-wrap-purify)))
      
    • jinx

      Enchanted Spell Checker. Here I use hunspell as a backend dictionary

      (use-package jinx
        :if (executable-find "enchant-2")
        :hook (emacs-startup . global-jinx-mode)
        :config
        (with-eval-after-load 'general
          (general-define-key
           :keymaps 'override
           [remap ispell-word] 'jinx-correct
           "C-M-$" 'jinx-languages)))
      
    • tempel

      Simple templates for Emacs.

      (use-package tempel
        :init
        (defun tempel-setup-capf ()
          ;; Add the Tempel Capf to `completion-at-point-functions'.
          ;; `tempel-expand' only triggers on exact matches. Alternatively use
          ;; `tempel-complete' if you want to see all matches, but then you
          ;; should also configure `tempel-trigger-prefix', such that Tempel
          ;; does not trigger too often when you don't expect it. NOTE: We add
          ;; `tempel-expand' *before* the main programming mode Capf, such
          ;; that it will be tried first.
          (setq-local completion-at-point-functions
                      (cons #'tempel-expand
                            completion-at-point-functions)))
        (global-tempel-abbrev-mode)
        :hook
        ((conf-mode prog-mode text-mode) . tempel-setup-capf)
        :config
        (with-eval-after-load 'general
          (general-define-key
           :keymaps 'general
           "M-+" 'tempel-complete
           "M-*" 'tempel-insert)))
      
      (use-package tempel-collection
        :after tempel
        :load-path "site-lisp/tempel-collection")
      

Shells

  • eshell

    emacs-eshell.png

    Figure 5: eshell in action

    A shell-like command interpreter implemented in Emacs Lisp.

    (use-package eshell
      :straight nil
      :custom
      (eshell-history-size 10000)
      (eshell-hist-ignore-dups t)
      (eshell-buffer-maximum-lines 10000)
      (eshell-scroll-to-bottom-on-input t)
      (eshell-destroy-buffer-when-process-dies t)
      (eshell-prompt-regexp "^[^\)]*[\)] "))
    
  • eshell-syntax-highlighting
    (use-package eshell-syntax-highlighting
      :defer t
      :after eshell
      :config
      (eshell-syntax-highlighting-global-mode +1))
    
  • sh-script
    (use-package sh-script
      :mode ("\\.bats\\'" . sh-mode)
      :mode ("\\.\\(?:zunit\\|env\\)\\'" . sh-mode)
      :mode ("/bspwmrc\\'" . sh-mode)
      :hook (sh-mode-local-vars . lsp-deferred)
      :hook (sh-mode-local-vars . tree-sitter-mode)
      :config
      (with-eval-after-load 'lsp
        (add-hook 'sh-mode-hook #'lsp-deferred)
        (add-hook 'shell-mode-hook #'lsp-deferred)
        (add-hook 'sh-mode-local-vars-hook #'lsp-deferred))
      (with-eval-after-load 'rainbow-delimiters
        (add-hook 'sh-mode #'rainbow-delimiters-mode))
      ;; recognize function names with dashes in them
      (setq sh-indent-after-continuation 'always)
      (add-to-list 'sh-imenu-generic-expression
                   '(sh (nil "^\\s-*function\\s-+\\([[:alpha:]_-][[:alnum:]_-]*\\)\\s-*\\(?:()\\)?" 1)
                        (nil "^\\s-*\\([[:alpha:]_-][[:alnum:]_-]*\\)\\s-*()" 1)))
      (with-eval-after-load 'smartparens
        (sp-local-pair 'sh-mode "`" "`" :unless '(sp-point-before-word-p sp-point-before-same-p))))
    
  • vterm
    (use-package vterm
      :defer t
      :commands vterm-mode
      :config
      (add-hook 'vterm-mode-hook
                (lambda ()
                  (setq-local global-hl-line-mode nil)
                  (setq-local hscroll-margin 0)))
      (setq vterm-kill-buffer-on-exit t)
      (evil-set-initial-state #'vterm-mode 'insert)
      (with-eval-after-load 'general
        (+config/leader-vterm
          "v" 'vterm))
      )
    
  • multi-vterm
    (use-package multi-vterm
      :defer t
      :after vterm
      :config
      (with-eval-after-load 'general
        (general-iemap
          :keymaps 'vterm-mode-map
          "C-SPC" '(:ignore t :wk "multi vterm")
          "C-SPC a" '(multi-vterm :wk "new vterm buffer")
          "C-SPC n" '(multi-vterm-next :wk "next vterm buffer")
          "C-SPC p" '(multi-vterm-prev :wk "previous vterm buffer")))
      )
    
  • vterm-toggle
    (use-package vterm-toggle
      :defer t
      :after vterm
      :commands vterm-toggle
      :bind
      (:map vterm-mode-map
            ("C-<return>" . vterm-toggle-insert-cd))
      :config
      (setq vterm-toggle-fullscreen-p nil)
      (add-to-list 'display-buffer-alist
                   '((lambda (buffer-or-name _)
                       (let ((buffer (get-buffer buffer-or-name)))
                         (with-current-buffer buffer
                           (or (equal major-mode 'vterm-mode)
                               (string-prefix-p vterm-buffer-name (buffer-name buffer))))))
                     (display-buffer-reuse-window display-buffer-at-bottom)
                     ;;(display-buffer-reuse-window display-buffer-in-direction)
                     ;;display-buffer-in-direction/direction/dedicated is added in emacs27
                     ;;(direction . bottom)
                     (dedicated . t) ;dedicated is supported in emacs27
                     (reusable-frames . visible)
                     (window-height . 0.4))))
    
  • bash-completion
    (use-package bash-completion
      :defer t
      :config
      (bash-completion-setup)
      :hook
      (shell-dynamic-complete-function bash-completion-dynamic-complete))
    
  • emacs-corfu-terminal
    (straight-use-package
     '(corfu-terminal
       :type git
       :repo "https://codeberg.org/akib/emacs-corfu-terminal.git"))
    
    (use-package corfu-terminal
      :unless (display-graphic-p)
      :init
      (corfu-terminal-mode +1))
    

Editing

  • select
    (use-package select
      :straight nil
      :custom
      (select-enable-clipboard t))
    
  • transient-mark-mode

    Highlight region.

    (transient-mark-mode 1)
    
  • delete-selection-mode

    When enabled, typed text replaces the selection if the selection is active. Otherwise, typed text is just inserted at point regardless of any selection.

    (delete-selection-mode 1)
    
  • subword
    (use-package subword
      :straight nil
      :init
      (global-subword-mode 1))
    
  • text-mode
    (use-package text-mode
      :straight nil
      :hook (text-mode . visual-line-mode)
      :config
      (setq-default sentence-end-double-space nil))
    
  • whitespace
    (use-package whitespace
      :straight nil
      :config
      (setq whitespace-line-column nil
            whitespace-style
            '(face indentation tabs tab-mark spaces space-mark newline newline-mark
                   trailing lines-tail)
            whitespace-display-mappings
            '((tab-mark ?\t [?› ?\t])
              (newline-mark ?\n [?¬ ?\n])
              (space-mark ?\  [?·] [?.]))))
    
  • ws-butler
    (use-package ws-butler
      :hook ((prog-mode text-mode) . ws-butler-mode))
    
  • better-jumper
    (use-package better-jumper
      :init
      (global-set-key [remap evil-jump-forward]  #'better-jumper-jump-forward)
      (global-set-key [remap evil-jump-backward] #'better-jumper-jump-backward)
      (global-set-key [remap xref-pop-marker-stack] #'better-jumper-jump-backward)
      (global-set-key [remap xref-go-back] #'better-jumper-jump-backward)
      (global-set-key [remap xref-go-forward] #'better-jumper-jump-forward)
      (better-jumper-mode +1))
    
  • smartparens

    I still have a mixed feeling for this package, but it still easier to configure than the builtin electric-pair-mode. Also, should this be in the Lisp section?

    ;; smartparens
    (use-package smartparens
      :demand t
      :commands sp-pair sp-local-pair sp-with-modes sp-point-in-comment sp-point-in-string
      :init
      (smartparens-global-mode)
      :config
      ;; smartparens recognizes `slime-mrepl-mode', but not `sly-mrepl-mode', so...
      (add-to-list 'sp-lisp-modes 'sly-mrepl-mode)
      (require 'smartparens-config)
      (add-hook 'eval-expression-minibuffer-setup-hook #'smartparens-mode)
      ;; Overlays are too distracting and not terribly helpful. show-parens does
      ;; this for us already (and is faster), so...
      (setq sp-highlight-pair-overlay nil
            sp-highlight-wrap-overlay nil
            sp-highlight-wrap-tag-overlay nil)
      (show-smartparens-global-mode 1)
      (smartparens-global-mode 1)
      ;; Fix usage of ' in Lisp modes
      ;; THANKS: https://github.com/Fuco1/smartparens/issues/286#issuecomment-32324743
      ;; (eval) is used as a hack to quiet Flycheck errors about (sp-with-modes)
      (eval
       '(sp-with-modes sp-lisp-modes
          ;; disable ', it's the quote character!
          (sp-local-pair "'" nil :actions nil)
          ;; also only use the pseudo-quote inside strings where it serve as
          ;; hyperlink.
          (sp-local-pair "`" "'" :when '(sp-in-string-p sp-in-comment-p))
          (sp-local-pair "`" nil
                         :skip-match (lambda (ms mb me)
                                       (cond
                                        ((equal ms "'")
                                         (or (sp--org-skip-markup ms mb me)
                                             (not (sp-point-in-string-or-comment))))
                                        (t (not (sp-point-in-string-or-comment))))))))
      (sp-with-modes '(html-mode sgml-mode nxml-mode web-mode)
        (sp-local-pair "<" ">"))
    
      (defun sp--markdown-skip-asterisk (ms mb me)
        (save-excursion
          (goto-char mb)
          (save-match-data (looking-at "^\\* "))))
    
      (sp-with-modes 'markdown-mode
        (sp-local-pair "*" "*"
                       :unless '(sp-point-after-word-p sp-point-at-bol-p)
                       :skip-match 'sp--markdown-skip-asterisk)
        (sp-local-pair "**" "**")
        (sp-local-pair "_" "_" :unless '(sp-point-after-word-p)))
    
      ;;; org-mode
      (defun sp--org-skip-asterisk (ms mb me)
        (or (and (= (line-beginning-position) mb)
                 (eq 32 (char-after (1+ mb))))
            (and (= (1+ (line-beginning-position)) me)
                 (eq 32 (char-after me)))))
      (defun sp--org-inside-LaTeX (id action context)
        (org-inside-LaTeX-fragment-p))
      (sp-with-modes 'org-mode
        (sp-local-pair "*" "*"
                       :unless '(sp-point-after-word-p sp--org-inside-LaTeX sp-point-at-bol-p)
                       :skip-match 'sp--org-skip-asterisk)
        (sp-local-pair "/" "/" :unless '(sp-point-after-word-p sp--org-inside-LaTeX))
        (sp-local-pair "~" "~" :unless '(sp-point-after-word-p sp--org-inside-LaTeX))
        (sp-local-pair "=" "=" :unless '(sp-point-after-word-p sp--org-inside-LaTeX))
        (sp-local-pair "\\[" "\\]"))
    
      ;; haskell
      (add-to-list 'sp-no-reindent-after-kill-modes 'haskell-mode)
    
      ;; You're likely writing lisp in the minibuffer, therefore, disable these
      ;; quote pairs, which lisps doesn't use for strings:
      (sp-local-pair '(minibuffer-mode minibuffer-inactive-mode) "'" nil :actions nil)
      (sp-local-pair '(minibuffer-mode minibuffer-inactive-mode) "`" nil :actions nil)
      )
    

    The keybinding I got from the example configuration messes up my evil-mode. I have them commented for now.

  • apheleia
    (use-package apheleia
      :defer t
      :init
      (apheleia-global-mode +1))
    
  • expand-region

    Emacs extension to increase selected region by semantic units.

    (use-package expand-region
      :config
      (with-eval-after-load 'general
        (general-define-key
         :keymaps 'global
         "C-=" 'er/expand-region)))
    
  • ispell
    (use-package ispell
      :ensure nil
      :config
      (setq ispell-dictionary "english"
            ispell-personal-dictionary (expand-file-name "dictionary/personal" user-emacs-directory))
      (add-to-list 'ispell-skip-region-alist
                   '(":\\(PROPERTIES\\|LOGBOOK\\):" . ":END:"))
      (add-to-list 'ispell-skip-region-alist
                   '("#\\+BEGIN_SRC" . "#\\+END_SRC"))
      (add-to-list 'ispell-skip-region-alist
                   '("#\\+BEGIN_EXAMPLE" . "#\\+END_EXAMPLE"))
      (when (executable-find "aspell")
        (setq ispell-program-name (executable-find "aspell")
              ispell-extra-args '("--sug-mode=ultra"
                                  "--run-together"))
        (unless ispell-aspell-dict-dir
          (setq ispell-aspell-dict-dir
                (ispell-get-aspell-config-value "dict-dir")))
        (unless ispell-aspell-data-dir
          (setq ispell-aspell-data-dir
                (ispell-get-aspell-config-value "data-dir")))
        (add-hook 'text-mode-hook
                  (lambda ()
                    (setq-local ispell-extra-args
                                (remove "--run-together" ispell-extra-args)))))
      (when (executable-find "hunspell")
        (setq ispell-program-name (executable-find "hunspell")))
      (when (executable-find "enchant-2")
        (setq ispell-program-name (executable-find "enchant-2")
              ispell-cmd-args '("-a"))))
    

IDE stuffs

  • eldoc
    (use-package eldoc
      :hook ((emacs-lisp-mode
              lisp-interaction-mode
              ielm-mode) . eldoc-mode))
    
  • magit   external

    emacs-magit.png

    Figure 6: Emacs magit

    (use-package magit
      :demand t
      :config
      (evil-set-initial-state #'git-commit-mode 'insert)
      (with-eval-after-load 'general 
        (+config/leader-go
          "g" 'magit-status))
      :custom
      (magit-revision-show-gravatars '("^Author:     " . "^Commit:     "))
      (magit-diff-refine-hunk 'all)
      (magit-log-arguments '("-n100" "--graph" "--decorate")))
    
  • projectile   external

    A project interaction library for Emacs, with little external dependency as possible.

    (use-package projectile
      :demand t
      :commands (projectile-project-root
                 projectile-project-name
                 projectile-project-p
                 projectile-locate-dominating-file
                 projectile-relevant-known-projects)
      :bind (([remap evil-jump-to-tag] . projectile-find-tag)
             ([remap find-tag] . projectile-find-tag))
      :hook (dired-before-readin . projectile-track-known-projects-find-file-hook)
      :custom
      (projectile-cache-file (expand-file-name ".projects" user-emacs-directory))
      (projectile-auto-discover nil)
      (projectile-enable-caching (not noninteractive))
      (projectile-globally-ignored-files '("DS_Store" "TAGS"))
      (projectile-globally-ignored-file-suffixes '(".elc" ".pyc" ".o"))
      (projectile-kill-buffers-filter 'kill-only-files)
      (projectile-known-projects-file (expand-file-name ".projectile_projects.eld" user-emacs-directory))
      (projectile-ignored-projects '("~/"))
      (projectile-project-search-path `(,(expand-file-name "Projects" (getenv "HOME"))))
      (projectile-project-root-files-bottom-up
       (append '(".projectile" ".project" ".git")
               (when (executable-find "hg")
                 '(".hg"))
               (when (executable-find "bzr")
                 '(".bzr"))))
      (projectile-project-root-files-top-down-recurring '("Makefile"))
      (compilation-buffer-name-function #'projectile-compilation-buffer-name)
      (compilation-save-buffers-predicate #'projectile-current-project-buffer-p)
      (projectile-git-submodule-command nil)
      (projectile-indexing-method 'hybrid)
      :config
      (global-set-key [remap evil-jump-to-tag] #'projectile-find-tag)
      (global-set-key [remap find-tag]         #'projectile-find-tag)
      (projectile-mode +1)
      (put 'projectile-git-submodule-command 'initial-value projectile-git-submodule-command)
      (with-eval-after-load 'general
        (+config/leader-key
          "SPC" 'projectile-find-file
          "p" '(:keymap projectile-command-map :package projectile :wk "projectile")))
      )
    
  • diff-hl   external

    emacs-diff-hl-indicator.png

    Figure 7: highlighted uncommited changes

    (use-package diff-hl
      :hook (find-file . diff-hl-mode)
      :hook (vc-dir-mode . diff-hl-dir-mode)
      :hook (dired-mode . diff-hl-dired-mode)
      :hook (diff-hl-mode . diff-hl-flydiff-mode)
      :hook (diff-hl-mode . diff-hl-show-hunk-mouse-mode)
      :hook (magit-pre-refresh-hook . diff-hl-magit-pre-refresh)
      :hook (magit-post-refresh-hook . diff-hl-magit-post-refresh)
      :init
      (global-diff-hl-mode)
      :custom
      (vc-git-diff-switches '("--histogram")
                            diff-hl-flydiff-delay 0.5
                            diff-hl-show-staged-changes nil)
      :config
      (when (featurep 'flycheck)
        (setq flycheck-indication-mode 'right-fringe))
      (with-eval-after-load 'general
        (+config/leader-go
          "*" 'diff-hl-show-hunk
          "=" 'diff-hl-diff-goto-hunk
          "[" 'diff-hl-previous-hunk
          "]" 'diff-hl-next-hunk
          "{" 'diff-hl-show-hunk-previous
          "}" 'diff-hl-show-hunk-next)))
    
  • perspective   external

    Useful when I'm working on several projectile projects at once. With each perspective having their own separate buffer list.

    (use-package perspective
      :config
      (setq persp-initial-frame-name "Main"
            persp-suppress-no-prefix-key-warning t)
      (if (featurep 'no-littering)
          (setq persp-state-default-file (expand-file-name ".perspective-state" no-littering-var-directory))
        (setq persp-state-default-file (expand-file-name ".perspective-state" user-emacs-directory)))
      (global-set-key [remap switch-to-buffer] #'persp-switch-to-buffer*)
      (when (featurep 'consult)
        (require 'consult)
        (unless (boundp 'persp-consult-source)
          (defvar persp-consult-source
            (list :name     "Perspective"
                  :narrow   ?s
                  :category 'buffer
                  :state    #'consult--buffer-state
                  :history  'buffer-name-history
                  :default  t
                  :items
                  #'(lambda () (consult--buffer-query :sort 'visibility
                                                      :predicate '(lambda (buf) (persp-is-current-buffer buf t))
                                                      :as #'buffer-name)))))
        (consult-customize consult--source-buffer :hidden t :default nil)
        (add-to-list 'consult-buffer-sources persp-consult-source))
      (with-eval-after-load 'general
        (general-def
          :keymaps 'perspective-map
          "TAB" '(persp-switch-last :wk "switch to last perspective")
          "P" 'projectile-persp-switch-project)
        (+config/leader-key
          "TAB" (general-simulate-key "C-c TAB"
                  :state '(normal visual)
                  :name general-SPC-h-simulates-C-c-TAB
                  :docstring "Simulates C-c TAB in normal and visual mode."
                  :which-key "Perspective"))
        (+config/leader-key
          "C-x" '(persp-switch-to-scratch-buffer :wk "switch to scratch buffer")))
      :init
      (customize-set-variable 'persp-mode-prefix-key (kbd "C-c TAB"))
      (unless (equal persp-mode t)
        (persp-mode 1))
      :bind (([remap switch-to-buffer] . persp-switch-to-buffer*)
             ([remap kill-buffer] . persp-kill-buffer*))
      :hook (kill-emacs . persp-state-save)
      )
    
  • persp-projectile   external
    (use-package persp-projectile
      :after perspective
      :commands projectile-persp-switch-project
      :config
      (with-eval-after-load 'general
        (general-def
          :keymaps 'perspective-map
          "P" 'projectile-persp-switch-project))
      )
    
  • git-link   external
    (use-package git-link
      :demand t
      :commands (git-link git-link-commit git-link-homepage)
      :config
      (with-eval-after-load 'general
        (+config/leader-go
          "G" '(:ignore t :wk "git")
          "Gl" 'git-link
          "Gh" 'git-link-homepage
          "Gc" 'git-link-commit))
      )
    
  • git-messenger   external

    emacs-git-messenger.png

    Figure 8: git messenger popup message

    (use-package git-messenger
      :config
      (with-eval-after-load 'general
        (+config/leader-go
          "Gm" 'git-messenger:popup-message))
      :custom
      ;; Enable magit-show-commit instead of pop-to-buffer
      (git-messenger:use-magit-popup t)
      (git-messenger:show-detail t))
    
  • git-timemachine   external
    (use-package git-timemachine
      :after magit
      :config
      (with-eval-after-load 'general
        (+config/leader-go
          "Gt" 'git-timemachine-toggle))
      )
    
  • org-project-capture   external
    (use-package org-project-capture
      :bind (("C-c n p" . org-project-capture-project-todo-completing-read))
      :config
      (progn
        (setq org-project-capture-backend
              (make-instance 'org-project-capture-projectile-backend))  ; Replace with your backend of choice
        (setq org-project-capture-projects-file (expand-file-name "projects.org" org-directory))
        (org-project-capture-single-file)))
    
  • lsp   external

    emacs-lsp.png

    Figure 9: Emacs lsp in action

    • Basic configuration
      • LSP
        (use-package lsp-mode
          :init
          (setq lsp-keymap-prefix "C-c C-l")
          (defun my/orderless-dispatch-flex-first (_pattern index _total)
            (and (eq index 0) 'orderless-flex))
          (defun my/lsp-mode-setup-completion ()
            (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
                  '(orderless)))
          (add-hook 'orderless-style-dispatchers #'my/orderless-dispatch-flex-first nil 'local)
          (when (featurep 'cape)
            (setq-local completion-at-point-functions (list (cape-capf-buster #'lsp-completion-at-point))))
          :hook
          (lsp-mode . lsp-enable-which-key-integration)
          (lsp-completion-mode . my/lsp-mode-setup-completion)
          ;; ((c-mode c++-mode xml-mode python-mode
          ;;          yaml-mode toml-mode python-mode-local-vars
          ;;          lua-mode jinja2-mode markdown-mode
          ;;          json-mode html-mode
          ;;          web-mode html-mode-local-vars-hook
          ;;          web-mode-local-vars-hook nxml-mode-local-vars-hook
          ;;          scss-mode-local-vars css-mode-local-vars
          ;;          less-css-mode-local-vars css-mode
          ;;          typescript-mode javascript-mode
          ;;          js2-mode typescript-tsx-mode) . lsp-deferred)
          :config
          (setq lsp-toml-command
                (if (file-exists-p (expand-file-name ".cargo/bin/taplo" "~"))
                    (expand-file-name ".cargo/bin/taplo" "~")
                  "taplo")
                lsp-rust-rls-server-command "rls"
                ;;lsp-eldoc-render-all t
                lsp-enable-snippet nil
                lsp-enable-indentation nil
                lsp-prefer-flymake nil
                lsp-keep-workspace-alive nil
                ;; lsp-modeline-code-actions-segments '(count icon name)
                )
          (when (featurep 'exwm)
            (advice-add #'corfu--make-frame :around
                        (defun +corfu--make-frame-a (oldfun &rest args)
                          (cl-letf (((symbol-function #'frame-parent)
                                     (lambda (frame)
                                       (or (frame-parameter frame 'parent-frame)
                                           exwm-workspace--current))))
                            (apply oldfun args))
                          (when exwm--connection
                            (set-frame-parameter corfu--frame 'parent-frame nil))))
        
            (advice-add #'corfu--popup-redirect-focus :override
                        (defun +corfu--popup-redirect-focus-a ()
                          (redirect-frame-focus corfu--frame
                                                (or (frame-parent corfu--frame)
                                                    exwm-workspace--current)))))
          (with-eval-after-load 'general
            (+config/leader-key
              :keymaps 'lsp-mode-map
              "l" (general-simulate-key "C-c C-l"
                    :name general-SPC-l-simulates-C-c-C-l
                    :docstring "Simulates C-c C-l"
                    :which-key "LSP")))
          (general-nvmap
            :keymaps 'lsp-mode-map
            "K" 'lsp-describe-thing-at-point)
          :custom
          (lsp-completion-provider :none)
          )
        
    • lsp-ui
      (use-package lsp-ui
        :hook (lsp-mode . lsp-ui-mode)
        :demand t
        :init
        (setq lsp-ui-sideline-enable t
              lsp-ui-sideline-update-mode 'line
              lsp-ui-sideline-show-code-actions t
              lsp-ui-sideline-show-hover t
              lsp-ui-doc-enable t
              lsp-ui-doc-include-signature t
              lsp-ui-doc-show-with-cursor t
              lsp-eldoc-enable-hover nil ; Disable eldoc displays in minibuffer
              lsp-ui-doc-position 'at-point
              lsp-ui-imenu-enable t
              lsp-ui-sideline-ignore-duplicate t
              lsp-ui-peek-enable t)
        :config
        (define-key lsp-ui-mode-map [remap xref-find-definitions] #'lsp-ui-peek-find-definitions)
        (define-key lsp-ui-mode-map [remap xref-find-references] #'lsp-ui-peek-find-references)
        (add-to-list 'lsp-language-id-configuration '(jinja2-mode . "jinja2") t)
        (add-to-list 'lsp-language-id-configuration '("\\.js2$" . "jinja2") t)
        (with-eval-after-load 'general
          (general-define-key
           :keymaps '(lsp-ui-mode-map)
           [remap xref-find-definitions] 'lsp-ui-peek-find-definitions
           [remap xref-find-references] 'lsp-ui-peek-find-references
           "M-." 'lsp-ui-peek-find-definitions
           "M-?" 'lsp-ui-peek-find-references))
        )
      
    • consult-lsp
      (use-package consult-lsp
        :after consult lsp
        :bind
        (:map lsp-mode-map
              ([remap xref-find-apropos] . consult-lsp-symbols)))
      
  • dap-mode
    (use-package dap-mode
      :config
      (dap-auto-configure-mode)
      (dap-ui-mode 1)
      (dap-tooltip-mode 1)
      (dap-ui-controls-mode 1))
    
  • Tree-sitter
    (use-package tree-sitter-langs)
    (use-package tree-sitter-indent)
    
    (use-package tree-sitter
      :defer t
      :config
      (require 'tree-sitter-langs)
      (setq tree-sitter-debug-jump-buttons t
            ;; and this highlights the entire sub tree in your code
            tree-sitter-debug-highlight-jump-region t))
    
    (use-package evil-textobj-tree-sitter
      :defer t
      :init
      (with-eval-after-load 'tree-sitter
        (require 'evil-textobj-tree-sitter))
      :config
      (defvar +tree-sitter-inner-text-objects-map (make-sparse-keymap))
      (defvar +tree-sitter-outer-text-objects-map (make-sparse-keymap))
      (defvar +tree-sitter-goto-previous-map (make-sparse-keymap))
      (defvar +tree-sitter-goto-next-map (make-sparse-keymap))
    
      (defvar +tree-sitter-hl-enabled-modes '(not web-mode typescript-tsx-mode)
        "A list of major modes which should be highlighted by tree-sitter.
    
    If this list begins with `not', then it negates the list.
    If it is t, it is enabled in all modes.
    If nil, it is disabled in all modes")
    
      (defun tree-sitter! ()
        "Dispatch to turn on tree sitter.
    
    Used as a hook function which turns on `tree-sitter-mode'
    and selectively turn on `tree-sitter-hl-mode'.
    according to `+tree-sitter-hl-enabled-modes'"
        (turn-on-tree-sitter-mode)
        ;; conditionally enable `tree-sitter-hl-mode'
        (let ((mode (bound-and-true-p tree-sitter-hl-mode)))
          (when-let (mode (if (pcase +tree-sitter-hl-enabled-modes
                                (`(not . ,modes) (not (memq major-mode modes)))
                                ((and `(,_ . ,_) modes) (memq major-mode modes))
                                (bool bool))
                              (unless mode +1)
                            (if mode -1)))
            (tree-sitter-hl-mode mode))))
    
    ;;;###autodef (fset 'set-tree-sitter-lang! #'ignore)
      (defun set-tree-sitter-lang! (mode lang)
        "Associate LANG with major MODE."
        (after! tree-sitter-langs
                (add-to-list 'tree-sitter-major-mode-language-alist (cons mode lang))))
    
      (defun +tree-sitter-get-textobj (group &optional query)
        "A wrapper around `evil-textobj-tree-sitter-get-textobj' to
    prevent eager expansion."
        (eval `(evil-textobj-tree-sitter-get-textobj ,group ,query)))
    
    ;;;###autoload
      (defun +tree-sitter-goto-textobj (group &optional previous end query)
        "Thin wrapper that returns the symbol of a named function, used in keybindings."
        (let ((sym (intern (format "+goto%s%s-%s" (if previous "-previous" "") (if end "-end" "") group))))
          (fset sym (lambda ()
                      (interactive)
                      (evil-textobj-tree-sitter-goto-textobj group previous end query)))
          sym))
    
      (evil-define-key '(visual operator) 'tree-sitter-mode
        "i" +tree-sitter-inner-text-objects-map
        "a" +tree-sitter-outer-text-objects-map)
      (evil-define-key 'normal 'tree-sitter-mode
        "[g" +tree-sitter-goto-previous-map
        "]g" +tree-sitter-goto-next-map)
    
      (with-eval-after-load 'general
        (general-define-key
         :keymaps '+tree-sitter-inner-text-objects-map
         "A" (+tree-sitter-get-textobj '("parameter.inner" "call.inner"))
         "f" (+tree-sitter-get-textobj "function.inner")
         "F" (+tree-sitter-get-textobj "call.inner")
         "C" (+tree-sitter-get-textobj "class.inner")
         "v" (+tree-sitter-get-textobj "conditional.inner")
         "l" (+tree-sitter-get-textobj "loop.inner")
         :keymaps '+tree-sitter-outer-text-objects-map
         "A" (+tree-sitter-get-textobj '("parameter.outer" "call.outer"))
         "f" (+tree-sitter-get-textobj "function.outer")
         "F" (+tree-sitter-get-textobj "call.outer")
         "C" (+tree-sitter-get-textobj "class.outer")
         "c" (+tree-sitter-get-textobj "comment.outer")
         "v" (+tree-sitter-get-textobj "conditional.outer")
         "l" (+tree-sitter-get-textobj "loop.outer")
         :keymaps '+tree-sitter-goto-previous-map
         "a" (+tree-sitter-goto-textobj "parameter.outer" t)
         "f" (+tree-sitter-goto-textobj "function.outer" t)
         "F" (+tree-sitter-goto-textobj "call.outer" t)
         "C" (+tree-sitter-goto-textobj "class.outer" t)
         "c" (+tree-sitter-goto-textobj "comment.outer" t)
         "v" (+tree-sitter-goto-textobj "conditional.outer" t)
         "l" (+tree-sitter-goto-textobj "loop.outer" t)
         :keymaps '+tree-sitter-goto-next-map
         "a" (+tree-sitter-goto-textobj "parameter.outer")
         "f" (+tree-sitter-goto-textobj "function.outer")
         "F" (+tree-sitter-goto-textobj "call.outer")
         "C" (+tree-sitter-goto-textobj "class.outer")
         "c" (+tree-sitter-goto-textobj "comment.outer")
         "v" (+tree-sitter-goto-textobj "conditional.outer")
         "l" (+tree-sitter-goto-textobj "loop.outer"))))
    
  • gptel   external
    (use-package gptel
      :after password-store
      :config
      (setq gptel-backend
            (gptel-make-gemini "Gemini" :key (password-store-get "google.com/gemini_api_key") :stream t)
            gptel-default-mode #'org-mode))
    

    An LLM client for Emacs, I'm trying it with google's gemini. This package is already good on its own. But the other reason is I'm also trying out consult-web, which integrates search results from various sources.

  • magit-gptcommit

    Magit commit with the help of gpt.

    (use-package magit-gptcommit
      :demand t
      :hook (gptel-mode . visual-line-mode)
      :after gptel magit password-store
      :config
      (setq magit-gptcommit-llm-provider `(make-llm-gemini :key ,(password-store-get "google.com/gemini_api_key")))
      (magit-gptcommit-mode 1)
      (magit-gptcommit-status-buffer-setup)
      (with-eval-after-load 'general
        (+config/leader-go
          "c" '(:ignore t :wk "chat")
          "cm" 'gptel-menu
          "cc" 'gptel
          "cs" 'gptel-send
          "cA" 'gptel-abort))
      :bind (:map git-commit-mode-map
                  ("C-c C-g" . magit-gptcommit-commit-accept)))
    
  • flycheck
    (use-package flycheck
      :init
      (global-flycheck-mode +1)
      :commands flycheck-list-errors flycheck-buffer
      :config
      (setq flycheck-emacs-lisp-load-path 'inherit)
      ;; Rerunning checks on every newline is a mote excessive.
      (delq 'new-line flycheck-check-syntax-automatically)
      ;; And don't recheck on idle as often
      (setq flycheck-idle-change-delay 1.0)
      ;; For the above functionality, check syntax in a buffer that you switched to
      ;; only briefly. This allows "refreshing" the syntax check state for several
      ;; buffers quickly after e.g. changing a config file.
      (setq flycheck-buffer-switch-check-intermediate-buffers t)
      ;; Display errors a little quicker (default is 0.9s)
      (setq flycheck-display-errors-delay 0.25)
      (with-eval-after-load 'general
        (+config/leader-key "!"
          (general-simulate-key "C-c !"
            :state '(normal visual)
            :name general-SPC-!-simulates-C-c-!
            :docstring "Simulates C-c ! in normal and visual mode."
            :which-key "flycheck"))
        (general-nvmap
          :keymaps 'flycheck-error-list-mode-map
          "C-n" 'flycheck-error-list-next-error
          "C-p" 'flycheck-error-list-previous-error
          "j" 'flycheck-error-list-next-error
          "k" 'flycheck-error-list-previous-error
          "RET" 'flycheck-error-list-goto-error
          "[return]" 'flycheck-error-list-goto-error)))
    
    • flycheck-popup-tip
      (use-package flycheck-popup-tip
        :commands flycheck-popup-tip-show-popup flycheck-popup-tip-delete-popup
        :hook (flycheck-mode . flycheck-popup-tip-mode))
      
    • flycheck-posframe
      (use-package flycheck-posframe
        :after flycheck
        :config (add-hook 'flycheck-mode-hook #'flycheck-posframe-mode)
        (setq flycheck-posframe-warning-prefix "⚠ "
              flycheck-posframe-info-prefix "ⓘ "
              flycheck-posframe-error-prefix "⮾ "))
      
    • consult-flycheck
      (use-package consult-flycheck
        :after (consult flycheck)
        :config
        (with-eval-after-load 'general
          (+config/leader-search
            "f" 'consult-flycheck)))
      

Emacs org-mode

  • org-mode core package
    (use-package org
      :demand t
      :commands org-tempo
      :preface
      (if (not +config/org-directory)
          (cond
           ((file-directory-p
             (expand-file-name "Dropbox/org" (getenv "HOME")))
            (setq org-directory (expand-file-name "Dropbox/org" (getenv "HOME"))))
           ((file-directory-p
             (expand-file-name "Sync/org" (getenv "HOME")))
            (setq org-directory (expand-file-name "Sync/org" (getenv "HOME"))))
           ((file-directory-p
             (expand-file-name "Documents/google-drive/org" (getenv "HOME")))
            (setq org-directory (expand-file-name "Documents/google-drive/org" (getenv "HOME")))))
        (customize-set-variable 'org-directory +config/org-directory))
      :hook ((org-mode . org-indent-mode)
             (org-mode . +config/org-prettify-symbols)
             (org-mode . (lambda () (electric-indent-local-mode -1)))
             (org-mode . variable-pitch-mode))
      :config
      (global-set-key (kbd "C-c l") #'org-store-link)
      (global-set-key (kbd "C-c a") #'org-agenda)
      (global-set-key (kbd "C-c c") #'org-capture)
      (when(file-directory-p (expand-file-name "braindump/org" org-directory))
        (customize-set-variable '+config/org-roam-directory
                                (expand-file-name "braindump/org" org-directory)))
      (when (file-directory-p (expand-file-name "alexforsale.github.io" org-directory))
        (customize-set-variable '+config/blog-directory
                                (expand-file-name "alexforsale.github.io" org-directory)))
      (modify-syntax-entry ?= "$" org-mode-syntax-table)
      (modify-syntax-entry ?~ "$" org-mode-syntax-table)
      (modify-syntax-entry ?_ "$" org-mode-syntax-table)
      (modify-syntax-entry ?+ "$" org-mode-syntax-table)
      (modify-syntax-entry ?/ "$" org-mode-syntax-table)
      (modify-syntax-entry ?* "$" org-mode-syntax-table)
      (add-to-list 'org-modules 'org-tempo t)
      (add-to-list 'org-structure-template-alist '("sh" . "src sh"))
      (add-to-list 'org-structure-template-alist '("co" . "src conf"))
      (add-to-list 'org-structure-template-alist '("lisp" . "src lisp"))
      (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
      (add-to-list 'org-structure-template-alist '("sc" . "src scheme"))
      (add-to-list 'org-structure-template-alist '("ts" . "src typescript"))
      (add-to-list 'org-structure-template-alist '("py" . "src python"))
      (add-to-list 'org-structure-template-alist '("go" . "src go"))
      (add-to-list 'org-structure-template-alist '("yaml" . "src yaml"))
      (add-to-list 'org-structure-template-alist '("js" . "src js"))
      (add-to-list 'org-structure-template-alist '("json" . "src json"))
      (add-to-list 'org-structure-template-alist '("n" . "note"))
      (org-babel-do-load-languages
       'org-babel-load-languages
       '((emacs-lisp . t)
         (awk . t)
         (C . t)
         (css . t)
         (calc . t)
         (ditaa . t) ; needs the `ditaa' package
         (dot . t ) ; `graphviz'
         (screen . t)
         (haskell . t)
         (java . t)
         (js . t)
         (latex . t)
         (lisp . t)
         (lua . t)
         (org . t)
         (perl . t)
         (plantuml . t)
         (python .t)
         (ruby . t)
         (shell . t)
         (sed . t)
         (scheme . t)
         (sql . t)
         (sqlite . t)))
      (setq-default org-use-sub-superscripts '{})
      (add-to-list 'org-babel-tangle-lang-exts '("js" . "js"))
      (defun +config/org-prettify-symbols ()
        (push '("[ ]" . "☐") prettify-symbols-alist)
        (push '("[X]" . "☑") prettify-symbols-alist)
        (prettify-symbols-mode))
      (require 'org-tempo)
      :custom
      (org-highlight-latex-and-related '(native script entities))
      (org-replace-disputed-keys t)
      (org-indirect-buffer-display 'current-window)
      (org-enforce-todo-dependencies t)
      (org-fontify-whole-heading-line t)
      (org-return-follows-link t)
      (org-mouse-1-follows-link t)
      (org-image-actual-width nil)
      (org-adapt-indentation nil)
      (org-startup-indented t)
      (org-link-descriptive nil)
      (org-log-done 'time)
      (org-log-refile 'time)
      (org-log-redeadline 'time)
      (org-log-reschedule 'time)
      (org-log-into-drawer t)
      (org-clone-delete-id t)
      (org-default-notes-file (expand-file-name "notes.org" org-directory))
      (org-insert-heading-respect-content nil)
      (org-pretty-entities t)
      (org-use-property-inheritance t)
      (org-priority-highest ?A)
      (org-priority-lowest ?D)
      (org-priority-default ?B)
      (org-todo-keywords
       '((sequence
          "TODO(t!)"  ; A task that needs doing & is ready to do
          "NEXT(n!)"  ; Tasks that can be delayed
          "PROG(p!)"  ; A task that is in progress
          "WAIT(w!)"  ; Something external is holding up this task
          "HOLD(h!)"  ; This task is paused/on hold because of me
          "|"
          "DONE(d!)"  ; Task successfully completed
          "DELEGATED(l!)" ; Task is delegated
          "KILL(k!)") ; Task was cancelled, aborted or is no longer applicable
         ))
      (org-todo-keyword-faces
       '(("PROG" . (:foreground "#5e81ac" :weight bold))
         ("WAIT" . (:foreground "#ebcb8b" :weight bold))
         ("HOLD" . (:foreground "#d08770" :weight bold))
         ("NEXT" . (:foreground "#81a1c1" :weight bold))
         ("DELEGATED" . "#8fbcbb")
         ("KILL" . "#a3be8c"))))
    
  • org-entities
    (use-package org-entities
      :straight nil
      :config
      (setq org-entities-user
            '(("flat"  "\\flat" nil "" "" "266D" "♭")
              ("sharp" "\\sharp" nil "" "" "266F" "♯"))))
    
  • ob-diagrams
    (use-package ob-diagrams
      :after org
      :config
      (add-to-list 'org-babel-load-languages '(diagrams . t)))
    
  • org-faces
    (use-package org-faces
      :straight nil
      :custom
      (org-fontify-quote-and-verse-blocks t))
    
  • org-archive
    (use-package org-archive
      :straight nil
      :after org
      :custom
      (org-archive-tag "archive")
      (org-archive-subtree-save-file-p t)
      (org-archive-mark-done t)
      (org-archive-reversed-order t)
      (org-archive-location (concat (expand-file-name "archives.org" org-directory) "::datetree/* Archived Tasks")))
    
  • org-capture
    (use-package org-capture
      :after org
      :straight nil
      :demand t
      :config
      (org-capture-put :kill-buffer t)
      (setq org-capture-templates ;; this is the default from `doom'.
            `(("i" "Inbox - Goes Here first!" entry
               (file+headline ,(expand-file-name "inbox.org" org-directory) "Inbox")
               "** %?\n%i\n%a" :prepend t)
              ("r" "Request" entry (file+headline ,(expand-file-name "inbox.org" org-directory) "Request")
               (file ,(expand-file-name "request.template" org-directory)))
              ("l" "Links" entry
               (file+headline ,(expand-file-name "links.org" org-directory) "Links")))))
    
  • org-refile
    (use-package org-refile
      :straight nil
      :after org
      :hook (org-after-refile-insert . save-buffer)
      :custom
      (org-refile-targets
       `((,(expand-file-name "projects.org" org-directory) :maxlevel . 1)
         (,(expand-file-name "routines.org" org-directory) :maxlevel . 1)
         (,(expand-file-name "personal.org" org-directory) :maxlevel . 1)))
      (org-refile-use-outline-path 'file)
      (org-outline-path-complete-in-steps nil))
    
  • org-fold
    (use-package org-fold
      :straight nil
      :after org org-contrib
      :custom
      (org-catch-invisible-edits 'smart))
    
  • org-id
    (use-package org-id
      :straight nil
      :after org
      :custom
      (org-id-locations-file-relative t)
      (org-id-link-to-org-use-id 'create-if-interactive-and-no-custom-id))
    
  • org-num
    (use-package org-num
      :straight nil
      :after org
      :custom
      (org-num-face '(:inherit org-special-keyword :underline nil :weight bold))
      (org-num-skip-tags '("noexport" "nonum")))
    
  • org-crypt
    (use-package org-crypt ; built-in
      :straight nil
      :after org
      :commands org-encrypt-entries org-encrypt-entry org-decrypt-entries org-decrypt-entry
      ;;:hook (org-reveal-start . org-decrypt-entry)
      :preface
      ;; org-crypt falls back to CRYPTKEY property then `epa-file-encrypt-to', which
      ;; is a better default than the empty string `org-crypt-key' defaults to.
      (defvar org-crypt-key nil)
      (with-eval-after-load 'org
        (add-to-list 'org-tags-exclude-from-inheritance "crypt")))
    
  • org-attach
    (use-package org-attach
      :straight nil
      :after org
      :commands (org-attach-new
                 org-attach-open
                 org-attach-open-in-emacs
                 org-attach-reveal-in-emacs
                 org-attach-url
                 org-attach-set-directory
                 org-attach-sync)
      :config
      (unless org-attach-id-dir
        (setq-default org-attach-id-dir (expand-file-name ".attach/" org-directory)))
      (with-eval-after-load 'projectile
        (add-to-list 'projectile-globally-ignored-directories org-attach-id-dir))
      :custom
      (org-attach-auto-tag nil))
    
  • org-agenda
    (use-package org-agenda
      :straight nil
      :after org
      :custom
      (org-agenda-files (list (concat org-directory "/")))
      (org-agenda-file-regexp "\\`[^.].*\\.org\\|[0-9]+$\\'")
      (org-agenda-include-inactive-timestamps t)
      (org-agenda-window-setup 'only-window)
      (org-stuck-projects '("+{project*}-killed-Archives/-DONE-KILL-DELEGATED"
                            ("TODO" "NEXT" "IDEA" "PROG")
                            nil ""))
      :config
      (with-eval-after-load 'evil
        (evil-set-initial-state #'org-agenda-mode 'normal))
      (setq org-agenda-custom-commands
            `(("w" "Work Agenda and all TODOs"
               ((agenda ""
                        ((org-agenda-span 1)
                         (org-agenda-start-on-weekday t)
                         (org-agenda-block-separator nil)
                         (org-agenda-use-time-grid t)
                         (org-agenda-day-face-function (lambda (date) 'org-agenda-date))
                         (org-agenda-format-date "%A %-e %B %Y")
                         (org-agenda-overriding-header "\nToday\n")))
                (tags-todo "TODO=\"TODO\"|\"NEXT\""
                           ((org-agenda-block-separator nil)
                            (org-agenda-skip-function '(org-agenda-skip-if-todo 'nottodo 'done))
                            (org-agenda-use-time-grid nil)
                            (org-agenda-overriding-header "\nIncomplete\n")))
                (agenda ""
                        ((org-agenda-span 7)
                         (org-agenda-start-on-weekday 1)
                         (org-agenda-block-separator nil)
                         (org-agenda-use-time-grid nil)
                         (org-agenda-overriding-header "\nWeekly\n"))))
               ((org-agenda-tag-filter-preset '("-personal" "-home"))))
              ("h" "Home Agenda and all personal TODOs"
               ((agenda ""
                        ((org-agenda-span 1)
                         (org-agenda-start-on-weekday t)
                         (org-agenda-block-separator nil)
                         (org-agenda-use-time-grid t)
                         (org-agenda-day-face-function (lambda (date) 'org-agenda-date))
                         (org-agenda-format-date "%A %-e %B %Y")
                         (org-agenda-overriding-header "\nToday\n")))
                (tags-todo "TODO=\"TODO\"|\"NEXT\""
                           ((org-agenda-block-separator nil)
                            (org-agenda-skip-function '(org-agenda-skip-if-todo 'nottodo 'done))
                            (org-agenda-use-time-grid nil)
                            (org-agenda-overriding-header "\nIncomplete\n")))
                (agenda ""
                        ((org-agenda-span 7)
                         (org-agenda-start-on-weekday 1)
                         (org-agenda-block-separator nil)
                         (org-agenda-use-time-grid nil)
                         (org-agenda-overriding-header "\nWeekly\n"))))
               ((org-agenda-tag-filter-preset '("+personal")))))))
    
  • org-clock
    (use-package org-clock
      :straight nil
      :after org
      :commands org-clock-save
      :hook (kill-emacs . org-clock-save)
      :custom
      (org-persist 'history)
      (org-clock-in-resume t)
      (org-clock-out-remove-zero-time-clocks t)
      (org-clock-history-length 20)
      (org-show-notification-handler "notify-send")
      (org-agenda-skip-scheduled-if-deadline-is-shown t)
      :config
      (org-clock-persistence-insinuate))
    
  • org-timer
    (use-package org-timer
      :straight nil
      :config
      (setq org-timer-format "Timer :: %s"))
    
  • org-eldoc
    (use-package org-eldoc
      :straight nil
      :after org org-contrib
      :config
      (puthash "org" #'ignore org-eldoc-local-functions-cache)
      (puthash "plantuml" #'ignore org-eldoc-local-functions-cache)
      (puthash "python" #'python-eldoc-function org-eldoc-local-functions-cache)
      :custom
      (org-eldoc-breadcrumb-separator " → "))
    
  • org-superstar   external
    (use-package org-superstar
      :hook (org-mode . org-superstar-mode)
      :custom
      (org-superstar-leading-bullet ?\s)
      (org-superstar-leading-fallback ?\s)
      (org-hide-leading-stars nil)
      (org-indent-mode-turns-on-hiding-stars nil)
      (org-superstar-todo-bullet-alist
       '(("TODO" . 9744)
         ("[ ]"  . 9744)
         ("DONE" . 9745)
         ("[X]"  . 9745)))
      :config
      (org-superstar-configure-like-org-bullets))
    
  • org-fancy-priority   external
    (use-package org-fancy-priorities ; priority icons
      :defer t
      :hook (org-mode . org-fancy-priorities-mode)
      :hook (org-agenda-mode . org-fancy-priorities-mode)
      :custom
      (org-fancy-priorities-list '("⚡" "⬆" "⬇" "☕")))
    
  • org-modern   external
    (use-package org-modern
      :demand t
      :config
      (set-face-attribute 'org-modern-symbol nil :family "Iosevka Nerd Font")
      (setq
       ;; Edit settings
       org-auto-align-tags nil
       org-tags-column 0
       org-fold-catch-invisible-edits 'show-and-error
       org-special-ctrl-a/e t
       org-insert-heading-respect-content t
       ;; Org styling, hide markup etc.
       org-hide-emphasis-markers nil ; set to nil for easier editing
       org-ellipsis "…"
       ;; Agenda styling
       org-agenda-tags-column 0
       org-agenda-block-separator ?─
       org-agenda-time-grid
       '((daily today require-timed)
         (800 1000 1200 1400 1600 1800 2000)
         " ┄┄┄┄┄ " "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄")
       org-agenda-current-time-string
       "◀── now ─────────────────────────────────────────────────")
      (global-org-modern-mode))
    
  • org-download
    (use-package org-download
      :after org
      :commands
      org-download-dnd
      org-download-yank
      org-download-screenshot
      org-download-clipboard
      org-download-dnd-base64
      :config
      (unless org-download-image-dir
        (setq org-download-image-dir org-attach-id-dir))
      (setq org-download-method 'attach
            org-download-timestamp "_%Y%m%d_%H%M%S"
            org-download-screenshot-method
            (cond ((featurep :system 'macos) "screencapture -i %s")
                  ((featurep :system 'linux)
                   (cond ((executable-find "maim")  "maim -s %s")
                         ((executable-find "scrot") "scrot -s %s")
                         ((executable-find "gnome-screenshot") "gnome-screenshot -a -f %s"))))
            org-download-heading-lvl nil
            org-download-link-format "[[download:%s]]\n"
            org-download-annotate-function (lambda (_link) "")
            org-download-link-format-function
            (lambda (filename)
              (if (eq org-download-method 'attach)
                  (format "[[attachment:%s]]\n"
                          (org-link-escape
                           (file-relative-name filename (org-attach-dir))))
                ;; Handle non-image files a little differently. Images should be
                ;; inserted as normal with previews. Other files, like pdfs or zips,
                ;; should be linked to, with an icon indicating the type of file.
                (format (concat (unless (image-type-from-file-name filename)
                                  (concat (+org-attach-icon-for filename)
                                          " "))
                                org-download-link-format)
                        (org-link-escape
                         (funcall org-download-abbreviate-filename-function filename)))))
            org-download-abbreviate-filename-function
            (lambda (path)
              (if (file-in-directory-p path org-download-image-dir)
                  (file-relative-name path org-download-image-dir)
                path))))
    
  • org-noter
    (use-package org-noter
      :defer t
      :preface
      ;; Allow the user to preempt this and set the document search path
      ;; If not set then use `org-directory'
      (defvar org-noter-notes-search-path nil)
      :config
      (unless org-noter-notes-search-path
        (setq org-noter-notes-search-path (list org-directory)))
      (setq org-noter-auto-save-last-location t
            org-noter-separate-notes-from-heading t))
    
  • org-appear
    (use-package org-appear ; better markup edit
      :hook (org-mode . org-appear-mode))
    
  • org-cliplink
    (use-package org-cliplink
      :after org
      :config
      (with-eval-after-load 'general
        (+config/leader-insert
          :keymaps 'org-mode-map
          "l" 'org-cliplink)))
    

Misc Tools

  • make-mode
    (use-package make-mode
      :straight nil
      :config
      (add-hook 'makefile-mode-hook 'indent-tabs-mode))
    
  • executable
    (use-package executable
      :straight nil
      :hook
      (after-save . executable-make-buffer-file-executable-if-script-p))
    
  • pinentry
    (use-package pinentry
      :defer t
      :config
      (pinentry-start))
    
  • pass
    (use-package password-store
      :defer t
      :config
      (setq password-store-password-length 12))
    
    (use-package password-store-otp
      :defer t
      :after password-store)
    
    (use-package pass
      :defer t)
    
    (use-package auth-source-pass
      :straight nil
      :init
      (auth-source-pass-enable))
    

    Elpaca complains about different of pass version installed. But it installed just fine.

  • rg

    Packaged as ripgrep in archlinux. The :if keyword won't load this if the executable not found.

    (use-package rg
      :if (executable-find "rg")
      :defer t)
    
  • direnv
  • ox-hugo

    Blogging tools

    (use-package ox-hugo
      :after ox)
    
  • exec-path-from-shell
    (use-package exec-path-from-shell
      :config
      (when (or (memq window-system '(mac ns x))
                (and (daemonp)
                     (memq window-system '(mac ns x))))
        (exec-path-from-shell-initialize)))
    
  • elfeed

    An Emacs web feeds client.

    (use-package elfeed
      :config
      (with-eval-after-load 'general
        (+config/leader-open
          "e" 'elfeed)))
    
    • extensions
      • elfeed-org

        Configure the Elfeed RSS reader with an Orgmode file.

        (use-package elfeed-org
          :after elfeed
          :init
          (elfeed-org)
          :config
          (setq rmh-elfeed-org-files (list (expand-file-name "elfeed.org" org-directory))))
        
      • elfeed-goodies
        (use-package elfeed-goodies
          :after elfeed
          :init
          (elfeed-goodies/setup))
        

Dired

  • dired
    (use-package dired
      :straight nil
      :commands (dired dired-jump)
      :init
      (setq dired-dwim-target t ; guess a default target directory
            dired-hide-details-hide-symlink-targets nil ; don't hide symbolic link targets
            dired-auto-revert-buffer #'dired-buffer-stale-p ; revert stale only
            dired-recursive-copies 'always ; always copy recursively
            dired-recursive-deletes 'top ; ask only for top-level
            dired-create-destination-dirs 'ask
            dired-listing-switches "-AGFhl --group-directories-first --time-style=long-iso"
            dired-clean-confirm-killing-deleted-buffers nil)
      :config
      (setq dired-mouse-drag-files t))
    

    The arguments for dired-listing-switches are:

    -A
    Show hidden files (“dotfiles”), such as .bashrc, but omit the implied .= and .. targets. The latter two refer to the present and parent directory, respectively.
    -G
    Do not show the group name in the long listing. Only show the owner of the file.
    -F
    Differentiate regular from special files by appending a character to them. The * is for executables, the / is for directories, the | is for a named pipe, the = is for a socket, the @ and the > are for stuff I have never seen.
    -h
    Make file sizes easier to read.
    -l
    Produce a long, detailed listing. Dired requires this.
    --group-directories-first
    Just as it said.
    --time-style=long-iso
    Uses the international standard for time representation in the file listing. So we have something like 2023-12-30 06:38 to show the last modified time.
  • image-dired
    (use-package image-dired
      :straight nil
      :config
      (setq image-dired-thumb-size 150
            image-dired-thumbnail-storage 'standard
            image-dired-external-viewer "xdg-open"))
    
  • dired-x
    (use-package dired-x
      :straight nil
      :hook (dired-mode . dired-omit-mode)
      :config
      (setq dired-omit-files
            (concat dired-omit-files
                    "\\|^\\.DS_Store\\'"
                    "\\|^\\.project\\(?:ile\\)?\\'"
                    "\\|^\\.\\(?:svn\\|git\\)\\'"
                    "\\|^\\.ccls-cache\\'"
                    "\\|\\(?:\\.js\\)?\\.meta\\'"
                    "\\|\\.\\(?:elc\\|o\\|pyo\\|swp\\|class\\)\\'"))
      ;; Disable the prompt about whether I want to kill the Dired buffer for a
      ;; deleted directory. Of course I do!
      (setq dired-clean-confirm-killing-deleted-buffers nil)
      (let ((cmd "xdg-open"))
        (setq dired-guess-shell-alist-user
              `(("\\.\\(?:docx\\|pdf\\|djvu\\|eps\\)\\'" ,cmd)
                ("\\.\\(?:jpe?g\\|png\\|gif\\|xpm\\)\\'" ,cmd)
                ("\\.\\(?:xcf\\)\\'" ,cmd)
                ("\\.csv\\'" ,cmd)
                ("\\.tex\\'" ,cmd)
                ("\\.\\(?:mp4\\|mkv\\|avi\\|flv\\|rm\\|rmvb\\|ogv\\)\\(?:\\.part\\)?\\'" ,cmd)
                ("\\.\\(?:mp3\\|flac\\)\\'" ,cmd)
                ("\\.html?\\'" ,cmd)
                ("\\.md\\'" ,cmd)))))
    

    The dired-guess-shell-alist-user expect us to have xdg-open (which is always available in my system anyway).

  • fd-dired   external

    Needs an external package fd, or fd-find

    (use-package fd-dired
      :if (executable-find "fd")
      :defer t
      :init
      (global-set-key [remap find-dired] #'fd-dired))
    
  • dired-git-info
    (use-package dired-git-info
      :defer t
      ;; :hook
      ;; (dired-after-readin . dired-git-info-auto-enable)
      :config
      ;; (setq +dired--git-info-p (bound-and-true-p dired-git-info-mode))
      ;; (when +dired--git-info-p
      ;;   (dired-git-info-mode -1))
      (setq dgi-auto-hide-details-p nil))
    
  • dired-aux
    (use-package dired-aux
      :straight nil
      :config
      (setq dired-create-destination-dirs 'ask
            dired-vc-rename-file t
            dired-isearch-filenames 'dwim
            dired-create-destination-dirs-on-trailing-dirsep t))
    
  • dired-rsync
    (use-package dired-rsync
      :defer t)
    
  • diredfl
    (use-package diredfl
      :defer t
      :hook (dired-mode . diredfl-global-mode))
    
  • all-the-icons-dired
    (use-package all-the-icons-dired
      :after all-the-icons
      :hook (dired-mode . all-the-icons-dired-mode))
    
  • wdired
    (use-package wdired
      :straight nil
      :commands (wdired-change-to-wdired-mode))
    

Treemacs

  • treemacs core package
    (use-package treemacs
      :hook ((treemacs-mode . (lambda () (hs-minor-mode -1))))
      :config
      (setq treemacs-hide-gitignored-files-mode t
            treemacs-no-png-images t
            treemacs-silent-refresh t
            treemacs-sorting 'mod-time-desc
            treemacs-python-executable (executable-find "python3")
            treemacs-collapse-dirs (if treemacs-python-executable 3 0)
            treemacs-deferred-git-apply-delay 0.5
            treemacs-directory-name-transformer #'identity
            treemacs-display-in-side-window t
            treemacs-eldoc-display 'simple
            treemacs-file-event-delay 2000
            treemacs-file-extension-regex treemacs-last-period-regex-value
            treemacs-file-follow-delay 0.2
            treemacs-file-name-transformer #'identity
            treemacs-follow-after-init t
            treemacs-expand-after-init t
            treemacs-find-workspace-method 'find-for-file-or-pick-first
            treemacs-git-command-pipe ""
            treemacs-goto-tag-strategy 'refetch-index
            treemacs-header-scroll-indicators '(nil . "^^^^^^")
            treemacs-hide-dot-git-directory t
            treemacs-indentation 2
            treemacs-indentation-string " "
            treemacs-is-never-other-window nil
            treemacs-max-git-entries 5000
            treemacs-missing-project-action 'ask
            treemacs-move-forward-on-expand nil
            treemacs-no-delete-other-windows t
            treemacs-project-follow-cleanup nil
            treemacs-persist-file (expand-file-name ".cache/treemacs-persist" user-emacs-directory)
            treemacs-position 'left
            treemacs-read-string-input 'from-child-frame
            treemacs-recenter-distance 0.1
            treemacs-recenter-after-file-follow nil
            treemacs-recenter-after-tag-follow nil
            treemacs-recenter-after-project-jump 'always
            treemacs-recenter-after-project-expand 'on-distance
            treemacs-litter-directories '("/node_modules" "/.venv" "/.cask")
            treemacs-project-follow-into-home nil
            treemacs-show-cursor nil
            treemacs-show-hidden-files t
            treemacs-silent-filewatch t
            treemacs-select-when-already-in-treemacs 'move-back
            treemacs-space-between-root-nodes t
            treemacs-tag-follow-cleanup t
            treemacs-tag-follow-delay 1.5
            treemacs-text-scale nil
            treemacs-user-mode-line-format nil
            treemacs-user-header-line-format nil
            treemacs-wide-toggle-width 70
            treemacs-width 35
            treemacs-width-increment 1
            treemacs-width-is-initially-locked t
            treemacs-workspace-switch-cleanup nil)
      (treemacs-peek-mode 1)
      (treemacs-filewatch-mode t)
      (treemacs-follow-mode t)
      (treemacs-fringe-indicator-mode 'always)
      (when treemacs-python-executable
        (treemacs-git-commit-diff-mode t))
      (pcase (cons (not (null (executable-find "git")))
                   (not (null treemacs-python-executable)))
        (`(t . t)
         (treemacs-git-mode 'deferred))
        (`(t . _)
         (treemacs-git-mode 'simple)))
      :bind
      (:map global-map
            ("M-0"       . treemacs-select-window)
            ("C-x t 1"   . treemacs-delete-other-windows)
            ("C-x t t"   . treemacs)
            ("C-x t d"   . treemacs-select-directory)
            ("C-x t B"   . treemacs-bookmark)
            ("C-x t C-t" . treemacs-find-file)
            ("C-x t M-t" . treemacs-find-tag))
      :config
      (with-eval-after-load 'general
        (+config/leader-tree
          "t" 'treemacs
          "p" 'treemacs-add-and-display-current-project-exclusively)
        (general-define-key
         :keymaps 'treemacs-mode-map
         "[mouse-1]" 'treemacs-single-click-expand-action)
        (general-nvmap
          :keymaps 'treemacs-mode-map
          "gr" '(treemacs-refresh :wk "refresh tree")
          "zm" '(:ignore t :wk "move")
          "zmf" '(treemacs-move-file :wk "move file")
          "zmd" '(treemacs-move-project-down :wk "move project down")
          "zmu" '(treemacs-move-project-up :wk "move project up")
          "zr" '(:ignore t :wk "rename")
          "zrf" '(treemacs-rename-file :wk "rename file")
          "zrp" '(treemacs-rename-project :wk "rename project")
          "zrw" '(treemacs-rename-workspace :wk "rename workspace")
          "zc" '(:ignore t :wk "create/clean")
          "zcC" '(treemacs-cleanup-litter :wk "cleanup litter")
          "zcd" '(treemacs-create-dir :wk "create directory")
          "zcf" '(treemacs-create-file :wk "create file")
          "zd" '(treemacs-delete-file :wk "delete file")))
      )
    
  • treemacs-projectile
    (use-package treemacs-projectile
      :after treemacs projectile)
    
  • treemacs-icons-dired
    (use-package treemacs-icons-dired
      :after treemacs
      :hook (dired-mode . treemacs-icons-dired-enable-once))
    
  • treemacs-magit
    (use-package treemacs-magit
      :after treemacs magit)
    
  • treemacs-evil
    (use-package treemacs-evil
      :after treemacs evil)
    
  • lsp-treemacs

    emacs-lsp-treemacs-symbol.png

    Figure 10: symbols in lsp-treemacs

    (use-package lsp-treemacs
      :after treemacs lsp
      :config
      (lsp-treemacs-sync-mode 1)
      (with-eval-after-load 'general
        (general-define-key
         :keymaps 'treemacs-mode-map
         "S" 'lsp-treemacs-symbols
         "X" 'lsp-treemacs-errors-list
         "Y" 'lsp-treemacs-call-hierarchy))
      )
    
  • treemacs-perspective
    (use-package treemacs-perspective
      :after treemacs perspective
      :config
      (treemacs-set-scope-type 'Perspectives))
    
  • treemacs-tab-bar
    (use-package treemacs-tab-bar
      :after treemacs tab-bar)
    
  • treemacs-nerd-icons
    (use-package treemacs-nerd-icons
      :after treemacs nerd-icons
      :config
      (treemacs-load-theme "nerd-icons"))
    

Mail

  • notmuch
    (use-package notmuch
      :if (executable-find "notmuch")
      :defer t
      :commands (notmuch)
      :hook
      (message-setup . mml-secure-sign-pgpmime)
      :config
      (global-set-key (kbd "<XF86Mail>") 'notmuch)
      (setq notmuch-fcc-dirs nil
            notmuch-search-result-format
            '(("date" . "%12s ")
              ("count" . "%-7s ")
              ("authors" . "%-30s ")
              ("subject" . "%-72s ")
              ("tags" . "(%s)"))
            notmuch-tag-formats
            '(("unread"
               (propertize tag 'face 'notmuch-tag-unread))
              ("flagged"
               (propertize tag 'face 'notmuch-tag-flagged)
               (notmuch-tag-format-image-data tag
                                              (notmuch-tag-star-icon))))
            notmuch-tagging-keys
            '(("a" notmuch-archive-tags "Archive")
              ("u" notmuch-show-mark-read-tags "Mark read")
              ("f" ("+flagged") "Flag")
              ("s" ("+spam" "-inbox") "Mark as spam")
              ("d" ("+deleted" "-inbox") "Delete"))
            notmuch-saved-searches
            '((:name "flagged" :query "tag:flagged" :key "f")
              (:name "sent" :query "tag:sent" :key "s")
              (:name "drafts"  :query "tag:draft" :key "d")
              (:name "all mail" :query "*" :key "a")
              (:name "unread" :query "tag:unread" :key "u")
              (:name "zum" :query "tag:zum" :key "z")
              (:name "mkn" :query "tag:mkn" :key "c")
              (:name "gmail" :query "tag:gmail" :key "g")
              (:name "hotmail" :query "tag:hotmail" :key "h")
              (:name "yahoo" :query "tag:yahoo" :key "h")
              (:name "ymail" :query "tag:ymail" :key "m")
              (:name "Today"
                     :query "date:today AND NOT tag:spam AND NOT tag:bulk"
                     :key "T"
                     :search-type 'tree
                     :sort-order 'newest-first)
              (:name "This Week"
                     :query "date:weeks AND NOT tag:spam AND NOT tag:bulk"
                     :key "W"
                     :search-type 'tree
                     :sort-order 'newest-first)
              (:name "This Month"
                     :query "date:months AND NOT tag:spam AND NOT tag:bulk"
                     :key "M"
                     :search-type 'tree
                     :sort-order 'newest-first)
              (:name "flagged"
                     :query "tag:flagged AND NOT tag:spam AND NOT tag:bulk"
                     :key "f"
                     :search-type 'tree
                     :sort-order 'newest-first)
              (:name "spam" :query "tag:spam"))
            notmuch-archive-tags '("-inbox" "-unread"))
      (setq-default notmuch-search-oldest-first nil)
      (if (executable-find "gpg2")
          (setq notmuch-crypto-gpg-program "gpg2")
        (setq notmuch-crypto-gpg-program "gpg"))
      (setq notmuch-crypto-process-mime t
            mml-secure-openpgp-sign-with-sender t)
      (define-key notmuch-show-mode-map "S"
                  (lambda ()
                    "Mark message as spam"
                    (interactive)
                    (notmuch-show-tag (list +spam -new))))
      (with-eval-after-load 'general
        (+config/leader-menu! "mail" "M-m"
          "m" '(notmuch :wk "Notmuch Mail"))
    
        (+config/leader-mail
          "m" '(notmuch :wk "open notmuch mail")
          "M" '(compose-mail :wk "new mail"))
    
        (general-nmap 'notmuch-common-keymap
          "gr" 'notmuch-refresh-this-buffer
          "gR" 'notmuch-poll-and-refresh-this-buffer))
      )
    
  • notmuch-indicator
    (use-package notmuch-indicator
      :if (executable-find "notmuch")
      :config
      (setq notmuch-indicator-args
            '((:terms "tag:unread and tag:inbox" :label "U" :label-face success)))
      (notmuch-indicator-mode))
    
  • consult-notmuch
    (use-package consult-notmuch
      :after consult
      :if (executable-find "notmuch")
      :config
      (add-to-list 'consult-buffer-sources 'consult-notmuch-buffer-source))
    
  • ol-notmuch
    (use-package ol-notmuch
      :if (executable-find "notmuch"))
    
  • notmuch-maildir
    (use-package notmuch-maildir
      :if (executable-find "notmuch")
      :config
      (notmuch-maildir-inject-section))
    
  • message
    (use-package message
      :straight nil
      :custom
      (message-directory (expand-file-name ".mail" (getenv "HOME")))
      (message-sendmail-envelope-from 'header))
    
  • sendmail
    (use-package sendmail
      :straight nil
      :custom
      (mail-specify-envelope-from t)
      (mail-envelope-from 'header)
      (send-mail-function 'sendmail-send-it)
      (sendmail-program (executable-find "msmtp")))
    

Programming languages

  • Lisp
    • Emacs-lisp
      • elisp-mode
        (when (locate-library "aggressive-indent")
          (add-hook 'lisp-mode-hook #'aggressive-indent-mode)
          (add-hook 'clojure-mode-hook #'aggressive-indent-mode)
          (add-hook 'scheme-mode-hook #'aggressive-indent-mode))
        
        ;;;; emacs-lisp
        (use-package elisp-mode
          :diminish emacs-lisp-mode
          :diminish elisp-mode
          :diminish outline-minor-mode
          :diminish lisp-data-mode
          :mode ("\\.Cask\\'" . emacs-lisp-mode)
          :straight nil
          :hook (emacs-lisp-mode . (lambda ()
                                     (outline-minor-mode)
                                     (rainbow-delimiters-mode)
                                     (highlight-quoted-mode))))
        
        • ielm
          (use-package ielm
            :straight nil
            ;;:hook (ielm-mode . (turn-on-smartparens-mode))
            :config
            (setq ielm-font-lock-keywords
                  (append '(("\\(^\\*\\*\\*[^*]+\\*\\*\\*\\)\\(.*$\\)"
                             (1 font-lock-comment-face)
                             (2 font-lock-constant-face)))
                          (when (require 'highlight-numbers nil t)
                            (highlight-numbers--get-regexp-for-mode 'emacs-lisp-mode))
                          (cl-loop for (matcher . match-highlights)
                                   in (append lisp-el-font-lock-keywords-2
                                              lisp-cl-font-lock-keywords-2)
                                   collect
                                   `((lambda (limit)
                                       (when ,(if (symbolp matcher)
                                                  `(,matcher limit)
                                                `(re-search-forward ,matcher limit t))
                                         ;; Only highlight matches after the prompt
                                         (> (match-beginning 0) (car comint-last-prompt))
                                         ;; Make sure we're not in a comment or string
                                         (let ((state (syntax-ppss)))
                                           (not (or (nth 3 state)
                                                    (nth 4 state))))))
                                     ,@match-highlights)))))
          
      • macrostep   external
        (use-package macrostep
          :defer t
          :bind (:map emacs-lisp-mode-map
                      ("C-c e" . macrostep-expand)))
        
    • Common lisp
      ;;;; common-lisp
      (defvar inferior-lisp-program "sbcl")
      (when (executable-find "ros")
        (setq inferior-lisp-program "ros -Q run"))
      
      (add-hook 'lisp-mode-hook #'rainbow-delimiters-mode)
      
      • sly   external
        (use-package sly
          :commands sly-autoloads
          :hook ((lisp-mode-local-vars . rainbow-delimiters-mode)
                 (lisp-mode . sly-editing-mode))
          :config
          (require 'sly-autoloads))
        
      • sly-quicklisp
        (use-package sly-quicklisp
          :after sly
          :load-path "site-lisp/sly-quicklisp")
        
      • sly-quicklisp
        (use-package sly-macrostep
          :after sly
          :load-path "site-lisp/sly-macrostep")
        
    • clojure
      • clojure-mode
        (use-package clojure-mode
          :hook (clojure-mode . rainbow-delimiters-mode)
          :config
          (with-eval-after-load 'lsp-mode
            (add-hook '(clojure-mode-local-vars-hook #'lsp-deferred))
            (add-hook '(clojurec-mode-local-vars-hook #'lsp-deferred))
            (add-hook '(clojurescript-mode-local-vars-hook #'lsp-deferred))
            (setq-local lsp-enable-indentation nil))
          (with-eval-after-load 'lsp-clojure
            (dolist (m '(clojure-mode
                         clojurec-mode
                         clojurescript-mode
                         clojurex-mode))
              (add-to-list 'lsp-language-id-configuration (cons m "clojure"))))
          (with-eval-after-load 'tree-sitter-langs
              (add-to-list 'tree-sitter-major-mode-language-alist '(clojurec-mode . clojure))
              (add-to-list 'tree-sitter-major-mode-language-alist '(clojurescript-mode . clojure))))
        
      • cider   external
        ;;;; clojure
        (use-package cider
          :hook (clojure-mode-local-vars . cider-mode)
          :init
          ;; HACK Fix radian-software/radian#446: CIDER tries to calculate the frame's
          ;;   background too early; sometimes before the initial frame has been
          ;;   initialized, causing errors.
          (defvar cider-docview-code-background-color nil)
          (defvar cider-stacktrace-frames-background-color nil)
          :config
          (add-hook 'cider-mode-hook #'eldoc-mode)
          (setq nrepl-hide-special-buffers t
                nrepl-log-messages nil
                cider-font-lock-dynamically '(macro core function var deprecated)
                cider-overlays-use-font-lock t
                cider-print-options '(("length" 100))
                cider-prompt-for-symbol nil
                cider-repl-history-display-duplicates nil
                cider-repl-history-display-style 'one-line
                cider-repl-history-file (concat doom-cache-dir "cider-repl-history")
                cider-repl-history-highlight-current-entry t
                cider-repl-history-quit-action 'delete-and-restore
                cider-repl-history-highlight-inserted-item t
                cider-repl-history-size 1000
                cider-repl-result-prefix ";; => "
                cider-repl-use-clojure-font-lock t
                cider-repl-use-pretty-printing t
                cider-repl-wrap-history nil
                cider-stacktrace-default-filters '(tooling dup)
        
                ;; Don't focus the CIDER REPL when it starts. Since it can take so long
                ;; to start up, you either wait for a minute doing nothing or be
                ;; prepared for your cursor to suddenly change buffers without warning.
                ;; See https://github.com/clojure-emacs/cider/issues/1872
                cider-repl-pop-to-buffer-on-connect 'display-only)
          (with-eval-after-load 'lsp-mode
            (setq cider-eldoc-display-for-symbol-at-point nil
                  cider-font-lock-dynamically nil)
            (defun +clojure--cider-disable-completion ()
              "Use lsp completion instead of cider."
              (remove-hook 'completion-at-point-functions #'cider-complete-at-point t))
            (add-hook 'cider-mode-hook #'+clojure--cider-disable-completion)))
        
      • clj-refactor   external
        (use-package clj-refactor
          :defer t
          :hook (clojure-mode . +config/clojure-mode-hook)
          :config
          (defun +config/clojure-mode-hook ()
            (clj-refactor-mode 1)
            ;; This choice of keybinding leaves cider-macroexpand-1 unbound
            (cljr-add-keybindings-with-prefix "C-c r")))
        
    • Scheme
      (use-package scheme
        :straight nil
        :interpreter ("scsh" . scheme-mode)
        :hook (scheme-mode . rainbow-delimiters-mode)
        :config
        (defvar calculate-lisp-indent-last-sexp)
        (defun +scheme-indent-function-a (indent-point state)
          "Advice to replace `scheme-indent-function'.
      
      This function is the same as `scheme-indent-function' except it properly indents
      property lists and names starting with 'default'."
          (let ((normal-indent (current-column)))
            (goto-char (1+ (elt state 1)))
            (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t)
            (if (and (elt state 2)
                     ;; NOTE looking-at -> looking-at-p
                     (not (looking-at-p "\\sw\\|\\s_")))
                (progn
                  ;; NOTE (if (not ...) (progn ...)) -> (unless ... ...)
                  (unless (> (save-excursion (forward-line 1) (point))
                             calculate-lisp-indent-last-sexp)
                    (goto-char calculate-lisp-indent-last-sexp)
                    (beginning-of-line)
                    (parse-partial-sexp (point) calculate-lisp-indent-last-sexp 0 t))
                  (backward-prefix-chars)
                  (current-column))
              ;; NOTE let -> let* & moved `method' def into let bindings
              (let* ((function (buffer-substring
                                (point) (progn (forward-sexp 1) (point))))
                     (method (or (get (intern-soft function) 'scheme-indent-function)
                                 (get (intern-soft function) 'scheme-indent-hook))))
                (cond ((or (eq method 'defun)
                           (and (null method)
                                (> (length function) 3)
                                ;; NOTE string-match -> string-match-p
                                ;; NOTE The original regexp is "\\`def" but it will mess
                                ;;      up indentation with such names as 'default-...'.
                                (string-match-p "\\`def" function)))
                       (lisp-indent-defform state indent-point))
                      ;; NOTE Added this clause to handle alignment of keyword symbols
                      ((and (null method)
                            (> (length function) 1)
                            ;; NOTE string-match -> string-match-p
                            (string-match-p "\\`:" function))
                       (let ((lisp-body-indent 1))
                         (lisp-indent-defform state indent-point)))
                      ((integerp method)
                       (lisp-indent-specform method state indent-point normal-indent))
                      (method
                       (funcall method state indent-point normal-indent)))))))
        (advice-add #'scheme-indent-function :override #'+scheme-indent-function-a))
      
      • geiser   external
        ;;;; scheme
        (use-package geiser
          :defer t
          :diminish geiser-autodoc-mode
          ;; :hook (geiser-repl-mode . turn-on-smartparens-strict-mode)
          :init
          (setq geiser-autodoc-identifier-format "%s → %s"
                geiser-repl-per-project-p t
                geiser-repl-history-filename (concat user-emacs-directory "geiser-history"))
          (defun +scheme/open-repl ()
            "Open the Scheme REPL."
            (interactive)
            (call-interactively #'geiser-repl-switch)
            (current-buffer))
          :config
          (with-eval-after-load 'general
            (+config/local-leader
              :keymaps '(scheme-mode-map geiser-mode-map)
              "'" 'geiser-repl-switch
              "\"" 'geiser-connect
              "[" 'geiser-squarify
              "\\" 'geiser-insert-lambda
              "s" 'geiser-set-scheme
              "R" 'geiser-reload
              "h" '(:ignore t :wk "help")
              "h<" 'geiser-xref-callers
              "h>" 'geiser-xref-callees
              "ha" 'geiser-autodoc-mode
              "hs" 'geiser-autodoc-show
              "hm" 'geiser-doc-lookup-manual
              "h." 'geiser-doc-symbol-at-point
              "r" '(:ignore t :wk "repl")
              "rf" 'geiser-load-file
              "rr" 'geiser-restart-repl)
            (+config/local-leader
              :keymaps 'scheme-mode-map
              "e" '(:ignore t :wk "eval")
              "eb" 'geiser-eval-buffer
              "B" 'geiser-eval-buffer-and-go
              "e" 'geiser-eval-last-sexp
              "d" 'geiser-eval-definition
              "D" 'geiser-eval-definition-and-go
              "r" 'geiser-eval-region
              "R" 'geiser-eval-region-and-go
              "r" '(ignore t :wk "repl")
              "rb" 'geiser-load-current-buffer)
            (+config/local-leader
              :keymaps 'geiser-repl-mode-map
              "c" 'geiser-repl-clear-buffer
              "q" 'geiser-repl-exit)))
        
      • geiser-guile   external
        (use-package geiser-guile
          :defer t
          :config
          (let ((nonguix-path (expand-file-name "Projects/guix/nonguix"
                                                (getenv "HOME")))
                (personal-path (expand-file-name "Projects/guix/devel/src"
                                                 (getenv "HOME"))))
            (when (file-directory-p nonguix-path)
              (add-to-list 'geiser-guile-load-path nonguix-path))
            (when (file-directory-p personal-path)
              (add-to-list 'geiser-guile-load-path personal-path)))
          (when (file-directory-p (expand-file-name "~/.config/guix/current/share/guile/site/3.0"))
            (add-to-list 'geiser-guile-load-path (expand-file-name "~/.config/guix/current/share/guile/site/3.0"))))
        
      • macrostep-geiser   external
        (use-package macrostep-geiser
          :defer t
          :after geiser-mode geiser-repl
          :hook ((geiser-mode geiser-repl-mode) . macrostep-geiser-setup)
          :bind (:map geiser-mode-map
                      ("C-c e" . macrostep-expand))
          :config
          (with-eval-after-load 'general
            (+config/local-leader
              :keymaps '(scheme-mode-map geiser-repl-mode-map)
              "m" 'macrostep-expand
              "M" 'macrostep-geiser-expand-all)))
        
      • flycheck-guile
        (use-package flycheck-guile
          :after geiser)
        
  • C and C++
    • cc-mode   builtin
      (use-package cc-mode
        :straight nil
        :mode ("\\.mm\\'" . objc-mode)
        :mode ("\\.h\\'" . +cc-c-c++-objc-mode)
        :hook (c-mode-common . rainbow-delimiters-mode)
        ;; :hook (c++-mode-local-vars . c++-ts-mode)
        ;; :hook (c-mode-common . c-ts-base-mode)
        ;; :hook (cmake-mode-local-vars . cmake-ts-mode)
        :config
        (setq c-basic-offset tab-width
              c-backspace-function #'delete-backward-char)
        (with-eval-after-load 'ffap
          (add-to-list 'ffap-alist '(c-mode . ffap-c-mode)))
        (with-eval-after-load 'tree-sitter
          (add-hook 'c-mode-hook #'tree-sitter! 'append))
        (with-eval-after-load 'lsp-mode
          (add-hook 'c-mode-hook #'lsp-deferred)))
      
    • demangle-mode
      (use-package demangle-mode
        :hook llvm-mode)
      
  • XML
    • nxml-mode
      (use-package nxml-mode
        :straight nil
        :mode "\\.p\\(?:list\\|om\\)\\'" ; plist, pom
        :mode "\\.xs\\(?:d\\|lt\\)\\'"   ; xslt, xsd
        :mode "\\.rss\\'"
        :config
        (setq nxml-slash-auto-complete-flag t
              nxml-auto-insert-xml-declaration-flag t))
      
  • YAML
    • yaml-mode   external
      (use-package yaml-mode
        :config
        (add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode))
        (add-to-list 'auto-mode-alist '("\\.yaml\\'" . yaml-mode))
        (setq tab-width 2)
        ;; (when (treesit-available-p)
        ;;   (add-to-list 'major-mode-remap-alist '(yaml-mode . yaml-ts-mode)))
        :hook
        ((yaml-mode . (lambda ()
                        (define-key yaml-mode-map "\C-m" 'newline-and-indent)))
         (yaml-mode . (lambda ()
                        (run-hooks 'prog-mode-hook)))))
      
  • TOML
    • toml-mode   external
      (use-package toml-mode
        :mode
        ("\\.toml\\'" . toml-mode)
        :config
        ;; (when (treesit-available-p)
        ;;    (add-to-list 'major-mode-remap-alist '(toml-mode . toml-ts-mode)))
        (with-eval-after-load 'lsp-mode
          (setq lsp-toml-command (executable-find "taplo")))
        (add-to-list 'lsp-language-id-configuration '("\\.toml$" . "toml"))
        (add-hook 'toml-mode-hook #'lsp-deferred))
      
  • Lua
    • lua-mode   external
      (use-package lua-mode
        :defer t
        :config
        (with-eval-after-load 'lsp-mode
          (setq lsp-lua-hint-enable t
                lsp-lua-hint-set-type t)))
      
  • Jinja2
    • jinja2-mode   external
      (use-package jinja2-mode
        :defer t
        :config
        ;; The default behavior is to reindent the whole buffer on save. This is
        ;; disruptive and imposing. There are indentation commands available; the user
        ;; can decide when they want their code reindented.
        (setq jinja2-enable-indent-on-save nil)
        (add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode))
        (add-to-list 'auto-mode-alist '("\\.yaml\\'" . yaml-mode)))
      
  • Json
    • json-mode
      (use-package json-mode)
      
  • Web
    • web-mode   external
      (defun +config/django-web-mode ()
        "Set web-engine to django if `manage.py' detected in `projectile-project-root'."
        (if (projectile-project-p)
            (if (file-exists-p (concat (projectile-project-root) "manage.py"))
                (web-mode-set-engine "django"))))
      
      (defun +config/web-mode-fix-js-comment ()
        "Fix comment handling in `web-mode' for JavaScript (from `doom')."
        (when (member web-mode-content-type '("javascript" "jsx"))
          ;; For some reason the default is to insert HTML comments even
          ;; in JavaScript.
          (setq-local comment-start "//")
          (setq-local comment-end "")
          ;; Needed since otherwise the default value generated by
          ;; `comment-normalize-vars' will key off the syntax and think
          ;; that a single "/" starts a comment, which completely borks
          ;; auto-fill.
          (setq-local comment-start-skip "// *")))
      
      (use-package web-mode
        :defer t
        :mode "\\.[px]?html?\\'"
        :mode "\\.\\(?:tpl\\|blade\\)\\(?:\\.php\\)?\\'"
        :mode "\\.erb\\'"
        :mode "\\.[lh]?eex\\'"
        :mode "\\.jsp\\'"
        :mode "\\.as[cp]x\\'"
        :mode "\\.ejs\\'"
        :mode "\\.hbs\\'"
        :mode "\\.mustache\\'"
        :mode "\\.svelte\\'"
        :mode "\\.twig\\'"
        :mode "\\.jinja2?\\'"
        :mode "\\.eco\\'"
        :mode "wp-content/themes/.+/.+\\.php\\'"
        :mode "templates/.+\\.php\\'"
        :init
        ;; If the user has installed `vue-mode' then, by appending this to
        ;; `auto-mode-alist' rather than prepending it, its autoload will have
        ;; priority over this one.
        (add-to-list 'auto-mode-alist '("\\.vue\\'" . web-mode) 'append)
        :mode "\\.vue\\'"
        :config
        (add-to-list 'web-mode-engine-file-regexps '("django" . "\\.\\(djhtml\\|tmpl\\|dtl\\|liquid\\|j2\\|njk\\|.html\\)\\'"))
        (setq web-mode-engines-alist
              '(("django"  . "\\.jinja\\'")
                ("django"  . "\\.djhtml\\'")
                ("django"  . "\\.html\\'")
                ("erb"     . "\\.erb\\'")
                ("erb"     . "\\.rhtml\\'")
                ("erb"     . "\\.ejs\\'")
                ("php"     . "\\.phtml\\'")
                ("php"     . "\\.php\\'")
                ("php"     . "\\.psp\\'")
                ("php"     . "\\.ctp\\'")
                ("jsp"     . "\\.jsp\\'")
                ("jsp"     . "\\.gsp\\'")
                ("asp"     . "\\.asp\\'")
                ("aspx"    . "\\.aspx\\'")
                ("aspx"    . "\\.ascx\\'")
                ("closure" . "\\.soy\\'")
                ("lsp"     . "\\.lsp\\'")
                ("mako"    . "\\.mako\\'")
                ("blade"   . "\\.blade\\.")
                ("svelte"  . "\\.svelte\\'")))
        (setq web-mode-markup-indent-offset 2
              web-mode-enable-auto-closing t
              web-mode-auto-close-style 1
              web-mode-css-indent-offset 2
              web-mode-code-indent-offset 2
              web-mode-enable-auto-pairing nil
              web-mode-enable-block-face t
              web-mode-enable-comment-interpolation t
              web-mode-enable-heredoc-fontification t
              web-mode-enable-css-colorization t
              web-mode-enable-part-face t
              web-mode-enable-current-element-highlight t
              web-mode-enable-current-column-highlight t
              web-mode-style-padding 1
              web-mode-script-padding 1
              web-mode-block-padding 0
              web-mode-comment-style 1
              web-mode-enable-curly-brace-indentation t
              web-mode-enable-auto-quoting nil)
        ;; web-mode-extra-auto-pairs
        ;; '(("erb" . (("beg" "end")))
        ;;   ("php" . (("beg" "end")))))
        (with-eval-after-load 'smartparens
          (sp-local-pair 'web-mode "<" nil :actions :rem)
          (sp-local-pair 'web-mode "{%" " %}")
          (sp-local-pair 'web-mode "{{" " }}")
          (sp-local-pair 'web-mode "<!--" " -->")
          )
        (with-eval-after-load 'general
          (general-nvmap
            :keymaps 'web-mode-map
            "gn" 'web-mode-navigate
            "zo" 'web-mode-fold-or-unfold
            "zw" 'web-mode-whitespaces-show)
          (general-vmap
            :keymaps 'web-mode-map
            "gs" 'web-mode-surround))
        (defun +config/web-mode-hook ()
          "Hooks for Web mode."
          (local-set-key (kbd "RET") 'newline-and-indent))
        ;; Use // instead of /* as the default comment delimited in JS
        (with-eval-after-load 'web-mode
          (setf (alist-get "javascript" web-mode-comment-formats nil nil #'equal)
                "//"))
        :hook ((html-mode-local-vars-hook
                mhtml-mode-local-vars-hook) . tree-sitter)
        :hook (web-mode . +config/django-web-mode)
        :hook (web-mode . +config/web-mode-fix-js-comment))
      
    • emmet-mode   external
      (use-package emmet-mode
        :defer t
        :hook (css-mode web-mode html-mode haml-mode nxml-mode rjsx-mode reason-mode)
        :config
        (when (require 'yasnippet nil t)
          (add-hook 'emmet-mode-hook #'yas-minor-mode-on))
        (setq emmet-move-cursor-between-quotes t)
        (with-eval-after-load 'general
          (general-vmap
            :keymaps 'emmet-mode-keymap
            "TAB" 'emmet-wrap-with-markup)
          (general-def
            :keymaps 'emmet-mode-keymap
            "M-E" 'emmet-expand-line))
        )
      
    • skewer-mode   external
      (use-package skewer-mode
        :defer t
        :hook ((web-mode . skewer-html-mode)
               (js2-mode . skewer-mode)))
      
    • lsp-tailwindcss   external
      (use-package lsp-tailwindcss
        :init
        (setq lsp-tailwindcss-add-on-mode t))
      
    • sass-mode   external
      (use-package sass-mode
        :defer t
        :hook (scss-mode . rainbow-mode))
      
    • css-eldoc   external
      (use-package css-eldoc
        :defer t
        :commands turn-on-css-eldoc
        ;;add a hook if you want always to see the selector options in the minibuffer
        :config
        (add-hook 'css-mode-hook 'turn-on-css-eldoc)
        (add-hook 'scss-mode-hook 'turn-on-css-eldoc))
      
    • com-css-sort   external
      (use-package com-css-sort
        :defer t
        :commands (com-css-sort com-css-sort-attributes-block com-css-sort-attributes-document)
        :config
        (setq com-css-sort-sort-type 'alphabetic-sort))
      
    • css-mode
      (use-package css-mode
        :straight nil
        :hook ((css-mode
                stylus-mode) . rainbow-mode)
        :hook (css-mode-local-vars . tree-sitter)
        :config
        (with-eval-after-load 'skewer-mode
          (add-hook 'css-mode-hook 'skewer-css-mode)))
      
    • javascript
      (defvar +javascript-npm-conf (make-hash-table :test 'equal))
      
      ;;;###autoload
      (defun +javascript-npm-conf (&optional project-root refresh-p)
        "Retrieves an alist of this project's 'package.json'. If REFRESH-P is non-nil
      ignore the cache."
        (let ((project-root (or project-root (doom-project-root))))
          (or (and (not refresh-p)
                   (gethash project-root +javascript-npm-conf))
              (let ((package-file (expand-file-name "package.json" project-root)))
                (when-let (json (and (file-exists-p package-file)
                                     (require 'json)
                                     (json-read-file package-file)))
                  (puthash project-root json +javascript-npm-conf))))))
      
      ;;;###autoload
      (defun +javascript-npm-dep-p (packages &optional project-root refresh-p)
        (when-let (data (and (bound-and-true-p +javascript-npm-mode)
                             (+javascript-npm-conf project-root refresh-p)))
          (let ((deps (append (cdr (assq 'dependencies data))
                              (cdr (assq 'devDependencies data)))))
            (cond ((listp packages)
                   (funcall (if (eq (car packages) 'and)
                                #'cl-every
                              #'cl-some)
                            (lambda (pkg) (assq pkg deps))
                            (if (listp packages) packages (list packages))))
                  ((symbolp packages)
                   (assq packages deps))
                  (t (error "Expected a package symbol or list, got %s" packages))))))
      
      ;;;###autoload
      (defun +javascript-add-npm-path-h ()
        "Add node_modules/.bin to `exec-path'."
        (when-let ((search-directory (or (doom-project-root) default-directory))
                   (node-modules-parent (locate-dominating-file search-directory "node_modules/"))
                   (node-modules-dir (expand-file-name "node_modules/.bin/" node-modules-parent)))
          (make-local-variable 'exec-path)
          (add-to-list 'exec-path node-modules-dir)
          (doom-log ":lang:javascript: add %s to $PATH" (expand-file-name "node_modules/" node-modules-parent))))
      
      
      ;;
      ;; Commands
      
      ;;;###autoload
      (defun +javascript/open-repl ()
        "Open a Javascript REPL. Meaning either `skewer-repl', if any of the
      skewer-*-mode's are enabled, or `nodejs-repl' otherwise."
        (interactive)
        (call-interactively
         (if (and (featurep 'skewer-mode)
                  (or (bound-and-true-p skewer-mode)
                      (bound-and-true-p skewer-css-mode)
                      (bound-and-true-p skewer-html-mode)))
             #'skewer-repl
           #'nodejs-repl))
        (current-buffer))
      
      ;;;###autoload
      (defun +javascript/skewer-this-buffer ()
        "Toggle a globalized skewer-mode, attaching an external browser (once),
      initiating an internal httpd server (once) and enabling the appropriate
      skewer-mode for the current buffer.
      
      Run this for any buffer you want to skewer."
        (interactive)
        (when (bound-and-true-p impatient-mode)
          (error "Skewer-mode isn't compatible with impatient mode"))
        (require 'skewer-mode)
        (unless (process-status "httpd")
          (run-skewer))
        (pcase major-mode
          ((or 'css-mode 'scss-mode 'less-css-mode)
           (unless (bound-and-true-p skewer-css-mode)
             (skewer-css-mode +1)))
          ((or 'web-mode 'html-mode)
           (unless (bound-and-true-p skewer-html-mode)
             (skewer-html-mode +1)))
          ('js2-mode
           (unless (bound-and-true-p skewer-mode)
             (skewer-mode +1)))
          (_ (error "Invalid mode %s" major-mode))))
      
      ;;;###autoload
      (defun +javascript/skewer-cleanup ()
        "Disable skewer-mode globally and disable the httpd server."
        (interactive)
        (when (process-status "httpd")
          (httpd-stop))
        (dolist (buf (buffer-list))
          (with-current-buffer buf
            (if (bound-and-true-p skewer-mode)
                (skewer-mode -1))
            (if (bound-and-true-p skewer-css-mode)
                (skewer-css-mode -1))
            (if (bound-and-true-p skewer-html-mode)
                (skewer-html-mode -1)))))
      
      ;;
      ;; Hooks
      
      ;;;###autoload
      (defun +javascript-cleanup-tide-processes-h ()
        "Clean up dangling tsserver processes if there are no more buffers with
      `tide-mode' active that belong to that server's project."
        (when tide-mode
          (unless (cl-loop with project-name = (tide-project-name)
                           for buf in (delq (current-buffer) (buffer-list))
                           if (and (buffer-local-value 'tide-mode buf)
                                   (with-current-buffer buf
                                     (string= (tide-project-name) project-name)))
                           return buf)
            (kill-process (tide-current-server)))))
      
      ;;
      ;; Advice
      
      ;;;###autoload
      (defun +javascript-tide-project-root-a ()
        "Resolve to `doom-project-root' if `tide-project-root' fails."
        (or tide-project-root
            (or (locate-dominating-file default-directory "tsconfig.json")
                (locate-dominating-file default-directory "jsconfig.json"))))
      
      (with-eval-after-load 'projectile
        (add-to-list 'projectile-globally-ignored-directories "^node_modules$")
        (add-to-list 'projectile-globally-ignored-directories "^flow-typed$"))
      
    • js2-mode   external
      (use-package js2-mode
        :defer t
        :interpreter ("node" . js2-mode)
        :hook (js2-mode . rainbow-delimiters-mode)
        :hook (js2-mode . js2-imenu-extras-mode)
        :hook (js2-mode . (lambda () (push '("function" . ?ƒ) prettify-symbols-alist)))
        :config
        (with-eval-after-load 'skewer-mode
          (add-hook 'js2-mode 'skewer-mode))
        (setq-default js2-basic-indent 2
                      js2-basic-offset 2
                      js2-auto-indent-p t
                      js2-cleanup-whitespace t
                      js2-enter-indents-newline t
                      js2-indent-on-enter-key t
                      js2-global-externs (list "window" "module" "require" "buster" "sinon" "assert" "refute" "setTimeout" "clearTimeout" "setInterval" "clearInterval" "location" "__dirname" "console" "JSON" "jQuery" "$")))
      
    • js2-refactor   external
      (use-package js2-refactor
        :defer t
        :hook (js2-mode . js2-refactor-mode)
        :init
        (js2r-add-keybindings-with-prefix "C-c C-r")
        :config
        (setq js2-skip-preprocessor-directives t)
        (with-eval-after-load 'general
          (general-nvmap
            :keymap 'js2-refactor-mode-map
            "zr" (general-simulate-key "C-c C-r"
                   :name general-z-r-simulates-C-c-C-r
                   :docstring "Simulates C-c C-r"
                   :which-key "JS2 Refactor")))
        )
      
    • rjsx-mode   external
      (use-package rjsx-mode
        :defer t
        :mode "\\.[mc]?js\\'"
        :mode "\\.es6\\'"
        :mode "\\.pac\\'"
        :hook (rjsx-mode . rainbow-delimiters-mode)
        :hook (rjsx-mode . rainbow-mode)
        :bind (([remap comment-dwim ] . rjsx-comment-dwim))
        :init
        (with-eval-after-load 'compilation
          (add-to-list 'compilation-error-regexp-alist 'node)
          (add-to-list 'compilation-error-regexp-alist-alist '(node "^[[:blank:]]*at \\(.*(\\|\\)\\(.+?\\):\\([[:digit:]]+\\):\\([[:digit:]]+\\)"
                                                                    2 3 4)))
        :config
        (with-eval-after-load 'compilation
          (add-to-list 'compilation-error-regexp-alist 'node)
          (add-to-list 'compilation-error-regexp-alist-alist
                       '(node "^[[:blank:]]*at \\(.*(\\|\\)\\(.+?\\):\\([[:digit:]]+\\):\\([[:digit:]]+\\)"
                              2 3 4)))
        (setq js-chain-indent t
              ;; These have become standard in the JS community
              js2-basic-offset 2
              ;; Don't mishighlight shebang lines
              js2-skip-preprocessor-directives t
              ;; let flycheck handle this
              js2-mode-show-parse-errors nil
              js2-mode-show-strict-warnings nil
              ;; Flycheck provides these features, so disable them: conflicting with
              ;; the eslint settings.
              js2-strict-missing-semi-warning nil
              ;; maximum fontification
              js2-highlight-level 3
              js2-idle-timer-delay 0.15)
        (with-eval-after-load 'general
          (general-nvmap
            :keymaps 'rjsx-mode-map
            "'" 'rjsx-jump-tag
            "gj" 'rjsx-jump-closing-tag
            "gk" 'rjsx-jump-opening-tag
            "gr" 'rjsx-rename-tag-at-point
            "z>" 'rjsx-electric-gt
            "z<" 'rjsx-electric-lt))
        )
      
    • xref-js2   external
      (use-package xref-js2
        :defer t
        :if (executable-find "ag") ; silver-searcher
        :config
        (add-hook 'js2-mode-hook (lambda ()
                                   (add-hook 'xref-backend-functions #'xref-js2-xref-backend nil t))))
      
    • typescript-mode   external
      (use-package typescript-mode
        :defer t
        :hook (typescript-mode . rainbow-delimiters-mode)
        :hook (typescript-tsx-mode . rainbow-delimiters-mode)
        :hook (typescript-mode . (lambda () (setq comment-line-break-function #'js2-line-break
                                                  typescript-indent-level (or (and (bound-and-true-p tide-mode)
                                                                                   (plist-get (tide-tsfmt-options) :indentSize))
                                                                              typescript-indent-level)
                                                  emmet-expand-jsx-className? t)))
        :init
        (add-to-list 'auto-mode-alist
                     (cons "\\.tsx\\'" #'typescript-mode))
        (with-eval-after-load 'flycheck
          (flycheck-add-mode 'javascript-eslint 'web-mode)
          (flycheck-add-mode 'javascript-eslint 'typescript-mode)
          (flycheck-add-mode 'javascript-eslint 'typescript-tsx-mode)
          (flycheck-add-mode 'typescript-tslint 'typescript-tsx-mode))
        (defun +javascript-disable-tide-checkers-h ()
          (add-to-list 'flycheck-disabled-checkers 'javascript-jshint)
          (add-to-list 'flycheck-disabled-checkers 'tsx-tide)
          (add-to-list 'flycheck-disabled-checkers 'jsx-tide))
        (add-hook 'typescript-tsx-mode-hook #'+javascript-disable-tide-checkers-h)
        :config
        (when (fboundp 'web-mode)
          (define-derived-mode typescript-tsx-mode web-mode "TypeScript-TSX"))
        (autoload 'js2-line-break "js2-mode" nil t))
      
    • tide   external
      (use-package tide
        :defer t
        :hook (tide-mode . tide-hl-identifier-mode)
        :config
        (setq tide-completion-detailed t
              tide-always-show-documentation t
              tide-server-max-response-length 524288
              tide-completion-setup-company-backend nil)
        (add-hook 'tide-mode-hook (lambda () (add-hook 'kill-buffer-hook #'+javascript-cleanup-tide-processes-h nil 'local))))
      
    • npm-mode   external
      (use-package npm-mode
        :defer t)
      
    • nodejs-repl   external
      (use-package nodejs-repl
        :defer t)
      
  • Latex
    • auctex
      (use-package auctex
        :straight nil
        :mode (("\\.tex\\'" . latex-mode)
               ("\\.latex\\'" . latex-mode))
        :commands (latex-mode LaTeX-mode plain-tex-mode)
        :init
        (progn
          (add-hook 'LaTeX-mode-hook #'LaTeX-preview-setup)
          (add-hook 'LaTeX-mode-hook #'flyspell-mode)
          (add-hook 'LaTeX-mode-hook #'turn-on-reftex)
          (setq-default TeX-engine 'xetex)
          (setq TeX-auto-save nil
                TeX-parse-self nil
                TeX-save-query nil
                TeX-PDF-mode t)
          (setq-default TeX-master nil)))
      
    • preview
      (use-package preview
        :straight nil
        :after auctex
        :commands LaTeX-preview-setup
        :init
        (progn
          (setq-default preview-scale 1.4
                        preview-scale-function '(lambda () (* (/ 10.0 (preview-document-pt)) preview-scale)))))
      
    • reftex
      (use-package reftex
        :straight nil
        :commands turn-on-reftex
        :init
        (setq reftex-plug-into-AUCTeX t))
      
    • bibtex
      (use-package bibtex
        :straight nil
        :config
        (setq bibtex-autokey-year-length 4
              bibtex-autokey-name-year-separator "-"
              bibtex-autokey-year-title-separator "-"
              bibtex-autokey-titleword-separator "-"
              bibtex-autokey-titlewords 2
              bibtex-autokey-titlewords-stretch 1
              bibtex-autokey-titleword-length 5))
      
    • LaTex
      ;; Auto-fill for LaTeX
      (defun lem-latex-auto-fill ()
        "Turn on auto-fill for LaTeX mode."
        (turn-on-auto-fill)
        (set-fill-column 80)
        (setq default-justification 'left))
      (add-hook 'LaTeX-mode-hook #'lem-latex-auto-fill)
      
      ;; Compilation command
      (add-hook 'LaTeX-mode-hook (lambda () (setq compile-command "latexmk -pdflatex=xelatex -f -pdf %f")))
      
      ;; Prevent ispell from verifying some LaTeX commands
      ;; http://stat.genopole.cnrs.fr/dw/~jchiquet/fr/latex/emacslatex
      (defvar lem-ispell-tex-skip-alists
        '("cite" "nocite"
          "includegraphics"
          "author" "affil"
          "ref" "eqref" "pageref"
          "label"))
      (setq ispell-tex-skip-alists
            (list
             (append (car ispell-tex-skip-alists)
                     (mapcar #'(lambda (cmd) (list (concat "\\\\" cmd) 'ispell-tex-arg-end)) lem-ispell-tex-skip-alists))
             (cadr ispell-tex-skip-alists)))
      
      ;; Indentation with align-current in LaTeX environments
      (defvar lem-LaTeX-align-environments '("tabular" "tabular*"))
      (add-hook 'LaTeX-mode-hook
                (lambda ()
                  (require 'align)
                  (setq LaTeX-indent-environment-list
                        ;; For each item in the list...
                        (mapcar (lambda (item)
                                  ;; The car is an environment
                                  (let ((env (car item)))
                                    ;; If this environment is in our list...
                                    (if (member env lem-LaTeX-align-environments)
                                        ;; ...then replace this item with a correct one
                                        (list env 'align-current)
                                      ;; else leave it alone
                                      item)))
                                LaTeX-indent-environment-list))))
      
      ;; Use dvipdfmx to convert DVI files to PDF in AUCTeX
      (eval-after-load 'tex
        '(add-to-list 'TeX-command-list
                      '("DVI to PDF" "dvipdfmx %d" TeX-run-command t t) t))
      
      ;; SyncTeX (http://www.emacswiki.org/emacs/AUCTeX#toc19)
      (defun synctex/un-urlify (fname-or-url)
        "A trivial function that replaces a prefix of file:/// with just /."
        (if (string= (substring fname-or-url 0 8) "file:///")
            (substring fname-or-url 7)
          fname-or-url))
      
  • Markdown
    • Basic Configuration
      (use-package markdown-mode
        :mode
        ("README\\.md\\'" . gfm-mode)
        :init
        (when (executable-find "markdown")
          (setq markdown-command "markdown"))
        (when (executable-find "multimarkdown")
          (setq markdown-command "multimarkdown"))
        :config
        (with-eval-after-load 'lsp-mode
          (add-hook 'markdown-mode-hook #'lsp-deferred)))
      
      ;; (use-package markdown-ts-mode
      ;;   :init
      ;;   (add-to-list 'major-mode-remap-alist '(markdown-mode . markdown-ts-mode)))
      
      (use-package poly-markdown
        :init
        :mode
        (("README\\.md\\'" . gfm-mode)
         ("\\.md$" . markdown-mode)
         ("\\.markdown$" . markdown-mode)))
      
      (use-package edit-indirect
        :after markdown-mode)
      
  • PlantUML
    (use-package plantuml-mode
      :commands plantuml-download-jar
      :init
      (setq plantuml-jar-path (concat user-emacs-directory "data/plantuml.jar")
            org-plantuml-jar-path plantuml-jar-path)
      :config
      (setq plantuml-default-exec-mode
            (cond ((file-exists-p plantuml-jar-path) 'jar)
                  ((executable-find "plantuml") 'executable)
                  (plantuml-default-exec-mode))))
    
    (use-package flycheck-plantuml
      :after plantuml-mode
      :config
      (flycheck-plantuml-mode)
      (when (eq plantuml-default-exec-mode 'executable)
        ;; Surprisingly, this works, even though flycheck-plantuml specifies -Djava.awt...
        (setq-default flycheck-plantuml-executable plantuml-executable-path)))
    
    (defun +plantuml-org-babel-execute:plantuml-a (body params)
      "Execute a block of plantuml code with org-babel.
    This function is called by `org-babel-execute-src-block'."
      (require 'plantuml-mode)
      ;; REVIEW Refactor me
      (let* ((body (replace-regexp-in-string
                    "^[[:blank:]\n]*\\(@start\\)"
                    "\\\\\\1"
                    body))
             (fullbody (org-babel-plantuml-make-body body params))
             (out-file (or (cdr (assq :file params))
                           (org-babel-temp-file "plantuml-" ".png")))
             (in-file (org-babel-temp-file "plantuml-")))
        (if (eq plantuml-default-exec-mode 'server)
            (if (bound-and-true-p org-export-current-backend)
                (user-error "Exporting plantuml diagrams in server mode is not supported (see `plantuml-default-exec-mode')")
              (save-current-buffer
                (save-match-data
                  (with-current-buffer
                      (url-retrieve-synchronously (plantuml-server-encode-url body)
                                                  nil t)
                    (goto-char (point-min))
                    ;; skip the HTTP headers
                    (while (not (looking-at "\n")) (forward-line))
                    (delete-region (point-min) (+ 1 (point)))
                    (write-file out-file)))))
          (let* ((cmd (concat (cond ((eq plantuml-default-exec-mode 'executable)
                                     (unless (executable-find plantuml-executable-path)
                                       (error "Could not find plantuml at %s"
                                              (executable-find plantuml-executable-path)))
                                     (concat (shell-quote-argument (executable-find plantuml-executable-path))
                                             " --headless"))
                                    ((not (file-exists-p plantuml-jar-path))
                                     (error "Could not find plantuml.jar at %s" org-plantuml-jar-path))
                                    ((concat "java " (cdr (assoc :java params)) " -jar "
                                             (shell-quote-argument
                                              (expand-file-name plantuml-jar-path)))))
                              " "
                              (pcase (file-name-extension out-file)
                                ("png" "-tpng")
                                ("svg" "-tsvg")
                                ("eps" "-teps")
                                ("pdf" "-tpdf")
                                ("tex" "-tlatex")
                                ("vdx" "-tvdx")
                                ("xmi" "-txmi")
                                ("scxml" "-tscxml")
                                ("html" "-thtml")
                                ("txt" "-ttxt")
                                ("utxt" "-utxt"))
                              " "
                              " -p " (cdr (assoc :cmdline params)) " < "
                              (org-babel-process-file-name in-file)
                              " > "
                              (org-babel-process-file-name out-file))))
            (with-temp-file in-file (insert fullbody))
            (message "%s" cmd)
            (org-babel-eval cmd "")))
        (unless (cdr (assq :file params))
          out-file)))
    
    (with-eval-after-load 'ob-plantuml
      (advice-add #'org-babel-execute:plantuml
                  :override #'+plantuml-org-babel-execute:plantuml-a)
      (add-to-list 'org-babel-default-header-args:plantuml
                   '(:cmdline . "-charset utf-8")))
    
  • Python
    • Builtin python mode   builtin
      (use-package python
        :straight nil
        :mode ("[./]flake8\\'" . conf-mode)
        :mode ("/Pipfile\\'" . conf-mode)
        :hook ((python-mode . lsp-deferred)
               (python-mode . lsp-ui-mode)
               (python-mode . lsp-ui-doc-mode)
               (python-mode . +python-use-correct-flycheck-executables-h)
               ;; (python-mode . guess-style-guess-tab-mode)
               (python-mode-local-vars . (lambda ()
                                           (lsp-deferred))))
        :config
        (if (executable-find "ipython")
            (setq python-interpreter (executable-find "ipython"))
          (setq python-interpreter (executable-find "python3")))
        (when (featurep 'projectile)
          (add-to-list 'projectile-globally-ignored-directories "^\\.venv$"))
        (let ((directories `("/usr/bin/" "/usr/local/bin/" "/opt/bin" ,(expand-file-name ".local/bin/" (getenv "HOME")))))
          (dolist (directory directories) (when (file-directory-p directory) (add-to-list 'python-shell-exec-path directory))))
        (setq python-indent-guess-indent-offset nil
              python-shell-completion-native-enable nil
              python-shell-exec-path (list "/usr/bin/" "/usr/local/bin" (expand-file-name ".local/bin/" (getenv "HOME")))
              python-indent-guess-indent-offset-verbose nil)
        (when (featurep 'lsp-mode)
          (setq lsp-pylsp-plugins-rope-completion-enabled t ; needs python-rope package
                lsp-pylsp-plugins-mccabe-enabled t ; needs python-mccabe package
                lsp-ruff-lsp-python-path (executable-find "python3")
                )
          (when (executable-find "black")
            (setq lsp-pylsp-plugins-black-enabled t))
          (when (executable-find "autopep8")
            (setq lsp-pylsp-plugins-autopep8-enabled t))
          (when (executable-find "flake8")
            (setq lsp-pylsp-plugins-flake8-enabled t))
          (when (executable-find "pycodestyle")
            (setq lsp-pylsp-plugins-pycodestyle-enabled t))
          (when (executable-find "pydocstyle")
            (setq lsp-pylsp-plugins-pydocstyle-enabled t))
          (when (executable-find "pylint")
            (setq lsp-pylsp-plugins-pylint-enabled t))
          (when (executable-find "pyflakes")
            (setq lsp-pylsp-plugins-pyflakes-enabled t))
          (when (executable-find "yapf")
            (setq lsp-pylsp-plugins-yapf-enabled t)))
        (when (featurep 'flycheck)
          (setq flycheck-python-mypy-executable (executable-find "mypy")
                flycheck-python-flake8-executable (executable-find "flake8")
                flycheck-python-pylint-executable (executable-find "pylint")
                flycheck-python-pyright-executable (executable-find "pyright")))
        (with-eval-after-load 'tree-sitter
          (add-hook 'python-mode-local-vars-hook #'tree-sitter! 'append))
        (with-eval-after-load 'general
          (+config/local-leader
            :keymaps 'python-mode-map
            "d" '(:ignore t :wk "doc")))
        )
      
      (defun +python-executable-find (exe)
        "Resolve the path to the EXE executable.
      Tries to be aware of your active conda/pipenv/virtualenv environment, before
      falling back on searching your PATH."
        (if (file-name-absolute-p exe)
            (and (file-executable-p exe)
                 exe)
          (let ((exe-root (format "bin/%s" exe)))
            (cond ((when python-shell-virtualenv-root
                     (let ((bin (expand-file-name exe-root python-shell-virtualenv-root)))
                       (if (file-exists-p bin) bin))))
                  ((when (require 'conda nil t)
                     (let ((bin (expand-file-name (concat conda-env-current-name "/" exe-root)
                                                  (conda-env-default-location))))
                       (if (file-executable-p bin) bin))))
                  ((when-let (bin (projectile-locate-dominating-file default-directory "bin/python"))
                     (setq-local doom-modeline-python-executable (expand-file-name "bin/python" bin))))
                  ((executable-find exe))))))
      
      (defun +python/open-repl ()
        "Open the Python REPL."
        (interactive)
        (require 'python)
        (unless python-shell-interpreter
          (user-error "`python-shell-interpreter' isn't set"))
        (pop-to-buffer
         (process-buffer
          (let ((dedicated (bound-and-true-p python-shell-dedicated)))
            (if-let* ((pipenv (+python-executable-find "pipenv"))
                      (pipenv-project (pipenv-project-p)))
                (let ((default-directory pipenv-project)
                      (python-shell-interpreter-args
                       (format "run %s %s"
                               python-shell-interpreter
                               python-shell-interpreter-args))
                      (python-shell-interpreter pipenv))
                  (run-python nil dedicated t))
              (run-python nil dedicated t))))))
      
      (defun +python/open-ipython-repl ()
        "Open an IPython REPL."
        (interactive)
        (require 'python)
        (let ((python-shell-interpreter
               (or (+python-executable-find (car +python-ipython-command))
                   "ipython"))
              (python-shell-interpreter-args
               (string-join (cdr +python-ipython-command) " ")))
          (+python/open-repl)))
      
      (defun +python/open-jupyter-repl ()
        "Open a Jupyter console."
        (interactive)
        (require 'python)
        (add-to-list 'python-shell-completion-native-disabled-interpreters "jupyter")
        (let ((python-shell-interpreter
               (or (+python-executable-find (car +python-jupyter-command))
                   "jupyter"))
              (python-shell-interpreter-args
               (string-join (cdr +python-jupyter-command) " ")))
          (+python/open-repl)))
      
      (defun +python/optimize-imports ()
        "organize imports"
        (interactive)
        (pyimport-remove-unused)
        (py-isort-buffer))
      
      (defun +python-use-correct-flycheck-executables-h ()
            "Use the correct Python executables for Flycheck."
            (let ((executable python-shell-interpreter))
              (save-excursion
                (goto-char (point-min))
                (save-match-data
                  (when (or (looking-at "#!/usr/bin/env \\(python[^ \n]+\\)")
                            (looking-at "#!\\([^ \n]+/python[^ \n]+\\)"))
                    (setq executable (substring-no-properties (match-string 1))))))
              ;; Try to compile using the appropriate version of Python for
              ;; the file.
              (setq-local flycheck-python-pycompile-executable executable)
              ;; We might be running inside a virtualenv, in which case the
              ;; modules won't be available. But calling the executables
              ;; directly will work.
              (setq-local flycheck-python-pylint-executable "pylint")
              (setq-local flycheck-python-flake8-executable "flake8")))
      
      (defun +python-pyenv-mode-set-auto-h ()
        "Set pyenv-mode version from buffer-local variable."
        (when (eq major-mode 'python-mode)
          (when (not (local-variable-p '+pyenv--version))
            (make-local-variable '+pyenv--version)
            (setq +pyenv--version (+python-pyenv-read-version-from-file)))
          (if +pyenv--version
              (pyenv-mode-set +pyenv--version)
            (pyenv-mode-unset))))
      
      (defun +python-pyenv-read-version-from-file ()
        "Read pyenv version from .python-version file."
        (when-let (root-path (projectile-locate-dominating-file default-directory ".python-version"))
          (let* ((file-path (expand-file-name ".python-version" root-path))
                 (version
                  (with-temp-buffer
                    (insert-file-contents-literally file-path)
                    (string-trim (buffer-string)))))
            (if (member version (pyenv-mode-versions))
                version  ;; return.
              (message "pyenv: version `%s' is not installed (set by `%s')."
                       version file-path)))))
      
    • pydoc   external
      (use-package pydoc
        :config
        (with-eval-after-load 'general
          (general-def
            :keymaps 'python-mode-map
            "C-c C-d" 'pydoc-at-point-no-jedi))
        )
      
    • ob-ipython
      (use-package ob-ipython
        :defer t
        :init
        (setq ob-ipython-resources-dir ".ob-ipython-resrc")
        (with-eval-after-load 'org-src
          (add-to-list 'org-src-lang-modes '("ipython" . python)))
        (with-eval-after-load 'ob-async
          (add-to-list 'ob-async-no-async-languages-alist "ipython")))
      
    • pip-requirements   external
      (use-package pip-requirements
        :defer t
        :config
        (add-hook 'pip-requirements-mode-hook #'pip-requirements-auto-complete-setup))
      
    • inferior-python-mode   builtin
      (use-package python
        :straight nil
        ;;:hook (inferior-python-mode . hide-mode-line-mode)
        :config
        (+config/local-leader
          :keymaps 'python-mode-map
          "r" '(run-python :wk "run python")))
      
    • lsp-pyright   external
      (use-package lsp-pyright
        :after lsp-mode
        :hook (python-mode . (lambda ()
                               (require 'lsp-pyright)
                               (lsp-deferred)
                               (lsp-ui-mode)
                               (lsp-ui-doc-mode)))
        :config
        (setq lsp-pyright-python-executable-cmd
              (if (executable-find "python3.12")
                  (executable-find "python3")
                "python")))
      
    • python-docstring   external
      (use-package python-docstring
        :diminish
        :hook (python-mode . python-docstring-mode)
        :config
        (with-eval-after-load 'general 
          (+config/local-leader
            :keymaps 'python-mode-map
            "dq" 'python-docstring-fill))
        )
      
    • pipenv   external
      (use-package pipenv
        :diminish
        :after python
        :commands pipenv-project-p
        :hook (python-mode . pipenv-mode)
        :init
        (setq pipenv-executable (executable-find "pipenv"))
        :config
        (setq pipenv-with-projectile nil)
        (with-eval-after-load 'general
          (+config/local-leader
            :keymaps 'python-mode-map
            "p" '(:keymap pipenv-command-map :package pipenv :wk "pipenv")))
        )
      
    • pyvenv   external
      ;; (use-package pyvenv
      ;;   :after python
      ;;   :demand t
      ;;   :init
      ;;   (pyvenv-mode +1)
      ;;   :config
      ;;   ;; Set correct Python interpreter
      ;;   (setq pyvenv-post-activate-hooks
      ;;         (list (lambda ()
      ;;                 (setq python-shell-interpreter (concat pyvenv-virtual-env "bin/python3")
      ;;                       lsp-pyright-venv-path pyvenv-virtual-env))))
      ;;   (setq pyvenv-post-deactivate-hooks
      ;;         (list (lambda ()
      ;;                 (setq python-shell-interpreter "python3"
      ;;                       lsp-pyright-venv-path nil)))))
      
      (use-package pyvenv
        :after python
        :config
        (add-hook 'python-mode-local-vars-hook #'pyvenv-track-virtualenv)
        (add-to-list 'global-mode-string
                     '(pyvenv-virtual-env-name (" venv:" pyvenv-virtual-env-name " "))
                     'append))
      
    • pyimport
      (use-package pyimport
        :defer t
        :config
        (with-eval-after-load 'general
          (+config/local-leader
            :keymaps 'python-mode-map
            "i" '(:ignore t :wk "import")
            "ii" 'pyimport-insert-missing
            "iR" 'pyimport-remove-unused
            "io" '+python/optimize-imports)))
      
    • py-isort
      (use-package py-isort
        :defer t
        :config
        (with-eval-after-load 'general
          (+config/local-leader
            :keymaps 'python-mode-map
            "i" '(:ignore t :wk "import")
            "is" 'py-isort-buffer
            "ir" 'py-isort-region)))
      
    • python-pytest
      (use-package python-pytest
        :commands python-pytest-dispatch
        :config
        (with-eval-after-load 'general
          (+config/local-leader
            :keymaps 'python-mode-map
            "t" '(:ignore t :wk "test")
            "ta" 'python-pytest
            "tf" 'python-pytest-file-dwim
            "tF" 'python-pytest-file
            "tt" 'python-pytest-function-dwim
            "tT" 'python-pytest-function
            "tr" 'python-pytest-repeat
            "tp" 'python-pytest-dispatch)))
      
    • poetry
      (use-package poetry
        :after python
        :init
        (setq poetry-tracking-strategy 'switch-buffer)
        ;; (add-hook 'python-mode-hook #'poetry-tracking-mode)
        :config
        (with-eval-after-load 'general
          (+config/local-leader
            :keymaps 'python-mode-map
            "p" 'poetry))
        )
      
    • pyenv
      (use-package pyenv
        :after python
        :config
        (when (executable-find "pyenv")
          (global-pyenv-mode)
          (add-to-list 'exec-path (expand-file-name "shims" (or (getenv "PYENV_ROOT") "~/.pyenv"))))
        (add-hook 'python-mode-local-vars-hook #'+python-pyenv-mode-set-auto-h))
      
    • flycheck-pycheckers   external
      (use-package flycheck-pycheckers
        :after flycheck
        :config
        (with-eval-after-load 'flycheck
          (add-hook 'flycheck-mode-hook #'flycheck-pycheckers-setup)))
      
    • ein   external
      (use-package ein)
      
  • Rust
    • rust-mode
      ;;;###autoload
      (defun +rust-cargo-project-p ()
        "Return t if this is a cargo project."
        (locate-dominating-file buffer-file-name "Cargo.toml"))
      
      ;;; Custom Cargo commands
      
      (autoload 'rustic-run-cargo-command "rustic-cargo")
      ;;;###autoload
      (defun +rust/cargo-audit ()
        "Run 'cargo audit' for the current project."
        (interactive)
        (rustic-run-cargo-command "cargo audit"))
      
      (with-eval-after-load 'projectile
        (add-to-list 'projectile-project-root-files "Cargo.toml"))
      
      
    • rustic   external
      (use-package rustic
        :mode ("\\.rs$" . rustic-mode)
        :bind
        (:map rustic-mode-map
              ("M-j" . lsp-ui-menu)
              ("M-?" . lsp-find-references)
              ("C-c C-c l" . flycheck-list-errors)
                    ("C-c C-c a" . lsp-execute-code-action)
                    ("C-c C-c r" . lsp-rename)
                    ("C-c C-c q" . lsp-workspace-restart)
                    ("C-c C-c Q" . lsp-workspace-shutdown)
                    ("C-c C-c s" . lsp-rust-analyzer-status))
        :init
        (with-eval-after-load 'rustic-flycheck
          (remove-hook 'rustic-mode-hook #'flycheck-mode)
          (remove-hook 'rustic-mode-hook #'flymake-mode-off)
          (remove-hook 'flycheck-mode-hook #'rustic-flycheck-setup))
        (with-eval-after-load 'org-src
          (defalias 'org-babel-execute:rust #'org-babel-execute:rustic)
          (add-to-list 'org-src-lang-modes '("rust" . rustic)))
        :config
        (add-hook 'rustic-cargo-run-mode-hook (lambda ()
                                               (comint-mode)
                                               (read-only-mode 0)))
        (add-hook 'rustic-mode-hook #'rainbow-delimiters-mode)
        (setq rustic-indent-method-chain t)
        (add-hook 'rustic-mode-local-vars-hook #'rustic-setup-lsp 'append)
        (add-hook 'rustic-mode-local-vars-hook #'flycheck-mode)
        (with-eval-after-load 'tree-sitter
          (add-hook 'rustic-mode-local-vars-hook #'tree-sitter! 'append))
        (when (executable-find "rustup")
          (setq rustic-analyzer-command
                `(,(executable-find "rustup")
                  "run"
                  "stable"
                  "rust-analyzer")))
        (setq rustic-indent-method-chain t)
        (setq rustic-babel-format-src-block nil
              rustic-format-trigger nil)
        (defun +rust/cargo-audit ()
          "Run 'cargo audit' for the current project."
          (interactive)
          (rustic-run-cargo-command "cargo audit"))
        (with-eval-after-load 'general
          (+config/local-leader
            :keymaps 'rustic-mode-map
            "b" '(:ignore t :wk "build")
            "ba" '+rust/cargo-audit
            "bb" 'rustic-cargo-build
            "bB" 'rustic-cargo-bench
            "bc" 'rustic-cargo-check
            "bC" 'rustic-cargo-clippy
            "bd" 'rustic-cargo-build-doc
            "bD" 'rustic-cargo-doc
            "bf" 'rustic-cargo-fmt
            "bn" 'rustic-cargo-new
            "bo" 'rustic-cargo-outdated
            "br" 'rustic-cargo-run
            "t" '(:ignore t :wk "test")
            "ta" 'rustic-cargo-test
            "t" 'rustic-cargo-current-test)))
      
    • flycheck-rust   external
      (use-package flycheck-rust
        :after flycheck
        :config
        (with-eval-after-load 'rustic
          (add-hook 'flycheck-mode-hook #'flycheck-rust-setup)))
      
  • Ansible
    • ansible   external
      (use-package ansible
        :defer t
        :if (executable-find "ansible")
        :commands ansible-auto-decrypt-encrypt
        :hook (ansible . 'ansible-auto-decrypt-encrypt)
        :init
        (put 'ansible-vault-password-file 'safe-local-variable #'stringp)
        :config
        (setq ansible-section-face 'font-lock-variable-name-face
              ansible-task-label-face 'font-lock-doc-face)
        (with-eval-after-load 'general
          (+config/local-leader
            :keymaps 'yaml-mode-map
            "a" '(:ignore t :wk ansible)
            "ad" 'ansible-decrypt-buffer
            "ae" 'ansible-encrypt-buffer))
        )
      
    • ansible-doc   external
      (use-package ansible-doc
        :after ansible
        :hook (yaml-mode . ansible-doc-mode)
        :config
        (when (featurep 'evil)
          (evil-set-initial-state '(ansible-doc-module-mode) 'emacs)))
      
  • Perl
    • cperl-mode   external
      (use-package cperl-mode
        :defer t
        :mode ("\\.\\([pP][Llm]\\|al\\)\\'" . cperl-mode))
      
  • Haskell
    • haskell-mode   external
      (use-package haskell-mode
        :defer t
        :init
        (setq flymake-allowed-file-name-masks nil)
        :custom
        (haskell-process-load-or-reload-prompt t)
        (haskell-process-auto-import-loaded-modules t)
        (haskell-process-log t)
        (haskell-tags-on-save t)
        :config
        (with-eval-after-load 'lsp-mode
          (add-hook 'haskell-mode-hook #'lsp-deferred)))
      
    • lsp-haskell   external
      (use-package lsp-haskell
        :defer t
        :after lsp)
      
  • Ron-mode
    (use-package ron-mode)
    
  • yuck-mode
    (use-package yuck-mode)
    
  • java

    Lsp-java requires jdt-language-server, this package should automatically installs it using the lsp-install-server commands

    (use-package lsp-java
      :after lsp-mode
      :hook (java-mode . lsp))
    
  • fish-mode
    (use-package fish-mode
      :config
      (setq fish-enable-auto-indent t))
    

Date: 2024-04-20 Sat 00:00

Author: Kristian Alexander P

Created: 2024-12-03 Tue 15:42