Ronaldo GliganHome

Ronaldo's Emacs literate configuration

Table of Contents

ellas.png

This document is a literate configuration made in Emacs' Org mode, which is exported to two files: early-init.el and init.el. Emacs then loads these files on startup.

This document is like a living organism; it is constantly changing and evolving.

Skim through the table of contents to find the sections that may be relevant to you. Take snippets as you fancy.

This is a note for inexperienced Emacs users. Emacs is a self documenting system. For you, this means that for every function and every variable you see possibly has documentation that you can use as a reference:

Also note that many Emacs modes and packages have nice and helpful manuals à la GNU.

1. Constants

1.1. Theme constants

(defconst gligan/light-theme 'ellas)
(defconst gligan/dark-theme 'cappuccino-noir) ; somnus

1.2. Font constants

(defconst gligan/fixed-pitch-font "Iosevka Comfy Motion")
(defconst gligan/variable-pitch-font
  (if (eq system-type 'darwin)
      "Inter"
    "Iosevka Comfy Motion Duo"))
(defconst gligan/semi-fixed-pitch-font "Iosevka Comfy Motion Duo")

(defconst gligan/fixed-pitch-font-size 130)
(defconst gligan/variable-pitch-font-size 160)

1.3. Frame aesthetic constants

(defconst gligan/spacious-frame t)

1.4. Constants for default choices

  • Major modes and compilers

    (defconst gligan/scratch-buffer-mode 'c-mode)
    
    (defconst gligan/c-compiler
      (if (eq system-type 'darwin) "clang" "gcc"))
    
  • Go with the flow of other code editors and do things like them. (Who doesn't like to follow fads?)

    (defconst gligan/fad-config nil)
    

1.5. Constants regarding folders and files

(defconst gligan/org-documents-folder "~/Documents/Org/")
(defconst gligan/org-roam-folder
  (concat gligan/org-documents-folder "Notes/"))

2. use-package and MELPA

(require 'package)
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
(package-initialize)

3. Editor preferences (better default behaviour)

  1. Use 2 spaces as indentation

    (setq-default tab-width 2)
    (setq-default indent-tabs-mode nil)
    
  2. Delete the active region when inserting text

    (delete-selection-mode 1)
    
  3. Make word-editing commands work with word components (subwords)

    (global-subword-mode)
    
  4. Display the column number and buffer size in the modeline

    (column-number-mode 1)
    (size-indication-mode 1)
    
  5. Enable word wrapping

    (setq-default word-wrap t)
    
  6. Different default mode for the scratch buffer

    (setq initial-major-mode gligan/scratch-buffer-mode)
    
  7. Display trailing whitespace (only in programming modes)

    (add-hook 'prog-mode-hook
              (lambda ()
                (setq show-trailing-whitespace t)))
    
  8. Don't close Emacs by accident

    (setq confirm-kill-emacs 'y-or-n-p)
    
  9. Dates should be formatted as DD/MM/YYYY and weeks start on Monday.

    (setq calendar-date-style 'european)
    (setq calendar-week-start-day 1)
    
  10. Pixel scrolling instead of line scrolling

    (pixel-scroll-precision-mode 1)
    
  11. Prefer short answers ("y or n") instead of "yes or no"

    (setq use-short-answers t)
    
  12. Don't repeat input lines in comint-mode

    (setq-default comint-process-echoes t)
    

3.1. Mac OS behaviour

Use modifier keys as if the Mac keyboard were a standard one.

(when (eq system-type 'darwin)
  (setq mac-command-modifier       'meta
        mac-option-modifier        'alt
        mac-right-option-modifier  nil
        ns-function-modifier       'hyper
        mac-pass-control-to-system nil))

4. Buffers at initialisation

I find this setup really nice when starting up a fresh instance of Emacs:

  • A *scratch* buffer using c-mode (variable gligan/scratch-buffer-mode).
  • A general *lisp* buffer and an *elisp* (Emacs Lisp) buffer.
  • A *notes* buffer (using text-mode).

This buffer setup is created by the function gligan/set-up-buffers.

(defun gligan/set-up-buffers ()
  (interactive)
  ;; Lisp buffer
  (with-current-buffer (generate-new-buffer "*lisp*")
    (lisp-mode))
  ;; Emacs Lisp buffer
  (with-current-buffer (generate-new-buffer "*elisp*")
    (emacs-lisp-mode)
    (setq-local lexical-binding t))
  ;; Notes buffer
  (with-current-buffer (generate-new-buffer "*notes*")
    (text-mode)
    (auto-fill-mode)))

I run this function with a hook after my whole Emacs configuration is loaded so that all the customisations applied after this section get applied to these temporary buffers.

(add-hook 'after-init-hook #'gligan/set-up-buffers)

5. Creating a minimal, zen/distraction-free frame

  1. Start all frames maximised

    (add-to-list 'default-frame-alist '(fullscreen . maximized))
    
  2. Minimal window without buttons or scrollbars

    (unless (eq system-type 'darwin)
      (menu-bar-mode -1))
    (tool-bar-mode -1)
    (scroll-bar-mode -1)
    
  3. Beautiful padding around the frame when the option is turned on.

    (when gligan/spacious-frame
      (modify-all-frames-parameters
       '((internal-border-width . 15)
         (right-divider-width . 15)
         (left-fringe . 8)
         (right-fringe . 8))))
    
  4. Backup files

    (setq backup-directory-alist
          (cons "." (expand-file-name "backup" user-emacs-directory)))
    (setq auto-save-default t
          create-lockfiles nil
          make-backup-files nil)
    (setq kill-buffer-delete-autosave-files t)
    
  5. Visual hacks

    (setq frame-resize-pixelwise t)
    
    (setq resize-mini-windows 'grow-only)
    
  6. Use a thin cursor instead of a block

    (setq-default cursor-type 'bar)
    
  7. When using a thin cursor, highlighting the current line is a good idea so that your eyes don't get lost in the vast sea of code in all the buffers you have opened.

    (use-package hl-line
      :custom
      (global-hl-line-sticky-flag t)
      :config
      (global-hl-line-mode 1))
    
  8. No start-up visual noise: disable welcome buffer, and scratch buffer does not contain a "helpful" elisp comment.

    (setq inhibit-startup-message t
          inhibit-startup-screen t
          initial-scratch-message nil)
    
  9. Use the proportional font to display text buffers

    (add-hook 'text-mode-hook #'variable-pitch-mode)
    

    The problem is that variable-pitch-mode makes all text proportional, and oftentimes I have block codes. mixed-pitch solves that:

    (use-package mixed-pitch
      :ensure t
      :defer t
      :hook (text-mode . mixed-pitch-mode))
    
  10. Decluttering dired mode and showing human-readable file sizes

    (use-package dired
      :hook ((dired-mode . dired-hide-details-mode)
             (dired-mode . dired-omit-mode ))
      :custom
      (dired-hide-details-hide-symlink-targets nil)
      (dired-free-space nil)
      (dired-mouse-drag-files t))
    

5.1. A simple, custom mode line

(setq mode-line-position-line-format "line %l")
(setq mode-line-position-column-format "column %c")

(setq mode-line-compact nil)
(setq mode-line-right-align-edge 'right-margin)

(setq-default mode-line-format
              (list "%e"
                    " "
                    '(:eval (if (package-installed-p 'nerd-icons)
                                (nerd-icons-icon-for-buffer)
                              ""))
                    "  "
                    mode-line-buffer-identification
                    "    "
                    '(:eval mode-name)
                    "    "
                    "("
                    mode-line-position-line-format
                    ", "
                    mode-line-position-column-format
                    ")"
                    '(:eval (if (and (buffer-file-name)
                                     (buffer-modified-p))
                                "  edited"
                              ""))
                    "    "
                    ;; (if (boundp 'mode-line-format-right-align)
                    ;;     mode-line-format-right-align "")
                    ;; I'll wait for Emacs 30 to have this feature
                    ;; mode-line-modes
                    mode-line-misc-info))

5.2. "Look, ma, I'm just like vscode"

(when gligan/fad-config
  (add-hook 'prog-mode 'display-line-numbers-mode))

6. Theming and Emacs aesthetics

First, declare all external themes as safe (that way, Emacs won't ask you every time you load a theme wether to consider it safe or not):

(setq custom-safe-themes t)

Whenever I change the theme I may want to run some code along with it. That is why I create a hook:

(defvar gligan/after-theme-toggle-hook nil)

6.1. Blend title bar & frame opacity on MacOS

(when (eq system-type 'darwin)
  (add-to-list 'default-frame-alist '(ns-transparent-titlebar  . t)))

6.2. Dark mode based on OS settings and custom toggle

The function gligan/toggle-theme takes care of switching the system theme.

(defun gligan/toggle-theme ()
  "Toggle between the system's light and dark modes"
  (interactive)
  ;; Toggling the theme
  (cond
   ((custom-theme-enabled-p gligan/light-theme)
    (progn (disable-theme gligan/light-theme)
           (load-theme gligan/dark-theme)))
   ((custom-theme-enabled-p gligan/dark-theme)
    (progn (disable-theme gligan/dark-theme)
           (load-theme gligan/light-theme)))
   (t (load-theme gligan/light-theme)))
  ;; Hooks
  (run-hooks 'gligan/after-theme-toggle-hook))
(global-set-key (kbd "M-g t t") 'gligan/toggle-theme)

Whereas the auto-dark package worries about keeping the Emacs theme in sync with the OS.

(use-package auto-dark
  :ensure t
  :custom
  (auto-dark-light-theme gligan/light-theme)
  (auto-dark-dark-theme gligan/dark-theme)
  :config
  (auto-dark-mode t)

  (add-hook 'auto-dark-dark-mode-hook  'gligan/set-modeline-font)
  (add-hook 'auto-dark-light-mode-hook 'gligan/set-modeline-font)
  (add-hook 'auto-dark-dark-mode-hook
            'gligan/spacious-frame-style-tweaks)
  (add-hook 'auto-dark-light-mode-hook
            'gligan/spacious-frame-style-tweaks))

6.2.1. Switching MacOS's theme

(defun gligan/switch-macos-theme ()
  (interactive)
  (when (eq system-type 'darwin)
    (do-applescript
     "tell application \"System Events\"
        tell appearance preferences
          set dark mode to not dark mode
        end tell
      end tell")))

(add-hook 'gligan/after-theme-toggle-hook
          'gligan/switch-macos-theme)

6.3. Styling when gligan/spacious-frame is t

(defun gligan/spacious-frame-style-tweaks ()
  (when gligan/spacious-frame
    (let ((bg (face-attribute 'default :background)))
      (set-face-attribute 'window-divider nil
                          :foreground bg)
      (set-face-attribute 'window-divider-first-pixel nil
                          :foreground bg)
      (set-face-attribute 'window-divider-last-pixel nil
                          :foreground bg))))

(gligan/spacious-frame-style-tweaks)

(add-hook 'gligan/after-theme-toggle-hook
          'gligan/spacious-frame-style-tweaks)

6.4. Custom fonts

(set-face-attribute 'default nil
                    :family gligan/fixed-pitch-font
                    :height gligan/fixed-pitch-font-size)
(set-face-attribute 'fixed-pitch nil
                    :family gligan/fixed-pitch-font
                    :height gligan/fixed-pitch-font-size)
(set-face-attribute 'variable-pitch nil
                    :family gligan/variable-pitch-font
                    :height gligan/variable-pitch-font-size)

6.4.1. Custom mode line font

(defun gligan/set-modeline-font ()
  (set-face-attribute 'mode-line-active (selected-frame)
                      :font gligan/semi-fixed-pitch-font)
  (set-face-attribute 'mode-line-inactive (selected-frame)
                      :font gligan/semi-fixed-pitch-font))

(add-hook 'gligan/after-theme-toggle-hook
          'gligan/set-modeline-font)

6.4.2. Font ligatures

With the font I'm using, ot all but most of the ligatures below work. As far as I know, all Iosevka and Cascadia Code ligatures are included in this list.

(use-package ligature
  :ensure t
  :hook (after-init . global-ligature-mode)
  :config
  (ligature-set-ligatures
   't
   '("!!" "!!." "!=" "!==" "#!" "##" "###" "####" "#(" "#:" "#=" "#?" "#[" "#_" "#_(" "#{" "$>" "%%" "&&" "(*" "*)" "**" "***" "*/" "*>" "++" "+++" "+:" "+>" "--" "---" "--->" "-->" "-:" "-<" "-<<" "->" "->>" "-|" "-~" ".-" ".." "..." "..<" ".=" ".?" "/*" "//" "///" "/=" "/==" "/>" ":+" ":-" "://" "::" ":::" "::=" ":<" ":=" ":>" ";;" "<!--" "<!---" "<$" "<$>" "<*" "<******>" "<*>" "<+" "<+>" "<-" "<--" "<---" "<---->" "<--->" "<-->" "<-<" "<->" "</" "</>" "<:" "<<" "<<-" "<<<" "<<=" "<=" "<=<" "<==" "<===" "<====>" "<===>" "<==>" "<=>" "<>" "<|" "<|>" "<||" "<|||" "<~" "<~>" "<~~" "=!=" "=/=" "=:" "=:=" "=<<" "==" "===" "===>" "==>" "=>" "=>>" ">-" ">->" ">=" ">=>" ">>" ">>-" ">>=" ">>>" "?." "?:" "?=" "??" "[|" "\\\\" "]#" "^=" "__" "_|_" "www" "{|" "|-" "|=" "|>" "|]" "||" "||=" "||>" "|||>" "|}" "~-" "~=" "~>" "~@" "~~" "~~>")))

7. LSP and completion

7.1. The eglot LSP client

Eglot is a minimalistic LSP client for Emacs. For Emacs >= 29, it comes installed by default. The main alternative to Eglot is lsp-mode, which is more complete: it has more features and more customisations, but it is slower and it does not come with Emacs.

(use-package eglot
  :ensure t
  :defer t
  :hook ((c++-mode
          c-mode
          haskell-mode
          nim-mode
          python-mode
          rust-mode
          tuareg-mode
          zig-mode)
         . eglot-ensure)
  :custom
  (eglot-autoshutdown t))

Here is a list of all the servers that Eglot can communicate to. On Fedora I had to install the following:

  • LSP features on C are achieved with clangd.

    (sudo) dnf install clang-tools-extra
    
  • Haskell has haskell-language-server, that can be installed with ghcup.
  • For Nim I use nimlsp:

    nimble install nimlsp
    
  • Python has many LSP servers, I use pylsp.

    pip install python-lsp-server
    
  • Rust has rust-analyzer.
  • OCaml needs ocaml-lsp-server and optionally ocamlformat.

    opam install ocaml-lsp-server ocamlformat
    
  • I can't build Zig's LSP server because it needs a custom version of the compiler.

7.2. Completion with corfu

(use-package corfu
  :ensure t
  :custom
  (corfu-cycle t)
  (corfu-auto nil)
  :init
  (global-corfu-mode))

8. Major modes for languages

8.1. OCaml

(use-package tuareg
  :ensure t
  :defer t)

(use-package dune
  :ensure t
  :defer t)

(use-package merlin
  :ensure t
  :defer t
  :custom
  (merlin-error-after-save nil)
  :config
  (add-hook 'tuareg-mode-hook #'merlin-mode))

(use-package merlin-eldoc
  :ensure t
  :defer t
  :hook (tuareg . merlin-eldoc-setup))

8.2. SML

(use-package sml-mode
  :custom
  (sml-indent-level 2)
  (sml-indent-args  2))

8.3. Zig

(use-package zig-mode
  :defer t
  :custom
  (zig-format-on-save nil)
  :config
  (zig-format-on-save-mode -1))

8.4. Shell mode

(setq sh-basic-offset 2)

8.5. Fish

(setq fish-indent-offset 2)

8.6. CSS

(setq css-indent-offset 2)

9. Org mode

These numbers below define the proportions of the org mode headers in contrast to the paragraph text.

(defun gligan/org-mode-font-setup ()
  (dolist (face '((org-document-title . 1.8)
                  (org-level-1        . 1.3)
                  (org-level-2        . 1.2)
                  (org-level-3        . 1.1)))
    (set-face-attribute (car face) nil
                        :font gligan/variable-pitch-font
                        :weight 'regular
                        :height (cdr face))))
(use-package org
  :hook ((org-mode . gligan/org-mode-font-setup)
         (org-mode . olivetti-mode)
         (org-mode . auto-fill-mode))
  :custom
  (org-directory 'gligan/org-documents-folder)

  (org-hide-emphasis-markers t)
  (org-pretty-entities t)
  (org-startup-folded 'fold)
  (org-startup-indented t)
  (org-tags-column 0)
  (org-highlight-latex-and-related '(latex))

  (org-src-tab-acts-natively t)
  (org-edit-src-content-indentation 0)

  (org-confirm-babel-evaluate nil))

9.1. Centering and auto-filling content

(use-package olivetti
  :ensure t)

9.2. Nice Org aesthetics

(use-package org-modern
  :ensure t
  :config
  (global-org-modern-mode 1))

9.3. Org Roam

(use-package org-roam
  :custom
  (org-roam-directory (file-truename gligan/org-roam-folder))
  :config
  (org-roam-db-autosync-mode)
  :bind (("C-c n f" . org-roam-node-find)
         ("C-c n r" . org-roam-node-random)
         ("C-c n d" . org-roam-dailies-capture-today)
         ("C-c n D" . org-roam-dailies-goto-date)
         (:map org-mode-map
               (("C-c n i" . org-roam-node-insert)
                ("C-c n o" . org-id-get-create)
                ("C-c n t" . org-roam-tag-add)
                ("C-c n a" . org-roam-alias-add)
                ("C-c n l" . org-roam-buffer-toggle)))))

Optionally install the package org-roam-ui to visualise your nodes and links between them in a local browser page.

9.4. Org agenda

(defun gligan/custom-agenda-view ()
  (interactive)
  (org-agenda nil "a"))

(global-set-key (kbd "C-c a") 'org-agenda)
(global-set-key (kbd "C-c s") 'gligan/custom-agenda-view)
(setq org-agenda-files (list gligan/org-documents-folder
                             gligan/org-roam-folder
                             (concat gligan/org-roam-folder "daily/")
                             )
      org-agenda-include-diary t)

My custom agenda views:

(setq org-agenda-custom-commands
      '(("a" "Ronaldo's agenda view"
         ((tags-todo "ua"
                     ((org-agenda-overriding-header "Universidad")))
          (tags-todo "+PRIORITY=\"A\""
                     ((org-agenda-overriding-header "High Priority Tasks")))
          (agenda)
          (todo ""
                ((org-agenda-overriding-header "All tasks"))))
         )))

org-agenda-include-diary shows special calendar events in the diary view.

;; (add-hook 'after-init-hook 'gligan/custom-agenda-view)
;; (add-hook 'org-agenda-mode-hook 'mixed-pitch-mode)

Now let's customise the looks of the agenda (mainly the fonts):

;; Variable pitch elements
(dolist (face '(org-agenda-structure
                org-agenda-diary
                org-agenda-calendar-event))
  (set-face-attribute face nil
                    :font gligan/variable-pitch-font))
;; Fixed pitch elements
(dolist (face '(org-agenda-date
                org-agenda-date-today))
  (set-face-attribute face nil
                    :font gligan/fixed-pitch-font))

;; (set-face-attribute 'org-agenda-structure nil :height 1.4)

10. Compilation mode

(setq-default compilation-scroll-output 'first-error)
(defun gligan/compile-command-for-buffer (&optional only-compile)
  ;; If the buffer is not associated with a file, then there is
  ;; nothing we can really do.
  (if (null (buffer-file-name))
      compile-command
    (let* ((executable-name
            (format "%s.exe" (file-name-sans-extension (buffer-file-name))))

           (buffer-extension (file-name-extension (buffer-file-name)))

           (command
            (pcase buffer-extension
              ("c"
               (format "%s %s -o %s"
                       (if (eq system-type 'darwin) "clang" "gcc")
                       (buffer-file-name)
                       executable-name))
              ("cpp"
               (format "%s -o %s"
                       (if (eq system-type 'darwin) "clang++" "g++")
                       (buffer-file-name)
                       executable-name))
              (_ nil))))

      (cond
       (only-compile command)
       ;; The default compilation command
       ((null command) compile-command)
       ;; Compile and then execute the command
       (t (format "%s && %s" command executable-name))))))
(defun gligan/switch-to-compilation-buffer ()
  (unless (string= (buffer-name) "*compilation*")
    (switch-to-buffer-other-window "*compilation*")))

(defun gligan/compile (&optional only-compile)
  (interactive
   (list
    ;; If given a prefix argument, then the command only compiles, and
    ;; does not run the executable
    (not (null current-prefix-arg))))
  (compile (compilation-read-command
            (gligan/compile-command-for-buffer only-compile))
           t)
  (gligan/switch-to-compilation-buffer))

(defun gligan/recompile ()
  (interactive)
  (let ((last-text-scale
         (with-current-buffer "*compilation*"
           text-scale-mode-amount)))
    (compilation-start compile-command t)
    (gligan/switch-to-compilation-buffer)
    (text-scale-set last-text-scale)))
(global-set-key (kbd "C-c c") 'gligan/compile)
(global-set-key (kbd "C-c x") 'gligan/recompile)

10.1. Colorise ANSI escape codes in compilation buffer

ansi-color is part of Emacs.

(use-package ansi-color
  :config
  (defun gligan/colorise-compilation ()
    (let ((inhibit-read-only t))
      (ansi-color-apply-on-region
       compilation-filter-start (point))))

  (add-hook 'compilation-filter-hook
            #'gligan/colorise-compilation))

11. Activating disabled modes

  1. Narrow to region

    (put 'narrow-to-region 'disabled nil)
    
  2. Remember recently opened files to jump back to them easily

    (recentf-mode 1)
    (global-set-key (kbd "C-x M-b") 'recentf)
    

12. Nicities

12.1. Automatically close and colorise bracket pairs with electric-pair-mode and rainbow-delimiters

electric-pair-mode is included with the Emacs distribution, so we just have to turn it on globally:

(electric-pair-mode 1)
(use-package rainbow-delimiters
  :defer 3
  :ensure t
  :hook (prog-mode . rainbow-delimiters-mode))

12.2. Better M-x completion with vertico, orderless and marginalia

(use-package vertico
  :ensure t
  :custom
  (vertico-cycle t)
  (vertico-count 5)
  :config
  (vertico-mode))

(use-package savehist
  :ensure t
  :config
  (savehist-mode))

(use-package orderless
  :ensure t
  :custom
  (completion-styles '(orderless flex))
  (completion-category-overrides '((eglot (styles . (orderless flex))))))

(use-package marginalia
  :ensure t
  :config
  (marginalia-mode))

12.3. Icons with dired and corfu integration (nerd-icons)

(use-package nerd-icons
  :ensure t)

(use-package nerd-icons-dired
  :ensure t
  :hook (dired-mode . nerd-icons-dired-mode))

(use-package nerd-icons-corfu
  :ensure t
  :after corfu
  :config
  (add-to-list 'corfu-margin-formatters #'nerd-icons-corfu-formatter))

12.4. Show possible key bindings (which-key)

which-key implements a menu that appears after a certain amount of time when a half-pressed keybinding is entered, showing all the possible routes or functions you can perform from there.

(use-package which-key
  :ensure t
  :defer 3
  :config
  (which-key-mode))

12.5. Fold code (yafolding)

Being able to fold regions of code is a feature that is quintessential for any modern source code editor. yafolding figures out the fold regions through indentation, so it is language agnostic. I do not customise the key bindings or any other setting – they are perfect right out of the box for me. Below I leave the default key bindings:

C-RET Folds the current scope
C-S-RET Folds the parent
C-M-RET Folds the entire buffer
(use-package yafolding
  :ensure t
  :defer 10
  :hook (prog-mode . yafolding-mode))

12.6. Enlarge region with moc (Master of ceremonies)

I don't like it when doing moc-focus I see the currently highlighted hl-line and the parentheses matched. I disable them.

(use-package moc
  :bind ("C-c m" . moc-focus)
  :config

  (push '(remove-hl-line
          . ((hl-line . (:background
                         (face-attribute 'default :background)))
             (show-paren-match . (:inverse-video nil))))
        moc-face-remap-presets)
  (add-hook 'moc-focus-mode-hook
            (lambda ()
              (moc-face-remap 'remove-hl-line))))

12.7. Smart abbreviations with tempel

The default templates file is located at $HOME/.emacs.d/templates. Tempel's template format is easy enough once you create a few templates.

(use-package tempel
  :bind (("M-+" . tempel-complete)))

Let me briefly explain to you how the templates work with an example:

;; $HOME/.emacs.d/templates

fundamental-mode

(today (format-time-string "%Y-%m-%d"))


c-mode c++-mode

(guard "#ifndef " (r (upcase (file-name-base (buffer-name))) header-name) "_H" n
       "#define " header-name "_H" n n
       q n n
       "#endif  /* " header-name "_H */" n)

The structure of the template file is as follows:

  • You write the name of the modes you want this template to work with. Note that all derived modes will also have available the templates (for instance, the "today" template is defined for the fundamental mode, so all derived modes –pretty much all modes– will have available that template).
  • After the mode names, we may have many s-expressions. Every sexp starts with the name of the template and the rest of the list is what it expands to.
    • Strings just insert their content literally
    • You can have Emacs Lisp expressions and their result will get inserted (unless they are nil, which wouldn't insert anything).
    • n inserts a new line.
    • You can have named values that get repeated with (r VALUE NAME) – see header-name in the example above.
    • When you reach the q, the template stops.

Now, in any buffer I can write "today", press C-+ and it expands to today's date.

When you have multiple inputs in a templates you can go back and forth between those inputs with C-{ and C-}.

For more information, see the Tempel repo.

12.8. Dictionary search on region with C-c d (dictionary.el)

(setq dictionary-server "dict.org")
(global-set-key (kbd "C-c d") #'dictionary-search)

13. Lisp

(defconst +cl-implementations+ '("sbcl" "ccl64" "clisp" "ecl"))

(setq inferior-lisp-program
      (cl-find-if #'executable-find
                  +cl-implementations+))

(unless inferior-lisp-program
  (message "No Common Lisp implementation found. Install one of %s"
           +cl-implementations+))

14. The Emacs Shell (eshell)

The eshell-mode-map is initially set to nil internally in eshell, and then it is populated locally. So, when I try to define-key for eshell-mode-map I get an error. A workaround is to execute define-key after eshell has loaded through a hook.

(use-package eshell)

(defun gligan/eshell-terminal-clear ()
  (interactive)
  (eshell/clear-scrollback)
  (eshell-send-input))

(defun gligan/custom-eshell-keys ()
  (define-key eshell-mode-map (kbd "C-l")
              'gligan/eshell-terminal-clear))

(add-hook 'eshell-mode-hook #'gligan/custom-eshell-keys)

15. Gligan's custom functionality

This section implements features for Emacs that I believe are easy and concise enough to write in my own configuration, without requiring external packages.

15.1. Transposing lines and region

Source: https://www.emacswiki.org/emacs/MoveText

(defun gligan/move-text-internal (arg)
  (cond
   ((and mark-active transient-mark-mode)
    (if (> (point) (mark))
        (exchange-point-and-mark))
    (let ((column (current-column))
          (text (delete-and-extract-region (point) (mark))))
      (forward-line arg)
      (move-to-column column t)
      (set-mark (point))
      (insert text)
      (exchange-point-and-mark)
      (setq deactivate-mark nil)))
   (t
    (let ((column (current-column)))
      (beginning-of-line)
      (when (or (> arg 0) (not (bobp)))
        (forward-line)
        (when (or (< arg 0) (not (eobp)))
          (transpose-lines arg))
        (forward-line -1))
      (move-to-column column t)))))
(defun gligan/move-text-down (arg)
  (interactive "*p")
  (gligan/move-text-internal arg))

(defun gligan/move-text-up (arg)
  (interactive "*p")
  (gligan/move-text-internal (- arg)))
(global-set-key (kbd "M-p") 'gligan/move-text-up)
(global-set-key (kbd "M-<up>") 'gligan/move-text-up)
(global-set-key (kbd "M-n") 'gligan/move-text-down)
(global-set-key (kbd "M-<down>") 'gligan/move-text-down)

15.2. Duplicate a line

(defun gligan/duplicate-line ()
  (interactive)
  (duplicate-line)
  (next-line))
(global-set-key (kbd "C-c l") 'gligan/duplicate-line)

16. Changelog

Only substantial changes are recorded in this section.

2025-01-22
  • gligan/recompile remembers text scale.
2025-01-21
  • Created this change log.
  • Removed pulsar package.
  • Compilation mode: moved to its own section, added custom compile commands, interactive compilation enabled by default (i.e. you can enter text in the compilation buffer and it gets sent to the process).
  • Deleted gligan/run-program as its functionality is unnecesary given the new behaviour of gligan/compile.