Preface

Overview

This is my personal configuration of GNU Emacs. Many of these settings have been stolen from research and other configurations. I have a very basic use-case, and this configuration is, currently, pretty straightforward to read.

Probably I’m using this operating system editor/interactive-interpreter as daily driver now. In the last section I explain my “experience” and reasons why I’m using Emacs.

Notes on this document

Every file is commented in a decent way, but I’m not very verbose. Sometimes I describe every single things written (in a decent way, I guess), other times, I’m awful.

Anyway, this document is not intended as a way to show my elisp-fu or something like that. When you read this, imagine a diary, a tale of my [mis]adventures 🤣.

Before GUI: “early init”

Is good practice to define an early-init.el file: this kind of approach provides better loading for essential stuff.

  • There are some tweaks taken from DOOM Emacs, David Wilson, Protesilaos Stavrou…but I’ll put some credits at the end of this document, along with useful resources.
  • The package manager, straight.el, provides reproducibility (like Nix and Guix) with recipes, allows the editing of packages and manual version control operations on repos. Here the list of advantages.
;;; early-init.el --- Early Init File -*- lexical-binding: t -*-

;;; Commentary:

;; Early init file has been introduced in Emacs 27, it is a file loaded
;; before GUI is initialized, so unwanted elements are here.
;; Example: scroll-bars, fringes, menu-bar, tool-bar.

;;; Code:

;; A big contributor to startup times is garbage collection. We up the gc
;; threshold to temporarily prevent it from running, then reset it later by
;; enabling `gcmh-mode'.
(setq gc-cons-threshold  most-positive-fixnum)

;; Add load-path for submodules
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))

;; Set a better directory to store the native comp cache
(when (and (fboundp 'native-comp-available-p)
           (native-comp-available-p))
  (add-to-list 'native-comp-eln-load-path (expand-file-name "var/eln-cache/" user-emacs-directory)))

;; From DOOM
;; Prevent unwanted runtime compilation for gccemacs (native-comp) users;
;; packages are compiled ahead-of-time when they are installed and site files
;; are compiled when gccemacs is installed.
(when (and (fboundp 'native-comp-available-p)
     (native-comp-available-p))
  (setq native-comp-deferred-compilation nil)
  ;; Silence compiler warnings as they can be pretty disruptive
  (setq native-comp-async-report-warnings-errors nil))

;; Another trick from DOOM
(unless (or (daemonp)
            noninteractive
            init-file-debug)
  (let ((old-file-name-handler-alist file-name-handler-alist))
    ;; `file-name-handler-alist' is consulted on each `require', `load' and
    ;; various path/io functions. You get a minor speed up by unsetting this.
    ;; Some warning, however: this could cause problems on builds of Emacs where
    ;; its site lisp files aren't byte-compiled and we're forced to load the
    ;; *.el.gz files (e.g. on Alpine).
    (setq-default file-name-handler-alist nil)
    ;; ...but restore `file-name-handler-alist' later, because it is needed for
    ;; handling encrypted or compressed files, among other things.
    (defun doom-reset-file-handler-alist-h ()
      (setq file-name-handler-alist
            ;; Merge instead of overwrite because there may have bene changes to
            ;; `file-name-handler-alist' since startup we want to preserve.
            (delete-dups (append file-name-handler-alist
                                 old-file-name-handler-alist))))
    (add-hook 'emacs-startup-hook #'doom-reset-file-handler-alist-h 101))

  ;; Premature redisplays can substantially affect startup times and produce
  ;; ugly flashes of unstyled Emacs.
  (setq-default inhibit-redisplay t
                inhibit-message t)
  (add-hook 'window-setup-hook
            (lambda ()
              (setq-default inhibit-redisplay nil
                            inhibit-message nil)
              (redisplay))))

;; From DOOM
;;
;; NOTE: In DOOM these are defined in another file, not in early init, that's horrible because
;; starting a client where this settings are defined later causes a little flash at startup (before redisplay)
;; where menu-bar is present.
;;
;; Not calling `menu-bar-mode', `tool-bar-mode', and
;; `scroll-bar-mode' because they do extra and unnecessary work that can be more
;; concisely and efficiently expressed with these six lines:
(push '(menu-bar-lines . 0)   default-frame-alist)
(push '(tool-bar-lines . 0)   default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)

;; And set these to nil so users don't have to toggle the modes twice to
;; reactivate them.
(setq menu-bar-mode nil
      tool-bar-mode nil
      scroll-bar-mode nil
      column-number-mode t
      fringe-mode 10)

;; Minor message for gc after loading
(add-hook 'emacs-startup-hook
          (lambda ()
            (message "Emacs loaded in %s with %d garbage collections."
                     (emacs-init-time) gcs-done)))

;; In Emacs 27+, package initialization occurs before `user-init-file' is
;; loaded, but after `early-init-file'. Doom handles package initialization, so
;; we must prevent Emacs from doing it early!
(setq package-enable-at-startup nil
      package-quickstart nil)

;; Configure and bootstrap `straight.el'
(setq straight-repository-branch "develop"
      straight-check-for-modifications '(check-on-save find-when-checking)
      straight-profiles `((nil . ,(expand-file-name "straight/versions/lock.el" user-emacs-directory))))

(defvar bootstrap-version)
(let ((bootstrap-file
       (expand-file-name "straight/repos/straight.el/bootstrap.el" user-emacs-directory))
      (bootstrap-version 5))
  (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))

;; Additional post-setup of `straight.el'
(require 'straight-x)
(defalias 'straight-ಠ_ಠ-mode nil)

;;; early-init.el ends here

Mandatory settings for the “init”

Note that init.el is mandatory, however I’m tangling it from this .org file (Emacs.org).

I have decided to tangle this document in init.el because I want to keep a few things in the main directory, while populating submodules to manage all of this.


;;; init.el --- Load the full configuration -*- lexical-binding: t -*-
;;; Commentary:

;; This file bootstraps the configuration, which is divided into
;; a number of other files.

;; NOTE: This file is generated from `Emacs.org`!

;;; Code:

;; We don't want user customizations in `init.el`, instead we use `custom.el`
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))

;; Disable damn sleep!
;; Yep, it's mandatory, that's the worst keybind ever, and should be remapped
(global-unset-key (kbd "C-z"))

Core settings

Convenience

Functions to determine if we are using a Nix installation of Emacs, or not, then we set our configuration path.


(defun archer-using-nix-p ()
  "Verifies if the running Emacs executable is under the `/nix/store/' path."
  (unless (or (equal system-type 'ms-dos)
              (equal system-type 'windows-nt))
    ;; Since there is no windows implementation of nix
    (string-prefix-p "/nix/store/"
                     (file-truename
                      (executable-find
                       (car command-line-args))))))

(defvar archer-config-path
  (let ((real-path (expand-file-name
                    ".dotfiles/home/modules/editors/emacs/config/"
                    (getenv "HOME"))))
    (if (and (archer-using-nix-p)
             (file-exists-p real-path))
        (expand-file-name real-path)
      (expand-file-name user-emacs-directory))))

Packages bootstrap

We are requiring init-setup, where configuration tools based on macros (e.g. use-package, leaf.el, setup.el) are initialized. I’m using setup.el right now. Compared to use-package, setup.el is less declarative: you have more control, I would say that it’s similar to vanilla Emacs configuration, but less verbose and with easy definition of new macros.

I also install blackout.el (and define a macro with setup.el) here, to manage modes displayed in the mode-line.


;; Require package management file
(require 'init-setup)
;;; init-setup.el --- `setup.el' configuration -*- lexical-binding: t -*-

;;; Commentary:

;; The package `setup.el' is configured here, with new forms and settings.

;;; Code:

(straight-use-package 'setup)
(require 'setup)

;; Forms section

(setup-define :quit
  'setup-quit
  :documentation "Always stop evaluating the body.")

(setup-define :needs
  (lambda (executable)
    `(unless (executable-find ,executable)
       ,(setup-quit)))
  :documentation "If EXECUTABLE is not in the path, stop here."
  :repeatable 1)

(setup-define :autoload
  (lambda (func)
    (let ((fn (if (memq (car-safe func) '(quote function))
                  (cadr func)
                func)))
      `(unless (fboundp (quote ,fn))
         (autoload (function ,fn) ,(symbol-name (setup-get 'feature)) nil t))))
  :documentation "Autoload COMMAND if not already bound."
  :repeatable t
  :signature '(FUNC ...))

(setup-define :load-after
  (lambda (features &rest body)
    (let ((body `(progn
                   (require ',(setup-get 'feature))
                   ,@body)))
      (dolist (feature (nreverse (ensure-list features)))
        (setq body `(with-eval-after-load ',feature ,body)))
      body))
  :indent 1
  :documentation "Load the current feature after FEATURES.")

(setup-define :with-after
  (lambda (features &rest body)
    (let ((body `(progn ,@body)))
      (dolist (feature (nreverse (ensure-list features)))
        (setq body `(with-eval-after-load ',feature ,body)))
      body))
  :indent 1
  :documentation "Evaluate BODY after FEATURES are loaded.")

(setup-define :hooks
  (lambda (hook func)
    `(add-hook ',hook #',func))
  :documentation "Add pairs of hooks."
  :repeatable t)

(setup-define :face
  (lambda (face spec) `(custom-set-faces (quote (,face ,spec))))
  :documentation "Customize FACE to SPEC."
  :signature '(face spec ...)
  :debug '(setup)
  :repeatable t
  :after-loaded t)

;; Blackout to hide minor modes
(straight-use-package 'blackout)
(setup-define :blackout
  (lambda (&optional mode)
    (let* ((mode (or mode (setup-get 'mode)))
           (mode (if (string-match-p "-mode\\'" (symbol-name mode))
                     mode
                   (intern (format "%s-mode" mode)))))
      `(blackout ',mode)))
  :documentation "Hide the mode-line lighter of the current mode with blackout.
MODE can be specified manually, and override the current-mode."
  :after-loaded t)

;; From https://git.acdw.net/emacs/tree/lisp/+setup.el
(defun +setup-warn (message &rest args)
  "Warn the user with that something bad happened in `setup'.
MESSAGE should be formatted (optionally) with ARGS"
  (display-warning 'setup (format message args)))

(defun +setup-wrap-to-demote-errors (body name)
  "Wrap BODY in a `with-demoted-errors' block.
This behavior is prevented if `setup-attributes' contains the
symbol `without-error-demotion'.

This function differs from `setup-wrap-to-demote-errors' in that
it includes the NAME of the setup form in the warning output."
  (if (memq 'without-error-demotion setup-attributes)
      body
    `(with-demoted-errors ,(format "Error in setup form on line %d (%s): %%S"
                                   (line-number-at-pos)
                                   name)
       ,body)))

(add-to-list 'setup-modifier-list '+setup-wrap-to-demote-errors)
(unless (memq debug-on-error '(nil init))
  (define-advice setup (:around (fn head &rest args) +setup-report)
    (+with-progress ((format "[Setup] %S..." head))
      (apply fn head args))))

;; Integration with `straight.el'
(defun setup--straight-handle-arg (arg var)
  (cond
   ((and (boundp var) (symbol-value var)) t)
   ((keywordp arg) (set var t))
   ((functionp arg) (set var nil) (funcall arg))
   ((listp arg) (set var nil) arg)))

(with-eval-after-load 'straight
  (setup-define :pkg
    (lambda (recipe &rest predicates)
      (let* ((skp (make-symbol "straight-keyword-p"))
             (straight-use-p (cl-mapcar
                              (lambda (f) (setup--straight-handle-arg f skp)) predicates))
             (form `(unless (and ,@straight-use-p
                                 (condition-case e (straight-use-package ',recipe)
                                   (error (+setup-warn ":straight error: %S" ',recipe)
                                          ,(setup-quit))
                                   (:success t)))
                      ,(setup-quit))))
        ;; Keyword arguments --- :quit is special and should short-circuit
        (if (memq :quit predicates)
            (setq form `,(setup-quit))
          ;; Otherwise, handle the rest of them ...
          (when-let ((after (cadr (memq :after predicates))))
            (setq form `(with-eval-after-load ,(if (eq after t) (setup-get 'feature) after)
                          ,form))))
        ;; Finally ...
        form))
    :documentation "Install RECIPE with `straight-use-package'.
If PREDICATES are given, only install RECIPE if all of them return non-nil.
The following keyword arguments are also recognized:
- :quit          --- immediately stop evaluating.  Good for commenting.
- :after FEATURE --- only install RECIPE after FEATURE is loaded.
                     If FEATURE is t, install RECIPE after the current feature."
    :repeatable nil
    :indent 1
    :shorthand (lambda (sexp)
                 (let ((recipe (cadr sexp)))
                   (or (car-safe recipe) recipe)))))

(provide 'init-setup)
;;; init-setup.el ends here

Performances enhancement

GCMH allows the auto-regulation of garbage collector based on idle timers. During normal use a high GC threshold is set; when idling GC is triggered and a low threshold is set.

Right now I’m good with 16MB for high threshold.

Other tweaks in this section have been stolen from DOOM and other configurations around.


(require 'init-performance)
;;; init-performance.el --- Performances enhancement -*- lexical-binding: t -*-

;;; Commentary:

;; This file should contain tweaks to obtain better overall performances.

;;; Code:


(setup (:pkg gcmh)
  (:require)
  (:blackout)
  ;; The GC introduces annoying pauses and stuttering into our Emacs experience,
  ;; so we use `gcmh' to stave off the GC while we're using Emacs, and provoke it
  ;; when it's idle. However, if the idle delay is too long, we run the risk of
  ;; runaway memory usage in busy sessions. If it's too low, then we may as well
  ;; not be using gcmh at all.
  (:option gcmh-idle-delay 'auto ; Default 15 seconds
           gcmh-auto-idle-delay-factor 10
           gcmh-high-cons-threshold (* 16 1024 1024))
  (gcmh-mode 1))

;; Aaand, here other code stolen from DOOM.
;; Performances are really better with this snippet (for me).
(setup tweaks
  ;; Reduce *Message* noise at startup. An empty scratch buffer (or the dashboard)
  ;; is more than enough.
  (setq inhibit-startup-screen t
        inhibit-startup-echo-area-message user-login-name
        inhibit-default-init t
        ;; Shave seconds off startup time by starting the scratch buffer in
        ;; `fundamental-mode', rather than, say, `org-mode' or `text-mode', which
        ;; pull in a ton of packages.
        initial-major-mode 'fundamental-mode
        initial-scratch-message nil)

  ;; Emacs "updates" its ui more often than it needs to, so slow it down slightly
  (setq idle-update-delay 1.0)
  ;; Resizing the Emacs frame can be a terribly expensive part of changing the
  ;; font. By inhibiting this, we halve startup times, particularly when we use
  ;; fonts that are larger than the system default (which would resize the frame).
  (setq frame-inhibit-implied-resize t)

  ;; PGTK builds only: this timeout adds latency to frame operations, like
  ;; `make-frame-invisible', which are frequently called without a guard because
  ;; it's inexpensive in non-PGTK builds. Lowering the timeout from the default
  ;; 0.1 should make childframes and packages that manipulate them (like `lsp-ui',
  ;; `company-box', and `posframe') feel much snappier. See emacs-lsp/lsp-ui#613.
  (setq pgtk-wait-for-event-timeout 0.001)

  ;; Introduced in Emacs HEAD (b2f8c9f), this inhibits fontification while
  ;; receiving input, which should help a little with scrolling performance.
  (setq redisplay-skip-fontification-on-input t))

(provide 'init-performance)
;;; init-appearance.el ends here

Pick me up mom, I’m scared!

Sometimes we forget shortcuts as we type them, which-key is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command in a popup.


(require 'init-help)
;;; init-help.el --- Sometimes we need help from someone/something :) -*- lexical-binding: t -*-

;;; Commentary:

;; The minibuffer is our best friend, let's use it more with extensions.

;;; Code:

(setup (:pkg which-key)
  (:blackout)
  (:option which-key-idle-delay 0.2)
  (which-key-mode 1))

(setup (:pkg helpful :quit)
  (:bind "C-h f"    helpful-callable
         "C-h v"    helpful-variable
         "C-h k"    helpful-key
         "C-h C"    helpful-command
         "C-c C-d"  helpful-at-point))

(provide 'init-help)
;;; init-help.el ends here

Appearance

In this section are contained line-numbers settings, modeline related configuration, minor tweaks for icons (needed also for dashboard) and colors.

Font

Readability is important, another package from Protesilaos, much more! Currently using Victor Mono as font, I love it, also for variable-pitch face.


(require 'init-fonts)
;;; init-fonts.el --- Appearance settings -*- lexical-binding: t -*-

;;; Commentary:

;; Only font configuration, nothing to say.

;;; Code:

(defgroup archer-faces()
  "Extensions for faces."
  :group 'faces)

(defcustom archer-font-height 120
  "Variable that specifies the font height."
  :type 'integer
  :group 'archer-faces)

(setup (:pkg fontaine)
  (:option x-underline-at-descent-line nil
           use-default-font-for-symbols t)

  (unless (version< emacs-version "28")
    (setq-default text-scale-remap-header-line t))

  (:option archer-font-height (pcase (system-name)
                                ("quietfrost" 180)
                                ("mate" 140)))

  (:option fontaine-latest-state-file (locate-user-emacs-file "var/fontaine-state.eld"))

  (:option fontaine-presets
           `((victor
              :default-family "VictorMono Nerd Font"
              :default-height ,archer-font-height)))

  (fontaine-set-preset (or (fontaine-restore-latest-preset) 'victor))

  (:with-hook kill-emacs-hook
    (:hook fontaine-store-latest-preset))

  (:with-hook (modus-themes-after-load-theme-hook ef-themes-post-load-hook)
    (:hook fontaine-apply-current-preset)))

(provide 'init-fonts)
;;; init-fonts.el ends here

Colors and general UI

I’m currently using Modus Themes, with Circadian to set light/dark version, based on time. It’s possible to switch themes on sunrise and sunset. Protesilaos made a great work, and these themes are, indeed, built into Emacs (but I always get the packaged version :D)


(require 'init-themes)
;;; init-themes.el --- Themes -*- lexical-binding: t -*-

;;; Commentary:

;; Configuration of `modus-themes' and `ef-themes', high accessibility themes by Protesilaos.

;;; Code:


(setup (:pkg modus-themes)
  ;; Preferences
  (:option modus-themes-org-blocks 'gray-background
           modus-themes-mixed-fonts nil
           modus-themes-variable-pitch-ui nil)

  ;; Overrides
  (:option modus-themes-common-palette-overrides
           ;; Modeline
           '((bg-mode-line-active bg-blue-subtle)
             (fg-mode-line-active fg-main)
             (border-mode-line-active blue-intense)
             ;; Region
             (bg-region bg-lavender)
             (fg-region unspecified)
             ;; Mouse Hovers
             (bg-hover bg-yellow-intense)
             ;; Fringe
             (fringe unspecified)
             ;; Inline code in prose (markup)
             (prose-block fg-dim)
             (prose-code green-cooler)
             (prose-done green)
             (prose-macro magenta-cooler)
             (prose-metadata fg-dim)
             (prose-metadata-value fg-alt)
             (prose-table fg-alt)
             (prose-tag magenta-faint)
             (prose-todo red)
             (prose-verbatim magenta-warmer)
             ;; Syntax
             (comment yellow-faint)
             (string green-warmer)
             ;; Checkers
             (underline-err red-faint)
             (underline-warning yellow-faint)
             (underline-note cyan-faint)
             ;; Links - No underlines
             (underline-link unspecified)
             (underline-link-visited unspecified)
             (underline-link-symbolic unspecified)
             ;; Box buttons
             (bg-button-active bg-main)
             (fg-button-active fg-main)
             (bg-button-inactive bg-inactive)
             (fg-button-inactive "gray50")
             ;; Prompts
             (fg-prompt cyan)
             (bg-prompt bg-cyan-nuanced)
             ;; Completion
             (fg-completion-match-0 fg-main)
             (fg-completion-match-1 fg-main)
             (fg-completion-match-2 fg-main)
             (fg-completion-match-3 fg-main)
             (bg-completion-match-0 bg-blue-subtle)
             (bg-completion-match-1 bg-yellow-subtle)
             (bg-completion-match-2 bg-cyan-subtle)
             (bg-completion-match-3 bg-red-subtle)
             ;; Mail citations
             (mail-cite-0 blue)
             (mail-cite-1 yellow)
             (mail-cite-2 green)
             (mail-cite-3 magenta)
             (mail-part magenta-cooler)
             (mail-recipient cyan)
             (mail-subject red-warmer)
             (mail-other cyan-cooler)
             ;; Line numbers
             (fg-line-number-inactive "gray50")
             (fg-line-number-active fg-main)
             (bg-line-number-inactive unspecified)
             (bg-line-number-active unspecified)))

  (modus-themes-select 'modus-operandi))

(setup (:pkg ef-themes))

;; I set circadian in the configuration of my themes
(setup (:pkg circadian)
  (:load-after modus-themes)
  (:option circadian-themes '(("8:00" . modus-operandi)
                              ("20:00" . modus-vivendi)))
  (circadian-setup))

(provide 'init-themes)
;;; init-themes.el ends here

Minor UI settings

Nothing special, just all-the-icons and misc settings.


(require 'init-appearance)
;;; init-appearance.el --- Appearance settings -*- lexical-binding: t -*-

;;; Commentary:

;; This file should contain appearance settings stuff.

;;; Code:

(setup appearance
  ;; A simple frame title
  (setq frame-title-format '("%b – Emacs")
        icon-title-format frame-title-format)

  ;; Stuff
  (setq calendar-date-style 'european)
  (setq display-time-default-load-average nil)
  (setq highlight-nonselected-windows nil)
  (setq echo-keystrokes 0.1)

  ;; Other graphical stuff
  (setq visible-bell nil)
  (setq x-gtk-use-system-tooltips t)
  (setq x-stretch-cursor nil)

  ;; Dialogs
  (setq use-dialog-box nil      ; Mouse events dialog
        use-file-dialog nil)    ; Disable dialog for files

  ;; Cursor
  (setq-default cursor-in-non-selected-windows nil)
  (setq-default cursor-type 'bar)
  (blink-cursor-mode 0)

  ;; Bidirectional settings
  (setq-default bidi-display-reordering 'left-to-right)
  (setq-default bidi-paragraph-direction 'left-to-right)

  ;; Lines related
  (setq-default truncate-lines nil)
  (setq-default visual-line-mode t)

  (setq-default indicate-buffer-boundaries nil))

;; You must run `all-the-icons-install-fonts` the first time.
(setup (:pkg all-the-icons)
  (:require all-the-icons))

(provide 'init-appearance)
;;; init-appearance.el ends here

Modeline

Just modeline customized.


(require 'init-modeline)
;;; init-modeline.el --- Modeline customization -*- lexical-binding: t -*-

;;; Commentary:

;; Modeline customization and other useless/cute packages.

;;; Code:
(setup modeline
  (unless (version< emacs-version "28")
    (setq mode-line-compact nil)
    (setq mode-line-position-column-line-format '("[L%l:C%C]")))

  (setq mode-line-percent-position '(-3 "%P"))
  (setq mode-line-defining-kbd-macro
        (propertize " Macro" 'face 'mode-line-emphasis))

  (setq-default mode-line-format
                '("%e"
                  mode-line-front-space
                  mode-line-client
                  "  "
                  mode-line-mule-info
                  "  "
                  mode-line-modified
                  mode-line-remote
                  mode-line-frame-identification
                  mode-line-buffer-identification
                  "  "
                  mode-line-position
                  (vc-mode vc-mode)
                  "  "
                  mode-line-modes
                  mode-line-misc-info
                  mode-line-end-spaces)))


;; <https://github.com/minad/recursion-indicator>.
(setup (:pkg recursion-indicator)
  (:option recursion-indicator-general (concat "general" (all-the-icons-material "cached" :v-adjust -0.1))
           recursion-indicator-minibuffer (concat "minibuffer " (all-the-icons-material "cached" :v-adjust -0.1)))

  (setq-default mode-line-modes
                (seq-filter (lambda (s)
                              (not (and (stringp s)
                                        (string-match-p
                                         "^\\(%\\[\\|%\\]\\)$" s))))
                            mode-line-modes))

  (recursion-indicator-mode 1))

;;; Keycast mode
(setup (:pkg keycast)
  ;; For `keycast-mode'
  (:option keycast-mode-line-window-predicate #'keycast-active-frame-bottom-right-p
           keycast-separator-width 1
           keycast-mode-line-remove-tail-elements nil
           keycast-mode-line-format "%3s%k%c%r"
           keycast-mode-line-insert-after 'mode-line-misc-info)

  ;; For `keycast-log-mode'
  (:option keycast-log-format "%-20K%C\n"
           keycast-log-newest-first t
           keycast-log-frame-alist '((minibuffer . nil)))

  ;; Based on Prot's configuration
  (:when-loaded
    (dolist (input '(self-insert-command
                     org-self-insert-command))
      (add-to-list 'keycast-substitute-alist `(,input "." "Typing…")))

    (dolist (event '(mouse-event-p
                     mouse-movement-p
                     mwheel-scroll))
      (add-to-list 'keycast-substitute-alist `(,event nil)))))

(provide 'init-modeline)
;;; init-modeline.el ends here

Dashboard Configuration

Useless and cute dashboard, nothing to say, and there are minor tweaks to make it work with server-mode and Emacs PGTK/NativeComp.


(require 'init-dash)

Here the init-dash.el file.

;;; init-dash.el --- Dashboard configuration -*- lexical-binding: t -*-

;;; Commentary:

;; Configuration of my dashboard, loaded at startup.

;;; Code:

(setup (:pkg dashboard)
  (:option dashboard-banner-logo-title "SUCK(EMAC)S - Personal Workspace"
           dashboard-startup-banner (expand-file-name "img/stallman.png" user-emacs-directory)
           dashboard-center-content t
           ;; Icons
           dashboard-set-heading-icons t
           dashboard-set-file-icons t
           dashboard-items '((recents . 5)
                             (bookmarks . 5))

           ;; Headings
           dashboard-heading-icons '((recents   . "history")
                                     (bookmarks . "bookmark")
                                     (agenda    . "calendar")
                                     (projects  . "briefcase")
                                     (registers . "database"))

           ;; Navigator under banner
           dashboard-set-navigator t
           dashboard-navigator-buttons
           `(((,(all-the-icons-faicon "archive" :height 1.1 :v-adjust 0.0)
               "Update Packages"
               "Click to updates your packages"
               (lambda (&rest _) (straight-pull-all)))))

           ;; Footer
           dashboard-footer-icon (all-the-icons-fileicon "emacs" :face 'font-lock-keyword-face))

  ;; This is required with PGTK!
  (setq initial-buffer-choice (lambda () (get-buffer-create "*dashboard*")))
  (dashboard-setup-startup-hook)

  (:with-hook after-init-hook
    (:hook dashboard-insert-startupify-lists))

  (:with-hook server-after-make-frame-hook
    (:hook dashboard-refresh-buffer)))

(provide 'init-dash)
;;; init-dash.el ends here

Interface interaction

This section contains my file and buffer related configurations. Nothing special.

Editing enhancement

Tweaks present here:

  • Scroll (and smooth scroll for Emacs >= 29) and horizontal scroll with mouse;
  • Truncate lines hook for prog-mode;
  • Electric-pair mode and show-paren;
  • Autorevert files after changes;
  • Undo Tree mode for simpler undo-redo (and visual branches!).
  • Rainbow-mode;
  • Delete-selection mode to overwrite selected regions;
  • Drag-stuff to…drag stuff around;
  • etc.

(require 'init-editing)
;;; init-editing.el --- Basic editing configuration -*- lexical-binding: t -*-

;;; Commentary:

;; This file is pretty simple, it only contains editing related utilities and preferences.
;; It's still experimental and very poor, so I only consider it a starting point.

;;; Code:

;;
;;; General

;; Force UTF-8
(setup encoding
  (setq coding-system-for-read 'utf-8-unix)
  (setq coding-system-for-write 'utf-8-unix)
  (setq default-process-coding-system '(utf-8-unix utf-8-unix))
  (setq locale-coding-system 'utf-8-unix)
  (setq selection-coding-system 'utf-8)
  (setq x-select-request-type nil)
  (setq-default buffer-file-coding-system 'utf-8-unix)
  (prefer-coding-system 'utf-8-unix)
  (set-clipboard-coding-system 'utf-8)
  (set-default-coding-systems 'utf-8-unix)
  (set-keyboard-coding-system 'utf-8-unix)
  (set-language-environment "UTF-8")
  (set-selection-coding-system 'utf-8)
  (set-terminal-coding-system 'utf-8-unix))

;;
;;; Keep history and keep the order

;; The `no-littering` package to keep folders where we edit files and the Emacs configuration folder clean.
(setup (:pkg no-littering)
  ;; The package doesn't set this by default so we must place
  ;; auto save files in the same path as it uses for sessions
  (:option auto-save-file-name-transforms `((".*" ,(no-littering-expand-var-file-name "auto-save/")))))

(setup saveplace
  (:option save-place-file (expand-file-name "var/saveplace" user-emacs-directory))
  (setq save-place-forget-unreadable-files t)
  (save-place-mode 1))

(setup backup
  (:option backup-directory-alist `(("." . ,(expand-file-name "var/backup" user-emacs-directory))))
  (setq backup-by-copying t)
  (setq version-control t)
  (setq delete-old-versions t)
  (setq kept-new-versions 5)
  (setq kept-old-versions 2)
  (setq create-lockfiles nil))

;;
;;; Lines related

(setup display-line-numbers
  ;; Defaults
  (setq-default display-line-numbers-widen t)
  (setq-default display-line-numbers-width 3)

  ;; Preferences
  (:option display-line-numbers-type 'relative
           display-line-numbers-width-start nil
           display-line-numbers-grow-only t)

  ;; Hooks
  (:with-hook (prog-mode-hook text-mode-hook conf-mode-hook)
    (:hook (lambda () (display-line-numbers-mode 1))))
  (:with-hook (org-mode-hook)
    (:hook (lambda () (display-line-numbers-mode 0)))))

(setup hl-line
  (:with-mode (prog-mode dired-mode)
    (:hook hl-line-mode)))

;;
;;; Scrolling

(setup scrolling
  ;; Enable smooth scroll on Emacs 29
  (unless (version< emacs-version "29")
    (pixel-scroll-precision-mode 1))

  ;; Vertical scroll
  (setq scroll-step 1
        scroll-margin 10
        ;; Reduce cursor lag by a tiny bit by not auto-adjusting `window-vscroll'
        ;; for tall lines.
        auto-window-vscroll nil)

  ;; Horizontal scroll
  (setq hscroll-margin 16
        hscroll-step 1
        auto-hscroll-mode t)

  ;; General tweaks

  ;; More performant rapid scrolling over unfontified regions. May cause brief
  ;; spells of inaccurate syntax highlighting right after scrolling, which should
  ;; quickly self-correct.
  (setq fast-but-imprecise-scrolling t)

  ;; 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 re-centered.
  (setq scroll-conservatively 101
        scroll-preserve-screen-position t
        scroll-preserve-screen-position t))

(setup mouse
  ;; Movement related
  (setq focus-follows-mouse t)
  (setq make-pointer-invisible t)
  (setq mouse-autoselect-window t)

  ;; Scroll
  (setq mouse-wheel-scroll-amount '(3 ((shift) . hscroll))
        mouse-wheel-scroll-amount-horizontal 2)

  ;; Behavior
  (setq mouse-wheel-follow-mouse t)
  (setq mouse-wheel-progressive-speed nil)
  (setq mouse-1-click-follows-link t)
  (setq mouse-yank-at-point t)

  (:global "<mouse-2>" clipboard-yank))

(setup elec-pair
  (electric-pair-mode 1))

(setup paren
  (:option show-paren-style 'parenthesis
           show-paren-when-point-in-periphery t
           show-paren-when-point-inside-paren nil)
  (show-paren-mode 1))

(setup selection
  (setq save-interprogram-paste-before-kill t)
  (setq kill-do-not-save-duplicates t)
  (setq select-enable-clipboard t)
  (setq select-enable-primary nil))

(setup (:require delsel)
  (:blackout delete-selection)
  (:with-hook after-init-hook
    (:hook delete-selection-mode)))

(setup (:pkg drag-stuff)
  (:blackout)
  (drag-stuff-global-mode 1)
  (drag-stuff-define-keys))

(setup (:pkg goto-last-change)
  (:global "C-z" goto-last-change))

(setup (:require autorevert)
  (:blackout auto-revert)
  (setq auto-revert-verbose t)
  (setq global-auto-revert-non-file-buffers t)
  (:with-hook after-init-hook
    (:hook global-auto-revert-mode)))

(setup (:require so-long)
  (global-so-long-mode 1))

(setup (:pkg diff-hl)
  (:hook-into prog-mode)

  (:with-mode dired-mode
    (:hook diff-hl-dired-mode))

  (:with-after magit
    (:with-hook magit-pre-refresh-hook
      (:hook diff-hl-magit-pre-refresh))
    (:with-hook magit-post-refresh-hook
      (:hook diff-hl-magit-post-refresh))))

(setup long-lines
  (set-display-table-slot standard-display-table 'truncation (make-glyph-code ?…))
  (set-display-table-slot standard-display-table 'wrap (make-glyph-code ?↩)))

(provide 'init-editing)
;;; init-editing.el ends here
  • Meow

    Meow is yet another modal editing mode for Emacs. Meow aims to blend modal editing into Emacs with minimal interference with its original key-bindings, avoiding most of the hassle introduced by key-binding conflicts.

    Keybindings are listed in init-meow.el.

    
    (require 'init-meow)
    
    ;;; init-meow.el --- Meow modal editing -*- lexical-binding: t -*-
    
    ;;; Commentary:
    
    ;; A kind of modal editing, alternative to evil and xah-fly-keys.
    
    ;;; Code:
    
    (setup (:pkg meow)
      (:require meow)
      (:blackout meow-mode)
    
      (defun meow-setup ()
        (setq meow-cheatsheet-layout meow-cheatsheet-layout-qwerty)
        (meow-motion-overwrite-define-key
         '("j" . meow-next)
         '("k" . meow-prev)
         '("<escape>" . ignore))
        (meow-leader-define-key
         ;; SPC j/k will run the original command in MOTION state.
         '("j" . "H-j")
         '("k" . "H-k")
         ;; Use SPC (0-9) for digit arguments.
         '("1" . meow-digit-argument)
         '("2" . meow-digit-argument)
         '("3" . meow-digit-argument)
         '("4" . meow-digit-argument)
         '("5" . meow-digit-argument)
         '("6" . meow-digit-argument)
         '("7" . meow-digit-argument)
         '("8" . meow-digit-argument)
         '("9" . meow-digit-argument)
         '("0" . meow-digit-argument)
         '("/" . meow-keypad-describe-key)
         '("?" . meow-cheatsheet))
        (meow-normal-define-key
         '("0" . meow-expand-0)
         '("9" . meow-expand-9)
         '("8" . meow-expand-8)
         '("7" . meow-expand-7)
         '("6" . meow-expand-6)
         '("5" . meow-expand-5)
         '("4" . meow-expand-4)
         '("3" . meow-expand-3)
         '("2" . meow-expand-2)
         '("1" . meow-expand-1)
         '("-" . negative-argument)
         '(";" . meow-reverse)
         '("," . meow-inner-of-thing)
         '("." . meow-bounds-of-thing)
         '("[" . meow-beginning-of-thing)
         '("]" . meow-end-of-thing)
         '("a" . meow-append)
         '("A" . meow-open-below)
         '("b" . meow-back-word)
         '("B" . meow-back-symbol)
         '("c" . meow-change)
         '("d" . meow-delete)
         '("D" . meow-backward-delete)
         '("e" . meow-line)
         '("E" . meow-join)
         '("f" . meow-find)
         '("g" . meow-cancel-selection)
         '("G" . meow-grab)
         '("h" . meow-left)
         '("H" . meow-left-expand)
         '("i" . meow-insert)
         '("I" . meow-open-above)
         '("j" . meow-next)
         '("J" . meow-next-expand)
         '("k" . meow-prev)
         '("K" . meow-prev-expand)
         '("l" . meow-right)
         '("L" . meow-right-expand)
         '("m" . meow-mark-word)
         '("M" . meow-mark-symbol)
         '("n" . meow-search)
         '("o" . meow-block)
         '("O" . meow-to-block)
         '("p" . meow-yank)
         '("P" . meow-yank-pop)
         '("q" . meow-quit)
         '("Q" . meow-goto-line)
         '("r" . meow-replace)
         '("R" . meow-swap-grab)
         '("s" . meow-kill)
         '("S" . meow-kill-whole-line)
         '("t" . meow-till)
         '("u" . meow-undo)
         '("U" . meow-undo-in-selection)
         '("v" . meow-visit)
         '("w" . meow-next-word)
         '("W" . meow-next-symbol)
         '("x" . meow-line)
         '("X" . meow-goto-line)
         '("y" . meow-save)
         '("Y" . meow-sync-grab)
         '("z" . meow-pop-selection)
         '("'" . repeat)
         '("<escape>" . ignore)))
    
      (:when-loaded
        (meow-setup)
        (meow-setup-indicator)
        (meow-global-mode 1)))
    
    (provide 'init-meow)
    ;;; init-meow.el ends here
    

Windows navigation

Moving around windows can be painful, but some built-in functions save our a*s.


(require 'init-windows)
;;; init-windows.el --- Windows navigation configuration -*- lexical-binding: t -*-

;;; Commentary:

;; Only movement between buffers/frames, nothing special.

;;; Code:

(setup windmove
  ;; Windmove with shift+arrows
  (windmove-default-keybindings)
  (add-hook 'org-shiftup-final-hook    #'windmove-up)
  (add-hook 'org-shiftdown-final-hook  #'windmove-down)
  (add-hook 'org-shiftleft-final-hook  #'windmove-left)
  (add-hook 'org-shiftright-final-hook #'windmove-right))

(setup window
  (setq window-resize-pixelwise nil)

  ;; Splitting around
  (setq split-width-threshold 160
        split-height-threshold nil)

  ;; Dividers
  (setq window-divider-default-right-width 8)
  (setq window-divider-default-places 'right-only)
  (window-divider-mode 0)

  (:global "C-x <up>"   enlarge-window
           "C-x <down>" shrink-window
           "C-x {"      shrink-window-horizontally
           "C-x }"      enlarge-window-horizontally))

(setup (:pkg beframe)
  (:option beframe-functions-in-frames '(project-prompt-project-dir)
           beframe-global-buffers '("*scratch*"
                                    "*Messages"
                                    "*Async-native-compile-log*"
                                    "*straight-byte-compilation*"
                                    "*straight-process*"
                                    "*dashboard*"))

  (:with-after consult
    (defface beframe-buffer
      '((t :inherit font-lock-string-face))
      "Face for `consult' framed buffers.")

    (defvar beframe--consult-source
      `( :name     "Frame-specific buffers (current frame)"
         :narrow   ?F
         :category buffer
         :face     beframe-buffer
         :history  beframe-history
         :items    ,#'beframe-buffer-names
         :action   ,#'switch-to-buffer
         :state    ,#'consult--buffer-state))

    (add-to-list 'consult-buffer-sources 'beframe--consult-source))

  (beframe-mode 1))

(setup (:pkg ace-window)
  (:global "M-o" ace-window
           "M-O" ace-swap-window)
  (setq aw-scope 'frame
        aw-dispatch-always t
        aw-minibuffer-flag t)
  (ace-window-display-mode 1))

(setup (:pkg avy)
  (:global "M-g j" avy-goto-char-timer)
  (setq avy-all-windows nil   ;; only current
        avy-all-windows-alt t ;; all windows with C-u
        avy-single-candidate-jump t
        avy-case-fold-search nil
        avy-timeout-seconds 0.5
        avy-style 'pre))

(provide 'init-windows)
;;; init-windows.el ends here

Buffer management

Sometimes buffers are too much, and I think that the classic buffer-menu is meh. With ibuffer I can group buffers in Gnus style, customize actions remembering Dired, and so on.


(require 'init-buffers)
;;; init-buffers.el --- Buffer navigation -*- lexical-binding: t -*-

;;; Commentary:

;; Buffer navigation and management

;;; Code:

(defun archer-human-readable-file-sizes-to-bytes (string)
  "Convert a human-readable file (as STRING) size into BYTES."
  (interactive)
  (cond
   ((string-suffix-p "G" string t)
    (* 1000000000 (string-to-number (substring string 0 (- (length string) 1)))))
   ((string-suffix-p "M" string t)
    (* 1000000 (string-to-number (substring string 0 (- (length string) 1)))))
   ((string-suffix-p "K" string t)
    (* 1000 (string-to-number (substring string 0 (- (length string) 1)))))
   (t
    (string-to-number (substring string 0 (- (length string) 1))))))

(defun archer-bytes-to-human-readable-file-sizes (bytes)
  "Convert number of BYTES to human-readable file size."
  (interactive)
  (cond
   ((> bytes 1000000000) (format "%10.1fG" (/ bytes 1000000000.0)))
   ((> bytes 100000000) (format "%10.0fM" (/ bytes 1000000.0)))
   ((> bytes 1000000) (format "%10.1fM" (/ bytes 1000000.0)))
   ((> bytes 100000) (format "%10.0fk" (/ bytes 1000.0)))
   ((> bytes 1000) (format "%10.1fk" (/ bytes 1000.0)))
   (t (format "%10d" bytes))))

(setup (:require ibuffer)
  ;; Use human readable Size column instead of original one
  (define-ibuffer-column size-h
    (:name "Size" :inline t :summarizer
           (lambda (column-strings)
             (let ((total 0))
               (dolist (string column-strings)
                 (setq total (+ (float (archer-human-readable-file-sizes-to-bytes string))
                                total)))
               (archer-bytes-to-human-readable-file-sizes total))))
    (archer-bytes-to-human-readable-file-sizes (buffer-size)))
  ;; Modify the default ibuffer-formats
  (:option ibuffer-formats
           '((mark modified read-only locked " "
                   (name 20 20 :left :elide)
                   " "
                   (size-h 11 -1 :right)
                   " "
                   (mode 16 16 :left :elide)
                   " "
                   filename-and-process)
             (mark " "
                   (name 16 -1)
                   " " filename)))
  ;; Add groups
  (:option ibuffer-saved-filter-groups
           '(("default"
              ("dired" (mode . dired-mode))
              ("git"   (or (mode . magit-mode)
                           (mode . magit-process-mode)
                           (mode . magit-diff-mode)
                           (mode . magit-status-mode)))
              ("elisp" (mode . emacs-lisp-mode))
              ("c"     (mode . c-mode))
              ("c++" (mode . c++-mode))
              ("nix" (mode . nix-mode))
              ("rust" (mode . rustic-mode))
              ("java" (mode . java-mode))
              ("telegram"  (or (mode . telega-root-mode)
                               (mode . telega-mode)
                               (mode . telega-chat-mode)))
              ("documents" (or (name . "\\.pdf")
                               (name . "\\.org")))
              ("mails" (or (mode . notmuch-show-mode)
                           (mode . notmuch-tree-mode)
                           (mode . notmuch-search-mode)
                           (mode . notmuch-message-mode)))
              ("emacs" (or
                        (name . "^\\*scratch\\*$")
                        (name . "^\\*Messages\\*$")
                        (name . "^\\*Warnings\\*$")
                        (name . "^\\*straight-process\\*$")
                        (name . "^\\*dashboard\\*$"))))))

  (:option ibuffer-expert t
           ibuffer-display-summary t
           ibuffer-show-empty-filter-groups nil
           ibuffer-use-other-window nil
           ibuffer-movement-cycle t
           ibuffer-default-sorting-mode 'filename/process
           ibuffer-use-header-line t
           ibuffer-default-shrink-to-minimum-size nil)

  (:hook (lambda () (ibuffer-switch-to-saved-filter-groups "default")
           (ibuffer-auto-mode 1)))

  (:global "C-x C-b" ibuffer))

;;; Unique names for buffers
(setup (:require uniquify)
  (:option uniquify-buffer-name-style 'forward
           uniquify-strip-common-suffix t
           uniquify-after-kill-buffer-p t))

(setup desktop
  (setq desktop-auto-save-timeout 300
           desktop-path `(,user-emacs-directory)
           desktop-base-file-name "desktop"
           desktop-files-not-to-save nil
           desktop-buffers-not-to-save nil
           desktop-globals-to-clear nil
           desktop-load-locked-desktop t
           desktop-missing-file-warning nil
           desktop-restore-eager 0
           desktop-restore-frames nil
           desktop-save 'ask-if-new)

  (:when-loaded
    (dolist (symbol '(kill-ring file-name-history))
      (add-to-list 'desktop-globals-to-save symbol)))

  (desktop-save-mode 1))

(provide 'init-buffers)
;;; init-buffers.el ends here

Dired

Dired is a built-in file manager for Emacs that does some pretty amazing things. For example you can enable writable dired buffer to edit everything and just save to apply your changes.

I have disabled dired-find-alternate-file warning, I’m using it ‘cause pressing Return key just opens too many buffers.

There’s also a package named trashed, to visit system trash.


(require 'init-dired)
;;; init-dired.el --- Dired -*- lexical-binding: t -*-

;;; Commentary:

;; Dired utilities and configuration for a better experience.

;;; Code:
(setup dired
  ;; 'Kay, with this I'm good, maybe
  (defun archer-dired-open-file ()
    "In Dired, open the file named on this line through xdg-open."
    (interactive)
    (let* ((file (dired-get-filename nil t)))
      (call-process "xdg-open" nil 0 nil file)))

  ;; Kill the current Dired buffer, then visit the file or directory
  (put 'dired-find-alternate-file 'disabled nil)

  ;; Emacs 29 options
  (unless (version< emacs-version "29")
    (setopt dired-mouse-drag-files t
            dired-make-directory-clickable t
            dired-free-space nil))

  (:option dired-listing-switches "-agho --group-directories-first"
           dired-kill-when-opening-new-dired-buffer t
           dired-recursive-copies 'always
           dired-recursive-deletes 'always
           dired-auto-revert-buffer #'dired-directory-changed-p
           dired-dwim-target t
           dired-hide-details-hide-symlink-targets nil
           delete-by-moving-to-trash t)

  (:bind-into dired-jump-map
    "j" dired-jump)

  (:bind-into dired-mode-map
    "C-c o" archer-dired-open-file))

(setup (:require dired-x)
  (:option dired-clean-confirm-killing-deleted-buffers t
           dired-clean-up-buffers-too t
           dired-x-hands-off-my-keys t
           dired-omit-files "^\\.$\\|^\\.[^.]")

  (:bind-into dired-mode-map
    "C-c d" dired-omit-mode)

  (:bind-into dired-mode-map
    "I" #'dired-info)

  (:with-mode dired-mode
    (:hook dired-omit-mode)))

(setup (:require dired-aux)
  (:option dired-create-destination-dirs 'always
           dired-do-revert-buffer t
           dired-isearch-filenames 'dwim
           dired-vc-rename-file t))

(setup (:require wdired)
  (:option wdired-allow-to-change-permissions t
           wdired-create-parent-directories t))

(setup (:require image-dired)
  (:option image-dired-external-viewer "xdg-open"
           image-dired-thumb-size 80
           image-dired-thumb-margin 2
           image-dired-thumb-relief 0
           image-dired-thumbs-per-row 4)

  (:bind-into image-dired-thumbnail-mode-map
    "<return>" #'image-dired-thumbnail-display-external))

(setup (:pkg diredfl)
  (:quit)
  (diredfl-global-mode 1))

(setup (:pkg dired-subtree)
  (:option dired-subtree-use-backgrounds nil)
  (:bind-into dired-mode-map
    "<tab>" dired-subtree-toggle
    "<backtab>" dired-subtree-remove))

(setup (:pkg dired-sidebar)
  (:autoload dired-sidebar-toggle-sidebar)
  (:global "C-x C-n" dired-sidebar-toggle-sidebar))

(setup (:pkg dired-collapse)
  (:load-after dired
    (:hook-into dired-mode-hook)))

(setup (:pkg all-the-icons-dired)
  (:option all-the-icons-dired-monochrome nil)
  (:load-after (all-the-icons dired)
    (:hook-into dired-mode-hook)))

(setup (:pkg trashed)
  (:option trashed-action-confirmer 'y-or-n-p
           trashed-use-header-line t
           trashed-sort-key '("Date deleted" . t)))

(provide 'init-dired)
;;; init-dired.el ends here

This is one of my favourite parts. I think that fast selection, completing and search are a must, always, everywhere.

Monster trio of completion

As Completion UI Vertico is my preferred choice, it’s lightweight and fast, and relies on Emacs internals. Marginalia for rich annotations provides a summary for candidates. Completion can be better with an Orderless (similar to FZF, if you know). Orderless is also customizable for matching style.


(require 'init-complete)
;;; init-complete.el --- Completion enhancement -*- lexical-binding: t -*-

;;; Commentary:

;; Emacs' internal completion is awesome, why should you use Ivy/Helm and similar?
;; They're wonderful, but complex and for me are unnecessary.
;; I'm using Vertico, Orderless and Marginalia (monster trio) for rich, orderless completion style.

;;; Code:

(setup minibuffer
  ;; Answers
  (fset #'yes-or-no-p #'y-or-n-p)
  (setq read-answer-short t)
  (setq use-short-answers t)

  ;; Files
  (setq file-name-shadow-properties '(invisible t intangible t))
  (file-name-shadow-mode 1)

  ;; Behavior
  (setq enable-recursive-minibuffers t)
  (minibuffer-depth-indicate-mode 1)
  (minibuffer-electric-default-mode 1)

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

  (:with-hook minibuffer-setup-hook
    (:hook cursor-intangible-mode)))

;; Persist history over Emacs restarts. Vertico sorts by history position.
(setup (:require savehist)
  (setq savehist-file (locate-user-emacs-file "var/savehist"))
  (setq history-length 10000)
  (setq history-delete-duplicates t)
  (setq savehist-save-minibuffer-history t)
  (:with-hook after-init-hook
    (:hook savehist-mode)))

;; Vertico
(setup (:pkg (vertico :files (:defaults "extensions/*")))

  (:also-load vertico-indexed
              vertico-flat
              vertico-grid
              vertico-mouse
              vertico-quick
              vertico-buffer
              vertico-repeat
              vertico-reverse
              vertico-directory
              vertico-multiform
              vertico-unobtrusive)

  (:option vertico-scroll-margin 0
           vertico-count 12
           vertico-resize t
           vertico-cycle t)

  (:bind-into vertico-map
    "<escape>" #'minibuffer-keyboard-quit)

  (advice-add #'vertico--format-candidate :around
              (lambda (orig cand prefix suffix index _start)
                (setq cand (funcall orig cand prefix suffix index _start))
                (concat
                 (if (= vertico--index index)
                     (propertize "λ " 'face 'vertico-current)
                   "  ")
                 cand)))

  (:option vertico-multiform-commands
           '((dired (vertico-sort-function . sort-directories-first))))

  (:option vertico-multiform-categories
           '((consult-grep buffer)
             (consult-ripgrep buffer)
             (consult-git-grep buffer)
             (consult-find buffer)
             (file (vertico-sort-function . sort-directories-first))))

  (:hooks rfn-eshadow-update-overlay-hook vertico-directory-tidy
          minibuffer-setup-hook  vertico-repeat-save)

  ;; Sort directories before files
  (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)))

  (vertico-mode 1)
  (vertico-multiform-mode 1))

;; Marginalia
(setup (:pkg marginalia)
  (:load-after vertico)
  (:bind-into minibuffer-local-map
    "M-A" marginalia-cycle)
  (marginalia-mode 1))

(setup (:pkg all-the-icons-completion)
  (:load-after (all-the-icons marginalia)
    (all-the-icons-completion-mode 1)
    (:with-mode marginalia-mode
      (:hook all-the-icons-completion-marginalia-setup))))

;; Orderless
(defun archer-orderless-literal-dispatcher (pattern _index _total)
  "Literal style dispatcher, using equal sign as a suffix."
  (cond
   ((equal "=" pattern)
    '(orderless-literal . "="))
   ((string-suffix-p "=" pattern)
    (cons 'orderless-literal (substring pattern 0 -1)))))

(defun archer-orderless-without-literal-dispatcher (pattern _index _total)
  "Literal without style dispatcher using the exclamation mark as a suffix."
  (cond
   ((equal "!" pattern)
    '(orderless-literal . "!"))
   ((string-suffix-p "!" pattern)
    (cons 'orderless-without-literal (substring pattern 0 -1)))))

(defun archer-orderless-initialism-dispatcher (pattern _index _total)
  "Leading initialism dispatcher using comma as suffix."
  (cond
   ((equal "," pattern)
    '(orderless-literal . ","))
   ((string-suffix-p "," pattern)
    (cons 'orderless-initialism (substring pattern 0 -1)))))

(defun archer-orderless-flex-dispatcher (pattern _index _total)
  "Flex dispatcher using the tilde suffix."
  (cond
   ((equal "~" pattern)
    '(orderless-literal . "~"))
   ((string-suffix-p "~" pattern)
    (cons 'orderless-flex (substring pattern 0 -1)))))

(setup (:pkg orderless)
  (setq completion-styles '(orderless basic)
        orderless-component-separator 'orderless-escapable-split-on-space
        completion-category-defaults nil)

  (setq orderless-style-dispatchers
        '(archer-orderless-literal-dispatcher
          archer-orderless-without-literal-dispatcher
          archer-orderless-initialism-dispatcher
          archer-orderless-flex-dispatcher))

  (setq completion-category-overrides
        '((file (styles . (partial-completion basic orderless)))
          (project-file (styles . (partial-completion basic orderless))))))

(provide 'init-complete)
;;; init-complete.el ends here

Embark

Embark provides contextual menu offering actions for a target determined in the context, exactly like a contextual menu.


(require 'init-embark)
;;; init-embark.el --- Embark, run a command based on point-*- lexical-binding: t -*-

;;; Commentary:

;; Sometimes you want to act near point, but there are many actions.
;; Embark ships many actions, dependant on target and modes.

;;; Code:

(defun archer-embark-which-key-indicator ()
  "An embark indicator that displays keymaps using which-key.
The which-key help message will show the type and value of the
current target followed by an ellipsis if there are further
targets."
  (lambda (&optional keymap targets prefix)
    (if (null keymap)
        (which-key--hide-popup-ignore-command)
      (which-key--show-keymap
       (if (eq (plist-get (car targets) :type) 'embark-become)
           "Become"
         (format "Act on %s '%s'%s"
                 (plist-get (car targets) :type)
                 (embark--truncate-target (plist-get (car targets) :target))
                 (if (cdr targets) "…" "")))
       (if prefix
           (pcase (lookup-key keymap prefix 'accept-default)
             ((and (pred keymapp) km) km)
             (_ (key-binding prefix 'accept-default)))
         keymap)
       nil nil t (lambda (binding)
                   (not (string-suffix-p "-argument" (cdr binding))))))))

(setq embark-indicators
      '(archer-embark-which-key-indicator
        embark-highlight-indicator
        embark-isearch-highlight-indicator))

(defun archer-embark-hide-which-key-indicator (fn &rest args)
  "Hide the which-key indicator immediately when using the completing-read prompter."
  (which-key--hide-popup-ignore-command)
  (let ((embark-indicators
         (remq #'archer-embark-which-key-indicator embark-indicators)))
    (apply fn args)))

;; Embark configuration
(setup (:pkg embark)
  (:load-after consult
    (:pkg embark-consult))

  (:load-after which-key
    (setq prefix-help-command #'embark-prefix-help-command)
    (advice-add #'embark-completing-read-prompter :around #'archer-embark-hide-which-key-indicator))

  (:global "C-." embark-act
           "C-;" embark-dwim
           "C-h B" embark-bindings) ;; alternative for `describe-bindings'

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

;; Used for export and edit after ripgrep magic.
(setup (:pkg wgrep))

(provide 'init-embark)
;;; init-embark.el ends here

Consult

Consult provides practical commands based on the Emacs completion function completing-read.

Consult offers, for example:

  • Buffer switching command consult-buffer to switch between buffers and recently opened files.
  • Multiple asynchronous search commands:
    • consult-grep
    • consult-ripgrep
    • consult-line, which resembles Swiper

(require 'init-consult)
;;; init-consult.el --- Consult completing read -*- lexical-binding: t -*-

;;; Commentary:

;; Consult provides commands based on Emacs `completion-read' functionality.  Here my basic configuration and key-bindings.  Totally WIP.

;;; Code:

(setup (:pkg consult)
  (:require consult)

  ;; C-c bindings (mode specific)
  (:global "C-c h" consult-history
           "C-c M" consult-mode-command
           "C-c b" consult-bookmark
           "C-c k" consult-kmacro)

  ;; C-x bindings
  (:global "C-x M-c" consult-complex-command      ; orig. repeat-complex-command
           "C-x b"   consult-buffer               ; orig. switch-to-buffer
           "C-x 4 b" consult-buffer-other-window  ; orig. switch-to-buffer-other-window
           "C-x 5 b" consult-buffer-other-frame)  ; orig. switch-to-buffer-other-frame

  ;; [C]-[M]-# bindings for registers
  (:global "C-M-#" consult-register
           "M-#"   consult-register-load
           "C-#"   consult-register-store) ; orig. abbrev-prefix-mark (unrelated)

  ;; Other custom bindings
  (:global "M-y"   consult-yank-pop  ; orig. yank-po
           "C-h a" consult-apropos)  ; orig. apropos-comman

  ;; M-g bindings (goto-map)
  (:bind-into goto-map
    "g"   consult-goto-line     ; orig. goto-line
    "o"   consult-org-heading   ; Alternative: consult-org-heading
    "m"   consult-mark
    "k"   consult-global-mark
    "i"   consult-imenu
    "I"   consult-imenu-multi
    "e"   consult-compile-error
    "f"   consult-flymake)     ; Alternative: consult-flymake

  ;; M-s bindings (search-map)
  (:bind-into search-map
    "f" consult-find
    "F" consult-locate
    "g" consult-grep
    "G" consult-git-grep
    "r" consult-ripgrep
    "l" consult-line
    "L" consult-line-multi
    "m" consult-multi-occur
    "k" consult-keep-lines
    "u" consult-focus-lines
    "e" consult-isearch-history)  ; Isearch integration

  ;; ??? From wiki
  ;; (:bind-into isearch-mode-map
  ;;   "M-e" consult-isearch-history       ; orig. isearch-edit-string
  ;;   "M-s e" consult-isearch-history     ; orig. isearch-edit-string
  ;;   "M-s l" consult-line                ; needed by consult-line to detect isearch
  ;;   "M-s L" consult-line-multi)         ; needed by consult-line to detect isearch

  ;; Register formatting. This improves the register
  ;; preview for `consult-register', `consult-register-load',
  ;; `consult-register-store' and the Emacs built-ins.
  (:option register-preview-delay 0
           register-preview-function #'consult-register-format)

  ;; Optionally configure the narrowing key.
  (:option consult-narrow-key "<")

  ;; Use Consult to select xref locations with preview
  (:option xref-show-xrefs-function #'consult-xref
           xref-show-definitions-function #'consult-xref)

  ;; Optionally configure preview. The default value
  ;; is 'any, such that any key triggers the preview.
  ;; 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-customize
   consult-ripgrep consult-git-grep consult-grep
   consult-bookmark consult-recent-file consult-xref
   consult--source-recent-file consult--source-project-recent-file
   consult--source-bookmark :preview-key "M-.")

  ;; Use `consult-completion-in-region' if Vertico is enabled.
  ;; Otherwise use the default `completion--in-region' function.
  (setq completion-in-region-function
        (lambda (&rest args)
          (apply (if vertico-mode
                     #'consult-completion-in-region
                   #'completion--in-region)
                 args)))

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

  ;; Enable automatic preview at point in the *Completions* buffer. This is
  ;; relevant when you use the default completion UI. You may want to also
  ;; enable `consult-preview-at-point-mode` in Embark Collect buffers.
  (:hooks completion-list-mode consult-preview-at-point-mode))

(setup (:pkg consult-dir)
  (:load-after consult
    (:global "C-x C-d" consult-dir)
    (:bind-into minibuffer-local-completion-map
      "C-x C-d" consult-dir-maybe
      "C-x C-j" consult-dir-jump-file)))

(setup (:pkg consult-eglot)
  (:load-after (consult eglot)
    (:global "M-g s" consult-eglot-symbols)))

(provide 'init-consult)
;;; init-consult.el ends here

Completion at point

I’m using Corfu with Cape right now, while Company stuff is here due to other modes completion backends which relies on it. I prefer Corfu especially because it uses Emacs completion facilities, and child frames instead of overlays.

Completions are provided by commands which provide completion, or by Capfs (completion-at-point-functions). Many major modes implement a Capf, also LSP clients which talk to the LSP server to retrieve completion.

Cape provides extensions and backends. A great thing of Cape is the cape-company-to-capf adapter for Company backends, and it is very easy to use!


(require 'init-complete-in-buffer)
;;; init-complete-in-buffer.el --- In buffer completion configuration -*- lexical-binding: t -*-

;;; Commentary:

;; Corfu completion UI and Cape extensions for better completion at point.

;;; Code:

(setup (:pkg corfu)
  (global-corfu-mode)

  (load "extensions/corfu-history")
  (load "extensions/corfu-popupinfo")

  (corfu-history-mode 1)

  (corfu-popupinfo-mode 1)
  (:option corfu-popupinfo-delay t)

  (add-to-list 'savehist-additional-variables 'corfu-history)

  ;; SECTION FOR SPECIAL FUNCTIONS
  ;; Movement
  (defun contrib-corfu-beginning-of-prompt ()
    "Move to beginning of completion input."
    (interactive)
    (corfu--goto -1)
    (goto-char (car completion-in-region--data)))

  (defun contrib-corfu-end-of-prompt ()
    "Move to end of completion input."
    (interactive)
    (corfu--goto -1)
    (goto-char (cadr completion-in-region--data)))

  (define-key corfu-map [remap move-beginning-of-line] #'corfu-beginning-of-prompt)
  (define-key corfu-map [remap move-end-of-line] #'corfu-end-of-prompt)

  ;; From Corfu's manual
  (defun contrib-corfu-move-to-minibuffer ()
    (interactive)
    (let ((completion-extra-properties corfu--extra)
          completion-cycle-threshold completion-cycling)
      (apply #'consult-completion-in-region completion-in-region--data)))
  (define-key corfu-map "\M-m" #'contrib-corfu-move-to-minibuffer)

  ;; Adapted from Corfu's manual.
  ;; (Found in Prot's configuration)
  (defun contrib-corfu-enable-always-in-minibuffer ()
    "Enable Corfu in the minibuffer if Vertico is not active.
Useful for prompts such as `eval-expression' and `shell-command'."
    (unless (or (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-popupinfo-delay nil)
      (corfu-mode 1)))
  (add-hook 'minibuffer-setup-hook #'contrib-corfu-enable-always-in-minibuffer 1)

  (:option corfu-cycle t
           corfu-auto t
           corfu-separator ?\s
           corfu-quit-at-boundary nil
           corfu-quit-no-match t
           corfu-preview-current #'insert
           corfu-preselect-first t
           corfu-on-exact-match #'insert
           corfu-echo-documentation 0.25
           corfu-min-width 30
           corfu-scroll-margin 5)

  (:bind-into corfu-popupinfo-map
    "M-p" corfu-popupinfo-scroll-down
    "M-n" corfu-popupinfo-scroll-up
    "M-d" corfu-popupinfo-toggle))

(setup (:pkg kind-icon)
  (:load-after corfu
    (:option kind-icon-default-face 'corfu-default
       kind-icon-default-style '(:padding 0 :stroke 0 :margin 0 :radius 0 :height 0.7 :scale 1.0))
    (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter)))

(setup (:pkg cape)
  ;; Needed for company-backends!
  (setup (:pkg company)
    (:autoload company-grab))

  (dolist (backend '(cape-symbol cape-keyword cape-file cape-dabbrev))
    (add-to-list 'completion-at-point-functions backend))

  (:global "C-c p p" completion-at-point
     "C-c p t" complete-tag
     "C-c p d" cape-dabbrev
     "C-c p h" cape-history
     "C-c p f" cape-file
     "C-c p k" cape-keyword
     "C-c p s" cape-symbol
     "C-c p a" cape-abbrev
     "C-c p i" cape-ispell
     "C-c p l" cape-line
     "C-c p w" cape-dict
     "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))

(provide 'init-complete-in-buffer)
;;; init-complete-in-buffer.el ends here

Org Mode

Org mode is the killer feature of Emacs. Markup language, agenda, brain, templates…you can do literally (xD) everything.

Essential configuration

I absolutely need focus when I’m editing my documents in the dark, so I want my buffer centered and lines untruncated.

Indentation is defined as a function for basic org-mode setup.

The purpose of visual-fill-column olivetti is to center org-mode buffers for a more pleasing writing experience as it centers the contents of the buffer horizontally to seem more like you are editing a document.

Org Modern replaces markup syntax with nice headings, TODOs etc.


(require 'init-org)
;;; init-org.el --- Org mode configuration -*- lexical-binding: t -*-

;;; Commentary:

;; Org mode is certainly the killer feature of Emacs.
;; You can do anything, for example capturing of templates, export, markdown like editing.

;;; Code:

(defun archer-org-mode-setup ()
  "Set important modes for me while editing org documents.

- Setting variable-pitch allows different face definition;
- I prefer visual-line here, instead of truncating lines."
  (variable-pitch-mode 1)
  (visual-line-mode 1))

(setup (:pkg org)
  ;; General
  (:option org-adapt-indentation nil
           org-fold-catch-invisible-edits 'smart
           org-cycle-separator-lines 1
           org-auto-align-tags nil
           org-tags-column 0 ;; place tags directly next to headline text
           org-archive-mark-done nil
           org-startup-folded 'content
           org-insert-heading-respect-content t
           org-read-date-prefer-future 'time
           org-startup-folded t
           org-startup-indented t

           ;; Prettify
           org-ellipsis " ⤵" ;; "…" "⤵"
           org-hide-leading-stars t
           org-pretty-entities t
           org-pretty-entities-include-sub-superscripts t
           org-hide-emphasis-markers t
           org-fontify-quote-and-verse-blocks t
           org-list-allow-alphabetical t
           org-highlight-latex-and-related '(native latex)
           org-image-actual-width 500

           ;; Date
           org-display-custom-times t
           org-time-stamp-custom-formats '("<%d %b %Y>" . "<%d/%m/%y %a %H:%M>")

           ;; Footnotes
           org-footnote-section nil   ;; place footnotes locally
           org-footnote-auto-adjust t ;; renumber footnotes

            ;; Insertion/Yanking
           org-M-RET-may-split-line '((default . t)) ;; don't split line when creating a new headline, list item, or table field
           org-yank-adjusted-subtrees t              ;; adjust subtrees to depth when yanked
           org-yank-folded-subtrees t                ;; fold subtrees on yank

           org-list-demote-modify-bullet '(("+" . "-") ("-" . "+") ("*" . "+"))
           org-list-indent-offset 1 ;; increase sub-item indentation

           ;; Movement
           org-return-follows-link t ;; make RET follow links
           org-special-ctrl-a/e t    ;; better movement in headers

           ;; Searching
           org-imenu-depth 8   ;; scan to depth 8 w/imenu
           imenu-auto-rescan t ;; make sure imenu refreshes

           ;; Source block settings
           org-src-fontify-natively t         ;; use lang-specific fontification
           org-src-window-setup 'other-window ;; edit source in other window
           org-src-tab-acts-natively t        ;; use lang bindings
           org-confirm-babel-evaluate t       ;; confirm evaluation

           ;; TODOS
           org-use-fast-todo-selection 'expert ;; don't use popup window for todos
           ;; don't set to DONE if children aren’t DONE
           org-enforce-todo-dependencies t
           org-enforce-todo-checkbox-dependencies t

           ;; Source blocks
           org-hide-block-startup nil
           org-src-preserve-indentation nil
           org-edit-src-content-indentation 2)

  (org-babel-do-load-languages
   'org-babel-load-languages '((emacs-lisp . t)
                               (shell . t)
                               (groovy . t)))

  (push '("conf-unix" . conf-unix) org-src-lang-modes)

  (:local-set completion-at-point-functions '(cape-dabbrev cape-file))

  (:hook archer-org-mode-setup))

(setup (:pkg org-appear)
  (:autoload org-appear-mode)
  (:hook-into org-mode)
  (:option org-appear-autoemphasis t
           org-appear-autolinks nil
           org-appear-autosubmarkers t))

(setup (:pkg org-modern)
  (:load-after org)
  (:hook-into org-mode)
  (set-face-attribute 'org-modern-symbol nil :family "Hack")
  (:option org-modern-label-border 1
           org-modern-hide-stars nil      ;; Compatibility with org-indent
           org-modern-block-fringe nil    ;; Bad
           org-modern-variable-pitch nil
           org-modern-timestamp t
           org-modern-table t
           org-modern-table-vertical 1
           org-modern-table-horizontal 0))

(setup (:pkg (org-modern-indent :type git :host github :repo "jdtsmith/org-modern-indent"))
  (:hook-into org-indent-mode))

(setup (:pkg olivetti)
  (:load-after org)
  (:hook-into org-mode)
  (:option olivetti-body-width 0.75
           olivetti-minimum-body-width 75
           olivetti-style 'fancy))

(provide 'init-org)
;;; init-org.el ends here

Babel and Tempo

To execute or export code in org-mode code blocks, we need to set up org-babel-load-languages for each language. This page documents all of the languages that you can use with org-babel.

Org Mode’s structure templates feature enables to quickly insert code blocks into your Org files in combination with org-tempo by typing < followed by the template name like el or py and then press TAB. To add more src block templates, just copy one of the lines and change the two strings at the end, the first to be the template name and the second to contain the name of the language (listed here).

There’s also a snippet that adds a hook to org-mode buffers so that archer-org-babel-tangle-config gets executed each time such a buffer gets saved. This function checks to see if the file being saved is the Emacs.org file you’re looking at right now, and if so, automatically exports the configuration here to the associated output files. This function is inspired by David Wilson of System Crafters.


(require 'init-org-languages)
;;; init-org-languages.el --- Language related org settings -*- lexical-binding: t -*-

;;; Commentary:

;; We can execute code in org-mode, but also define structure templates
;; to insert blocks (like src blocks).
;; Tangling is also an important feature, let's use it.

;;; Code:

(setup org-tempo
  (:load-after org
    (add-to-list 'org-structure-template-alist '("bash" . "src bash"))
    (add-to-list 'org-structure-template-alist '("el" . "src emacs-lisp"))
    (add-to-list 'org-structure-template-alist '("cc" . "src c"))
    (add-to-list 'org-structure-template-alist '("j" . "src java")))

  (:with-mode org-mode
    (:local-set electric-pair-inhibit-predicate
                `(lambda (c) (if (char-equal c ?<) t (,electric-pair-inhibit-predicate c))))))

(setup ob-tangle
  ;; Auto tangling
  (defun archer-org-babel-tangle-config ()
    "Auto tangle configuration on save if we are in the right directory."
    (when (string-equal (file-name-directory (buffer-file-name))
                        (expand-file-name archer-config-path))
      ;; Dynamic scoping to the rescue
      (let ((org-confirm-babel-evaluate nil))
        (org-babel-tangle))))

  (:with-mode org-mode
    (:with-hook after-save-hook
      (:hook archer-org-babel-tangle-config))))

(provide 'init-org-languages)
;;; init-org-languages.el ends here

Exporting


(require 'init-org-export)

Org is surely nice, but what about export? Wonderful, but it needs some tweaks, and not only for presentations.

The first section of this file is regards good LaTeX export through ox-latex. The best way to set export options is the following.

Structure every file like this:


#+LaTeX_CLASS: article
#+LaTeX_CLASS_OPTIONS: [letterpaper]
#+OPTIONS: toc:nil
#+SETUPFILE: ~/your/path/to/setup/file.org

Reveal.js presentations are exported through ox-reveal, which is very simple to configure.

The hidden gem is ox-hugo, you can manage your website content from Emacs, that’s cool. You can also manage your contents with a single file, multiple files, or both ways!

;;; init-org-export.el --- Org exports configuration -*- lexical-binding: t -*-

;;; Commentary:

;; We can export in any format with org-mode, but we need some
;; tweaks to achieve good results.
;; Here are listed all the settings for ox-latex, ox-reveal, etc.

;;; Code:

;; LaTeX export
(setup ox-latex
  (:load-after ox)
  (:option org-latex-toc-command "\\tableofcontents \\clearpage"  ; Newpage after TOC
           ;; Enable listings
           org-latex-listings t
           ;; Previewing LaTeX fragments in org mode, default changed for bad quality.
           org-latex-create-formula-image-program 'imagemagick
           ;; Using minted for tables
           org-latex-listings 'minted
           org-latex-packages-alist '(("" "minted"))
           org-latex-minted-options '(("breaklines" "true")
                                      ("breakanywhere" "true"))
           ;; PDF process
           ;; '("latexmk -pdflatex='pdflatex -interaction nonstopmode' -pdf -bibtex -f %f")
           org-latex-pdf-process '("pdflatex --shell-escape -interaction nonstopmode -output-directory %o %f"
                                   "pdflatex --shell-escape -interaction nonstopmode -output-directory %o %f"
                                   "pdflatex --shell-escape -interaction nonstopmode -output-directory %o %f"))

  ;; (add-to-list 'org-latex-listings-langs '(yaml "yaml"))
  ;; (add-to-list 'org-latex-listings-langs '(groovy "groovy"))

  ;; LaTeX base classes
  (:when-loaded (add-to-list 'org-latex-classes
                             '("org-plain-latex"
                               "\\documentclass{article}
                 [NO-DEFAULT-PACKAGES]
                 [PACKAGES]
                 [EXTRA]"
                               ("\\section{%s}" . "\\section*{%s}")
                               ("\\subsection{%s}" . "\\subsection*{%s}")
                               ("\\subsubsection{%s}" . "\\subsubsection*{%s}")
                               ("\\paragraph{%s}" . "\\paragraph*{%s}")
                               ("\\subparagraph{%s}" . "\\subparagraph*{%s}")))))

;; Reveal.js
(setup (:pkg (ox-reveal :type git :host github :repo "yjwen/org-reveal"))
  (:load-after ox)
  (:option org-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js"))

;; Hugo
(setup (:pkg ox-hugo)
  (:load-after ox))

(provide 'init-org-export)
;;; init-org-export.el ends here

Development

All my packages needed to develop in a decent way. Bye IDEs.

Projects management and Git


(require 'init-projects)

Projectile provides easy project management and navigation.

Common Git operations are easy to execute quickly using Magit’s command panel system.

NOTE: Make sure to configure a GitHub token before using this package!

;;; init-projects.el --- Projects management -*- lexical-binding: t -*-

;;; Commentary:

;; Git integration and projects' folders management.

;;; Code:

(setup (:pkg direnv)
  (:hook-into prog-mode))

(setup (:pkg magit)
  (:autoload magit-status)
  (:option magit-display-buffer-function 'magit-display-buffer-same-window-except-diff-v1))

(setup (:pkg forge)
  (:load-after magit))

(setup (:pkg blamer))

;; `projectile', not using to try `project.el'
(setup (:pkg projectile :quit)
  (:blackout)

  ;; NOTE: Set this to the folder where you keep your Git repos!
  (:option projectile-project-search-path '("~/projects")
           projectile-switch-project-action #'projectile-dired)

  (projectile-mode)

  (:global "C-c C-p" projectile-command-map))

(setup (:pkg consult-projectile :quit)
  (:load-after (consult projectile)))

;; `treemacs' stuff, I'm not using it
(setup (:pkg treemacs :quit)
  (:option 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-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           t
           treemacs-max-git-entries                 5000
           treemacs-missing-project-action          'ask
           treemacs-move-forward-on-expand          nil
           treemacs-no-png-images                   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              '("/.direnv" "/node_modules" "/.venv" "/.cask")
           treemacs-project-follow-into-home        nil
           treemacs-show-cursor                     nil
           treemacs-show-hidden-files               t
           treemacs-silent-filewatch                nil
           treemacs-silent-refresh                  nil
           treemacs-sorting                         'alphabetic-asc
           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                           20
           treemacs-width-increment                 1
           treemacs-width-is-initially-locked       nil
           treemacs-workspace-switch-cleanup        nil)

  (:when-loaded
    (setq treemacs-collapse-dirs         (if treemacs-python-executable 3 0)
          treemacs-file-extension-regex  treemacs-last-period-regex-value)

    (treemacs-follow-mode t)
    (treemacs-filewatch-mode t)
    (treemacs-fringe-indicator-mode 'always)
    (treemacs-hide-gitignored-files-mode nil)

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


  (:global "M-0"        treemacs-select-window
           "C-c C-t 1"  treemacs-delete-other-windows
           "C-c C-t t"  treemacs
           "C-c C-t d"  treemacs-select-directory
           "C-c C-t b"  treemacs-bookmark
           "C-c C-t f"  treemacs-find-file
           "C-c C-t T"  treemacs-find-tag))

(setup (:pkg treemacs-projectile :quit)
  (:load-after (treemacs projectile)))

(setup (:pkg treemacs-all-the-icons :quit)
  (:load-after treemacs
    (treemacs-load-theme "all-the-icons")))

(setup (:pkg treemacs-magit :quit)
  (:load-after (treemacs magit)))

(provide 'init-projects)
;;; init-projects.el ends here

Code style

Format-all-the-code lets you auto-format source code in many languages. It is very nice, you need only the formatters installed on your system.

Ethan-wspace is a nice package to avoid useless/horrible extra whitespaces.

Rainbow-delimiters is useful in programming modes because it colorizes nested parentheses and brackets according to their nesting depth. This makes it a lot easier to visually match parentheses in Emacs Lisp code without having to count them yourself.

The rest of init-code-style.el regards tab settings.


(require 'init-code-style)
;;; init-code-style.el --- Code style settings -*- lexical-binding: t -*-

;;; Commentary:

;; OCD, so I have to remove useless whitespace after save or on demand, and format all my code.
;; Plus, general tab settings, tree-sitter support, fancy stuff.

;;; Code:

(setup (:pkg format-all)
  (:blackout)
  (:hook-into prog-mode)
  (:global "<f1>" format-all-buffer))

(setup (:pkg editorconfig)
  (:blackout)
  (editorconfig-mode 1))

(setup eldoc
  (:blackout)
  (global-eldoc-mode 1))

(setup (:pkg rainbow-mode)
  (:hook-into web-mode json-mode))

(setup (:pkg ethan-wspace)
  (:blackout)
  (:global "C-c c" ethan-wspace-clean-all)
  (:hook-into prog-mode)
  ;; Required
  (:option mode-require-final-newline nil
           require-final-newline nil))

;; Tabs, indentation, and the TAB key
(setup indent
  (:option tab-always-indent 'complete
           tab-first-completion 'word-or-paren-or-punct
           tab-width 2
           indent-tabs-mode nil)) ; Use spaces!

(setup (:pkg rainbow-delimiters)
  (:hook-into prog-mode))

(setup (:pkg tree-sitter)
  (:autoload tree-sitter-mode tree-sitter-hl-mode)
  (:hook-into nix-mode c-mode c++-mode java-mode python-mode)
  (:hooks tree-sitter-after-on-hook tree-sitter-hl-mode))

(setup (:pkg tree-sitter-langs)
  (:load-after treesitter))

(provide 'init-code-style)
;;; init-code-style.el ends here

Syntax checking

Lately I’ve been trying Flymake, built-in into Emacs. Flycheck has many checkers though, so here we go with “how to use Flycheck chekers in Flymake”


(require 'init-spell-and-check)
;;; init-spell-and-check.el --- Spell and syntax checking based on modes -*- lexical-binding: t -*-

;;; Commentary:

;; Flyspell as spell checker, while Flycheck as syntax checker for prog-mode.

;;; Code:

(setup flymake
  (:option flymake-fringe-indicator-position 'left-fringe
           flymake-suppress-zero-counters t
           flymake-start-on-flymake-mode t
           flymake-no-changes-timeout 0.3
           flymake-start-on-save-buffer t
           flymake-proc-compilation-prevents-syntax-check t
           flymake-wrap-around nil)

  (:option flymake-mode-line-format
           '("" flymake-mode-line-exception flymake-mode-line-counters))

  (:option flymake-mode-line-counter-format
           '(" " flymake-mode-line-error-counter
             flymake-mode-line-warning-counter
             flymake-mode-line-note-counter ""))

  (add-to-list 'elisp-flymake-byte-compile-load-path load-path)

  (:bind-into ctl-x-x-map
    "m" #'flymake-mode)

  (:bind "C-c ! s" #'flymake-start
         "C-c ! d" #'flymake-show-buffer-diagnostics ; Emacs28
         "C-c ! D" #'flymake-show-project-diagnostics ; Emacs28
         "C-c ! n" #'flymake-goto-next-error
         "C-c ! p" #'flymake-goto-prev-error)

  (:hook-into prog-mode text-mode))

;; From Purcell's dotfiles
(setup (:pkg flymake-flycheck)
  (:load-after flymake)
  (:when-loaded
    (defun sanityinc/enable-flymake-flycheck ()
      (setq-local flymake-diagnostic-functions
                  (append flymake-diagnostic-functions
                          (flymake-flycheck-all-chained-diagnostic-functions))))

    (:option flycheck-emacs-lisp-load-path 'inherit)

    (:hooks flymake-mode sanityinc/enable-flymake-flycheck)))

;; (setup flycheck (:quit) (:pkg flycheck)
;;   (:autoload flycheck-list-errors flycheck-buffer)
;;   (:option flycheck-emacs-lisp-load-path 'inherit
;;            flycheck-idle-change-delay 1.0
;;            flycheck-display-errors-delay 0.25
;;            flycheck-emacs-lisp-initialize-packages t)
;;   (global-flycheck-mode))

(setup flyspell
  (:hooks text-mode-hook (lambda () flyspell-mode 1)
          prog-mode-hook flyspell-prog-mode))

(provide 'init-spell-and-check)
;;; init-spell-and-check.el ends here

Language Server Protocol

Language Server Protocol support with multiples languages support for Emacs.

There are two ways to use LSP with Emacs: lsp-mode and Eglot (built into Emacs 29). I prefer the latter for the following reason, given by the author of Eglot:

Eglot is considerably less code and hassle than lsp-mode.el. In most cases, there’s nothing to configure. It’s a minimalist approach focused on user experience and performance.

To avoid copy-pasting, here the full comparision.


(require 'init-lsp)
;;; init-lsp.el --- Language Server Protocol client -*- lexical-binding: t -*-

;;; Commentary:

;; Here the configuration for LSP-Mode.

;;; Code:

;;
;;; NOTE: These are taken from https://github.com/doomemacs/doomemacs/blob/master/modules/tools/lsp/config.el
(defvar archer-lsp--default-read-process-output-max nil)
(defvar archer-lsp--default-gcmh-high-cons-threshold nil)
(defvar archer-lsp--optimization-init-p nil)

(define-minor-mode archer-lsp-optimization-mode
  "Deploys universal GC and IPC optimizations for `lsp-mode' and `eglot'."
  :global t
  :init-value nil
  :group 'lsp
  (if (not archer-lsp-optimization-mode)
      (setq-default read-process-output-max archer-lsp--default-read-process-output-max
                    gcmh-high-cons-threshold archer-lsp--default-gcmh-high-cons-threshold
                    archer-lsp--optimization-init-p nil)
    ;; Only apply these settings once!
    (unless archer-lsp--optimization-init-p
      (setq archer-lsp--default-read-process-output-max (default-value 'read-process-output-max)
            archer-lsp--default-gcmh-high-cons-threshold (default-value 'gcmh-high-cons-threshold))
      (setq-default read-process-output-max (* 1024 1024))
      ;; REVIEW LSP causes a lot of allocations, with or without the native JSON
      ;;        library, so we up the GC threshold to stave off GC-induced
      ;;        slowdowns/freezes. Doom uses `gcmh' to enforce its GC strategy,
      ;;        so we modify its variables rather than `gc-cons-threshold'
      ;;        directly.
      (setq-default gcmh-high-cons-threshold (* 2 archer-lsp--default-gcmh-high-cons-threshold))
      (gcmh-set-high-threshold)
      (setq archer-lsp--optimization-init-p t))))

(defcustom archer-lsp-client 'eglot
  "Preferred lsp-client."
  :type 'symbol
  :group 'lsp)

;;
;;; LSP-MODE

(setup lsp-mode
  (:quit)
  (:pkg lsp-mode)
  (:autoload lsp)

  (:when-loaded
    ;; Function to enable corfu in lsp-mode
    (defun archer-lsp-mode-setup-completion ()
      (setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
            '(orderless))) ;; Configure orderless

    ;; LSP completion with Corfu
    (when (corfu-mode)
      (setq lsp-completion-provider :none)
      (add-hook 'lsp-completion-mode-hook #'archer-lsp-mode-setup-completion)))

  (:option lsp-keymap-prefix "C-c l"
           lsp-keep-workspace-alive nil
           lsp-auto-guess-root nil
           lsp-log-io nil
           lsp-restart 'auto-restart
           lsp-enable-symbol-highlighting t
           lsp-enable-on-type-formatting t
           lsp-signature-auto-activate nil
           lsp-signature-render-documentation t
           lsp-modeline-code-actions-enable nil
           lsp-modeline-diagnostics-enable nil
           lsp-headerline-breadcrumb-enable t
           lsp-semantic-tokens-enable nil
           lsp-eldoc-render-all t
           lsp-idle-delay 0.5
           lsp-enable-snippet t
           lsp-enable-folding nil
           lsp-enable-imenu t
           lsp-eldoc-hook '(lsp-hover))

  (:with-mode (c-mode c++-mode java-mode nix-mode rustic-mode cmake-mode terraform-mode)
    (:hook lsp-deferred))

  (:hook-into lsp-enable-which-key-integration))

(setup lsp-ui
  (:quit)
  (:pkg lsp-ui)
  (:autoload lsp-ui-mode)
  (:hook-into lsp-mode)
  (:load-after lsp)
  (:when-loaded
    (:option lsp-ui-doc-enable t
             lsp-ui-doc-header t
             lsp-ui-doc-include-signature t
             lsp-ui-doc-border '(face-foreground 'default)
             lsp-ui-sideline-show-code-actions t
             lsp-ui-sideline-delay 0.05)))

(setup lsp-java
  (:quit)
  (:pkg lsp-java)
  (:load-after lsp))

;;
;;; EGLOT

;; Eglot is built-in in Emacs 29+, so this condition doesn't consent the installation
;; if it is already present.
(setup (:and (not (package-installed-p 'eglot))
             (:pkg eglot))
  ;; List of modes and servers
  (:when-loaded
    (add-to-list 'eglot-server-programs '((c++-mode c-mode) "clangd"))
    (add-to-list 'eglot-server-programs '(terraform-mode . ("terraform-ls" "serve")))
    (add-to-list 'eglot-server-programs `(nix-mode . ,(eglot-alternatives '(("nil")
                                                                            ("rnix-lsp"))))))
  ;; Hooks
  (:with-mode (c-mode c++-mode java-mode nix-mode rustic-mode terraform-mode)
    (:hook eglot-ensure)))

(setup (:pkg eglot-java)
  (:load-after eglot)
  (:with-mode (java-mode)
    (:hook eglot-java-mode)))

(setup (:if-feature gcmh)
  (:with-hook (eglot-managed-mode-hook lsp-mode-hook)
    (:hook archer-lsp-optimization-mode)))

(provide 'init-lsp)
;;; init-lsp.el ends here

Snippets


(require 'init-snippets)
;;; init-snippets.el --- Snippets -*- lexical-binding: t -*-

;;; Commentary:

;; Remember code snippet for common functions? Bleah.

;;; Code:

(setup (:pkg yasnippet)
  (:blackout yas-minor-mode)
  (:hooks prog-mode-hook yas-minor-mode)
  (:when-loaded
    (yas-reload-all)))

(setup (:pkg yasnippet-snippets)
  (:load-after yasnippet))

(setup (:pkg (cape-yasnippet :type git :host github :repo "elken/cape-yasnippet"))
  (:load-after (cape yasnippet)
    (defun archer-add-cape-yasnippet ()
      (add-to-list 'completion-at-point-functions #'cape-yasnippet))

    (:with-mode eglot-managed-mode-hook
      (:hook archer-add-cape-yasnippet))

    (:global "C-c p y" cape-yasnippet)))


(provide 'init-snippets)
;;; init-snippets.el ends here

Extra modes


(require 'init-extra-modes)
;;; init-extra-modes.el --- Extra modes -*- lexical-binding: t -*-

;;; Commentary:

;; This is not divided in multiple files, it's useless, I'm good this way :D.

;;; Code:

(setup (:pkg cmake-mode)
  (:file-match (rx (or "CmakeLists.txt" ".cmake") eos)))

(setup (:pkg nix-mode)
  (:file-match (rx ".nix" eos)))

(setup (:pkg markdown-mode)
  (:file-match (rx (or ".md" ".markdown" ".mdown") eos)))

(setup (:pkg yaml-mode)
  (:file-match (rx (or ".yml" ".yaml") eos)))

(setup (:pkg json-mode)
  (:file-match (rx ".json" eos)))

(setup (:pkg rustic)
  (:file-match (rx ".rs" eos))
  (:option rustic-format-on-save nil ; There's `format-all-mode'
           rustic-lsp-client archer-lsp-client))

(setup (:pkg terraform-mode)
  (:file-match (rx ".tf" eos)))

(setup (:pkg company-terraform)
  (:autoload company-terraform)

  (:with-after (cape terraform-mode)
    (defun archer-cape-company-terraform()
      "Add completion at point functions made from company backends for `terraform'."
      (setq-local
       completion-at-point-functions
       (append (list (cape-company-to-capf #'company-terraform)) completion-at-point-functions)))

    (:with-hook terraform-mode-hook
      (:hook archer-cape-company-terraform))))

(provide 'init-extra-modes)
;;; init-extra-modes.el ends here

Frontend for other uses

Emacs can be a frontend for almost everything.

Mails

I’ve used mu4e (mu-for-emacs) for almost a year. It is an e-mail client for GNU Emacs version 24.4 or higher, built on top of the mu e-mail search engine. mu4e is optimized for quickly processing large amounts of e-mail.

However, I’ve always struggled with it, and I recently tried notmuch. Oh boy, I wish I had done it sooner! It is a very fast tag-based email indexer and system to use with multiple clients, Emacs, Neomutt, and so on.

“Not much mail” is what Notmuch thinks about your email collection. Even if you receive 12000 messages per month or have on the order of millions of messages that you’ve been saving for decades. Regardless, Notmuch will be able to quickly search all of it. It’s just plain not much mail.

“Not much mail” is also what you should have in your inbox at any time. Notmuch gives you what you need, (tags and fast search), so that you can keep your inbox tamed and focus on what really matters in your life, (which is surely not email).


(require 'init-mail)
;;; init-mail.el --- Mail configuration -*- lexical-binding: t -*-

;;; Commentary:

;; `Notmuch' is a fast, tag-based email indexer to use with your favorite interface (e.g. Emacs :D).
;; I previously used `mu4e', I didn't really like it though.

;; This code is heavily based on Prot's code.
;; https://github.com/protesilaos/dotfiles/blob/master/emacs/.emacs.d/prot-lisp/prot-notmuch.el
;; https://github.com/protesilaos/dotfiles/blob/master/emacs/.emacs.d/prot-emacs-modules/prot-emacs-email.el

;; I change the prefix of other people's code, and I **always** mention them.  I hope it is not a problem.

;;; Code:

(defgroup archer-notmuch()
  "Extensions for notmuch."
  :group 'notmuch)

(defcustom archer-notmuch-delete-tag "deleted"
  "Tag that applies to mail marked for deletion."
  :type 'string
  :group 'archer-notmuch)

(defcustom archer-notmuch-mark-delete-tags
  `(,(format "+%s" archer-notmuch-delete-tag) "-inbox" "-unread")
  "List of tags to mark for deletion."
  :type '(repeat string)
  :group 'archer-notmuch)

(defcustom archer-notmuch-mark-archive-tags '( "-deleted" "-inbox" "-unread")
  "List of tags to mark for archive."
  :type '(repeat string)
  :group 'archer-notmuch)

(defcustom archer-notmuch-mark-flag-tags '("+flagged" "-unread")
  "List of tags to mark as important (flagged is a special tag)."
  :type '(repeat string)
  :group 'archer-notmuch)

(defcustom archer-notmuch-mark-spam-tags '("+spam" "-inbox" "-unread")
  "List of tags to mark as spam."
  :type '(repeat string)
  :group 'archer-notmuch)

;;;; Autoload of commands
(autoload 'notmuch-interactive-region "notmuch")
(autoload 'notmuch-tag-change-list "notmuch")
(autoload 'notmuch-search-next-thread "notmuch")
(autoload 'notmuch-search-tag "notmuch")

(defmacro archer-notmuch-search-tag-thread (name tags)
  "Produce NAME function parsing TAGS."
  (declare (indent defun))
  `(defun ,name (&optional untag beg end)
     ,(format
       "Mark with `%s' the currently selected thread.
Operate on each message in the currently selected thread.  With
optional BEG and END as points delimiting a region that
encompasses multiple threads, operate on all those messages
instead.
With optional prefix argument (\\[universal-argument]) as UNTAG,
reverse the application of the tags.
This function advances to the next thread when finished."
       tags)
     (interactive (cons current-prefix-arg (notmuch-interactive-region)))
     (when ,tags
       (notmuch-search-tag
        (notmuch-tag-change-list ,tags untag) beg end))
     (when (eq beg end)
       (notmuch-search-next-thread))))

(archer-notmuch-search-tag-thread
  archer-notmuch-search-delete-thread
  archer-notmuch-mark-delete-tags)

(archer-notmuch-search-tag-thread
  archer-notmuch-search-flag-thread
  archer-notmuch-mark-flag-tags)

(archer-notmuch-search-tag-thread
  archer-notmuch-search-spam-thread
  archer-notmuch-mark-spam-tags)

(defmacro archer-notmuch-show-tag-message (name tags)
  "Produce NAME function parsing TAGS."
  (declare (indent defun))
  `(defun ,name (&optional untag)
     ,(format
       "Apply `%s' to message.
With optional prefix argument (\\[universal-argument]) as UNTAG,
reverse the application of the tags."
       tags)
     (interactive "P")
     (when ,tags
       (apply 'notmuch-show-tag-message
              (notmuch-tag-change-list ,tags untag)))))

(archer-notmuch-show-tag-message
  archer-notmuch-show-delete-message
  archer-notmuch-mark-delete-tags)

(archer-notmuch-show-tag-message
  archer-notmuch-show-flag-message
  archer-notmuch-mark-flag-tags)

(archer-notmuch-show-tag-message
  archer-notmuch-show-spam-message
  archer-notmuch-mark-spam-tags)

(autoload 'notmuch-refresh-this-buffer "notmuch")
(autoload 'notmuch-refresh-all-buffers "notmuch")

(defun archer-notmuch-refresh-buffer (&optional arg)
  "Run `notmuch-refresh-this-buffer'.
With optional prefix ARG (\\[universal-argument]) call
`notmuch-refresh-all-buffers'."
  (interactive "P")
  (if arg
      (notmuch-refresh-all-buffers)
    (notmuch-refresh-this-buffer)))

(defun archer-lieer-sendmail ()
  "Set the required variables to send a mail through `lieer'.
To improve."
  (let (from (message-fetch-field "from"))
    (when (string= from "[email protected]")
      (setq-local sendmail-program "gmi"
                  message-sendmail-extra-arguments '("send" "--quiet" "-t" "-C" "~/mails/gmail")))))

;; Current client for mails
(setup notmuch
  (:autoload notmuch notmuch-mua-new-mail)
  ;; UI
  (:option notmuch-show-logo t
           notmuch-column-control 0.5
           notmuch-hello-auto-refresh t
           notmuch-hello-recent-searches-max 15
           notmuch-hello-thousands-separator "."
           notmuch-show-all-tags-list t
           notmuch-hello-insert-footer t
           notmuch-hello-sections
           '(notmuch-hello-insert-header
             notmuch-hello-insert-saved-searches
             notmuch-hello-insert-search
             notmuch-hello-insert-recent-searches
             notmuch-hello-insert-alltags))
  ;; Search
  (:option notmuch-search-oldest-first nil
           notmuch-show-empty-saved-searches t
           notmuch-search-result-format
           '(("date" . "%12s ")
             ("count" . "%-7s ")
             ("authors" . "%-20s ")
             ("subject" . "%80s ")
             ("tags" . "[%s]"))
           notmuch-tree-result-format
           '(("date" . "%12s  ")
             ("authors" . "%-20s")
             ((("tree" . "%s")
               ("subject" . "%s"))
              . " %-80s ")
             ("tags" . "[%s]"))
           notmuch-search-line-faces
           '(("unread" . notmuch-search-unread-face)
             ("flagged" . notmuch-search-flagged-face)))

  ;; Saved searches
  (:option notmuch-saved-searches
           ;; Personal
           `(( :name "📥 inbox (personal)"
               :query "tag:inbox and tag:personal"
               :sort-order newest-first
               :key ,(kbd "p i"))
             ( :name "📔 unread (personal)"
               :query "tag:unread and tag:inbox and tag:personal"
               :sort-order newest-first
               :key ,(kbd "p u"))
             ;; University
             ( :name "📥 inbox (university)"
               :query "tag:inbox and tag:university"
               :sort-order newest-first
               :key ,(kbd "u i"))
             ( :name "📔 unread (university)"
               :query "tag:unread and tag:inbox and tag:university"
               :sort-order newest-first
               :key ,(kbd "u u"))))

  ;; Tags
  (:option notmuch-archive-tags archer-notmuch-mark-archive-tags
           notmuch-message-replied-tags '("+replied")
           notmuch-message-forwarded-tags '("+forwarded")
           notmuch-show-mark-read-tags '("-unread")
           notmuch-draft-tags '("+draft")
           notmuch-draft-folder "drafts"
           notmuch-draft-save-plaintext 'ask)

  ;; Tag formats (with emojis)
  (:option notmuch-tag-formats
           '(("unread" (propertize tag 'face 'notmuch-tag-unread))
             ("flagged" (propertize tag 'face 'notmuch-tag-flagged) ;; Icon is enough
              (concat "🚩")))

           notmuch-tag-deleted-formats
           '(("unread" (notmuch-apply-face bare-tag 'notmuch-tag-deleted)
              (concat "🚫" tag))
             (".*" (notmuch-apply-face tag 'notmuch-tag-deleted)
              (concat "🚫" tag)))

           notmuch-tag-added-formats
           '((".*" (notmuch-apply-face tag 'notmuch-tag-added)
              (concat "✏️" tag))))

  ;; Reading
  (:option notmuch-show-relative-dates t
           notmuch-show-all-multipart/alternative-parts nil
           notmuch-show-indent-messages-width 1
           notmuch-show-indent-multipart t
           notmuch-show-part-button-default-action 'notmuch-show-view-part
           notmuch-show-text/html-blocked-images "." ; block everything
           notmuch-wash-wrap-lines-length 120
           notmuch-unthreaded-show-out nil
           notmuch-message-headers '("To" "Cc" "Subject" "Date")
           notmuch-message-headers-visible t)

  (:option notmuch-wash-citation-lines-prefix 3
           notmuch-wash-citation-lines-suffix 3)

  ;; TODO Composition
  (:option notmuch-mua-compose-in 'current-window
           notmuch-mua-hidden-headers nil
           notmuch-address-command 'internal
           notmuch-always-prompt-for-sender t
           notmuch-mua-cite-function 'message-cite-original
           notmuch-mua-reply-insert-header-p-function 'notmuch-show-reply-insert-header-p-never
           notmuch-mua-user-agent-function nil
           notmuch-maildir-use-notmuch-insert t
           notmuch-crypto-process-mime t
           notmuch-crypto-get-keys-asynchronously t
           notmuch-mua-attachment-regexp   ; see `notmuch-mua-send-hook'
           (concat "\\b\\(attache\?ment\\|attached\\|attach\\|"
                   "pi[èe]ce\s+jointe?\\)\\b"))

  ;; Tagging keys
  (:option notmuch-tagging-keys
           `((,(kbd "d") archer-notmuch-mark-delete-tags "⛔ Mark for deletion")
             (,(kbd "a") archer-notmuch-mark-archive-tags "📫 Mark to archive")
             (,(kbd "f") archer-notmuch-mark-flag-tags "🚩 Flag as important")
             (,(kbd "s") archer-notmuch-mark-spam-tags "⚠️ Mark as spam")
             (,(kbd "r") ("-unread") "✅ Mark as read")
             (,(kbd "u") ("+unread") "📔 Mark as unread")))

  ;; Identities
  (:option notmuch-identies '("[email protected]" "[email protected]")
           notmuch-fcc-dirs '(("[email protected]" . "gmail +personal +sent")
                              ("[email protected]" . "unina/sent +university +sent")))

  ;; Other cosmetic formatting
  (add-to-list 'notmuch-tag-formats '("encrypted" (concat tag "🔒")))
  (add-to-list 'notmuch-tag-formats '("attachment" (concat tag "📎")))

  (:with-hook notmuch-mua-send-hook
    (:hook notmuch-mua-attachment-check))

  (:global "C-c m" notmuch
           "C-x m" notmuch-mua-new-mail)

  (:bind-into notmuch-search-mode-map
    "/" notmuch-search-filter
    "r" notmuch-search-reply-to-thread
    "R" notmuch-search-reply-to-thread-sender)

  (:bind-into notmuch-show-mode-map
    "r" notmuch-show-reply
    "R" notmuch-show-reply-sender)

  (:bind-into notmuch-search-mode-map
    "a" nil
    "A" notmuch-search-archive-thread
    "D" archer-notmuch-search-delete-thread
    "S" archer-notmcuh-search-spam-thread
    "g" archer-notmuch-refresh-buffer)

  (:bind-into notmuch-show-mode-map
    "a" nil
    "A" notmuch-show-archive-message-then-next-or-next-thread
    "D" archer-notmuch-show-delete-message
    "S" archer-notmuch-show-spam-message))

(setup (:pkg consult-notmuch)
  (:load-after (consult notmuch)))

(setup sendmail
  (:option send-mail-function 'sendmail-send-it
           mail-specify-envelope-from t
           message-sendmail-envelope-from 'header
           mail-envelope-from 'header)
  (:with-hook message-send-hook
    (:hook archer-lieer-sendmail)))

(setup message
  (:option message-cite-style 'message-cite-style-gmail
           message-citation-line-function 'message-insert-formatted-citation-line))

(provide 'init-mail)
;;; init-mail.el ends here

Reading

I don’t like DocView because the rendering is given by images in tmp storage, zoom is “bad” (for me, of course), rendering can be slow, with especially PDFs big. My choice is pdf-tools, that renders on demand pages, has good quality, and is very comfortable.


(require 'init-pdf)
;;; init-pdf.el --- PDF reading customization, using pdf-tools -*- lexical-binding: t -*-

;;; Commentary:

;; Just pdf-tools installation and set as default

;;; Code:

;; ???
;; (pdf-view-mode-hook . (lambda () (display-line-numbers-mode -1)))
;; (pdf-view-mode-hook . pdf-tools-enable-minor-modes)

(setup (:pkg pdf-tools)
  (:option display-buffer-alist '(("^\\*outline"
                                   display-buffer-in-side-window
                                   (side . left)
                                   (window-width . 0.20)
                                   (inhibit-switch-frame . t))))

  (:with-mode pdf-view-mode
    (:file-match "\\.[pP][dD][fF]\\'"))

  (pdf-tools-install :no-query))

(setup (:pkg saveplace-pdf-view)
  (:load-after pdf-tools))

(provide 'init-pdf)
;;; init-pdf.el ends here

Terminal

The “best” terminal emulator in Emacs.


(require 'init-shell)
;;; init-shell.el --- Emacs <3 Shell -*- lexical-binding: t -*-

;;; Commentary:

;; This file should contain `eshell', `vterm', and similar terminal emulators available for Emacs.

;;; Code:

(setup (:and (not (archer-using-nix-p))
             (:pkg vterm))
  (:autoload vterm vterm-other-window)
  (:option vterm-buffer-name-string "vterm: %s"
           vterm-max-scrollback 5000
           vterm-kill-buffer-on-exit t))

(setup (:pkg multi-vterm)
  (:load-after vterm)
  (:option multi-vterm-dedicated-window-height-percent 20))

(provide 'init-shell)
;;; init-shell.el ends here

Telegram

Beautiful client, maybe the best telegram client around. A PITA, sometimes, due to tdlib compatibility.


(require 'init-telega)
;;; init-telega.el --- Telegram on Emacs -*- lexical-binding: t -*-

;;; Commentary:

;; Here we go.  The idea of using Emacs for everything is (almost) real.
;; `telega' is a great client, maybe the best client around for Telegram.
;; Sometimes it has issues which depend on the version `tdlib' installed on your system, but what the hell: it's good!

;;; Code:

(setup (:and (not (archer-using-nix-p))
             (:pkg telega))

  (:autoload telega)

  (:option telega-use-images t
           telega-emoji-font-family "Noto Color Emoji"
           telega-emoji-use-images nil
           telega-emoji-company-backend 'telega-company-emoji
           telega-completing-read-function completing-read-function
           telega-animation-play-inline 2
           telega-inserter-for-chat-button 'telega-ins--chat-full-2lines
           telega-chat-button-width 30
           switch-to-buffer-preserve-window-point t
           telega-chat--display-buffer-action '((display-buffer-reuse-window display-buffer-use-some-window))
           telega-completing-read-function 'completing-read
           telega-root-fill-column (+ 20 telega-chat-button-width))


  (put (get 'telega-chat 'button-category-symbol)
       :inserter 'telega-ins--chat-full-2lines)

  ;; From Andrew Tropin <3
  (defun archer-telega-chat-mode ()
    "Add completion at point functions made from company backends."
    (setq-local
     completion-at-point-functions
     (mapcar #'cape-company-to-capf (append (list 'telega-company-emoji
                                                  'telega-company-username
                                                  'telega-company-hashtag)
                                            (when (telega-chat-bot-p telega-chatbuf--chat)
                                              '(telega-company-botcmd))))))

  (:when-loaded
    (:also-load telega-mnz)
    (:global "C-c t" telega-prefix-map))

  (:with-mode telega-chat-mode
    (:hook archer-telega-chat-mode)
    (:hook telega-mnz-mode))

  (:with-hook telega-load-hook
    (:hook telega-notifications-mode)))

(provide 'init-telega)
;;; init-telega.el ends here

Media

Manage your media from Emacs? Possible!


(require 'init-media)
;;; init-media.el --- Manage your medias and more from Emacs O-O -*- lexical-binding: t -*-

;;; Commentary:

;; This section is poor right now, but should contain multimedia functionality to avoid leaving Emacs.

;;; Code:

(setup (:pkg mpv))

(setup (:pkg emms)
  (:require emms-setup)
  (emms-all)

  (:option emms-mode-line t
           ;; emms-source-file-default-directory "~/idkrn/"
           emms-info-asynchronously t
           emms-playing-time t
           emms-info-functions '(emms-info-exiftool)
           emms-browser-covers 'emms-browser-cache-thumbnail-async)

  (add-hook 'emms-player-started-hook #'emms-notify-track-description))

(provide 'init-media)
;;; init-media.el ends here

Daemons control

Nice mode to control your system (and user) services without leaving Emacs.


(setup (:pkg daemons))

End


;;; init.el ends here

This is the end of my init.el, and of my configuration.

Useful things

My experience

I started using Emacs in late 2021, at the beginning of the third year of university.

Why? I needed something to write notes in a fast way, but I didn’t last long: writing notes during my lessons slowed me down, probably because slides given by professors were enough.

Anyway, discovering Emacs was a surprise, and at first it was terrible, because I didn’t know where to start! Too many things to learn, but the community is awesome, resources are good, documentation is almost perfect, and it’s VERY fun. So, I gave a chance to myself to learn Emacs.

How I learned?

C-h, essentially, self-documentation is useful ;). Also EmacsWiki, videos and blog posts, manual, and so on.

Good resources

My learning path has been discontinuous, but good enough to learn this beautiful piece of software from 1976 (1984, for GNU Emacs).

System Crafters
helped me a lot with the series `Emacs from Scratch`, his channel introduced Emacs to me for the first time. My first configuration was almost a copy-paste of David’s configuration…This slowed me down a lot.
Protesilaos Stavrou
is a gold mine, he’s a very clever, wonderful person. I appreciate his verbose explanations about any kind of magic trick he does with Emacs.
Mike Zamansky
has a series dedicated to Emacs, and helped me to figure out some obscure matters.
Andrew Tropin
helped me on both Emacs and Nix (now he’s using Guix), the problem of reproducibility is fascinating, and this guy is really prepared.
Steve Purcell
has a dev-centered configuration, but everyone can take inspiration from its dotfiles.
Vincent Zhang
author of Centaur, really good work.
Doom Emacs
an opinionated distribution of Emacs, providing many modules and optimizations.