Source on GitHub

Emacs literate configuration

Prelude

Welcome to my literate Emacs configuration. Like many of its kind, it illustrates Newton's famous aphorism about standing on the shoulders of giants. A heartfelt nod goes to the unsung heroes whose Emacs Lisp snippets form the backbone of this file. Speaking of Lisp, I have also had a taste of the enlightenment promised in hacker lore. Here is one of my favorite examples, penned by RMS himself:

In days long ago,
When clocks were so slow,
These basic keystrokes
Took too long for folks,
Unless they were writ
In C code to flit.
Today that's not so,
In Lisp they can go.

Literate programming with Org mode

The beauty of a literate configuration is that it lets me maintain an Emacs configuration, a website, and an Org document at the same time. For example, the header of this file contains the following code:

#+TITLE: Emacs literate configuration
#+PROPERTY: header-args :tangle ../init.el
#+OPTIONS: toc:2 num:nil
#+HTML_HEAD_EXTRA: <link rel="stylesheet" href="site.css">

With these options, I tangle the source blocks in this configuration to my Emacs init.el whenever I save the file (see the Org hook), while also styling the exported page with a small stylesheet stored alongside the Org source. The build-site.el script in the source repository uses the Org exporter bundled with Emacs and a pinned local copy of htmlize for syntax highlighting, keeping local and automated builds reproducible.

Deployment with Nix

Although it is perfectly feasible to configure Emacs with Nix, I prefer a hand-written Emacs Lisp configuration. I use the following overlay to track the latest commits from Emacs' master branch:

nixpkgs.overlays = [
  # Emacs overlay
  (import (builtins.fetchTarball {
    url = https://github.com/nix-community/emacs-overlay/archive/master.tar.gz;
  }))
];

environment.systemPackages = with pkgs; [
  emacsGitNativeComp
];

My NixOS configuration is version-controlled with Git and can deploy my Emacs configuration automatically with Home Manager:

# Emacs configuration
home.file.".emacs.d/" = {
  source = pkgs.fetchFromGitHub {
    owner = "Panadestein";
    repo = "emacsd";
    rev = "f0713755fda2578638246ddb99e0cf26763cdd47";
    sha256 = "00230dm4ssjxyn5crw7qjazs0zk8y2s3ir7ddq5p7rb7x4glgxh4";
  };
  recursive = true;
  onChange = builtins.readFile /etc/nixos/dotfiles/set_emacs.sh;
};
#!/usr/bin/env bash

cd "$HOME"/.emacs.d
mkdir -p ./lib ./snippets/irp-mode ./undo
emacs --batch --eval "(require 'org)" --eval '(org-babel-tangle-file "./content/index.org")'

General configuration

To comply with the Emacs conventions for libraries, the tangled init.el must have the following header and footer:

;;; init.el --- Panadestein's Emacs configuration -*- lexical-binding: t -*-

;; Copyright (C) 2020-2022 Ramón L. Panadés Barrueta

;; Author: Ramón L. Panadés Barrueta <rpana92@gmail.com>
;; Keywords: internal
;; URL: https://panadestein.github.io/emacsd/

;;; Commentary:
;; A fully fledged, reproducible Emacs configuration

;;; Code:

Setting up a package manager

One of the cornerstones of any Emacs distribution is its package manager. I was mostly happy using use-package with the package.el backend, but sometimes needed newer versions of packages such as org-mode. Although tools such as Quelpa make this possible without abandoning package.el, I chose the more reproducible approach offered by straight.el. This purely functional package manager brings me into closer alignment with the lofty principles championed by the Nix community.

(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/raxod502/straight.el/develop/install.el"
         'silent 'inhibit-cookies)
      (goto-char (point-max))
      (eval-print-last-sexp)))
  (load bootstrap-file nil 'nomessage))

To use straight.el properly, I add the following code to early-init.el:

;;; early-init.el --- Emacs early init file  -*- lexical-binding: t; -*-
;;; Commentary:
;; Code to run before init.el is loaded.
;;; Code:

;; Disable package.el
(setq package-enable-at-startup nil)

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

It also integrates neatly with use-package:

(straight-use-package 'use-package)
(use-package straight
  :custom (straight-use-package-by-default t)
  :bind  (("C-<f2>" . hydra-straight-helper/body)))

For reference, here is how to configure use-package with package.el: first add the GNU ELPA and MELPA archives, then require use-package.

(setq package-archives '(("gnu" . "https://elpa.gnu.org/packages/")
                         ("melpa" . "https://melpa.org/packages/"))
      package-quickstart nil)

(eval-and-compile
  (unless (and (fboundp 'package-installed-p)
               (package-installed-p 'use-package))
    (package-refresh-contents)
    (package-install 'use-package))
  (if init-file-debug  
      (setq use-package-compute-statistics t)
    (setq use-package-compute-statistics nil))
  (setq use-package-always-ensure t)
  (setq use-package-expand-minimally t)
  (require 'use-package))

Temporary workarounds

The typical caveat of using bleeding-edge software is the occasional need for manual fixes. Here, I introduce (hopefully) temporary workarounds to address issues in broken packages.

;; All sorts of nasty things happen if wrong Org version
(straight-use-package 'org)

;; Nix-mode needs this package
(use-package reformatter
  :straight t)

;; A bunch of packages need it, including magit
(use-package compat
  :straight (:host github
             :repo "emacs-compat/compat"))

Other workarounds:

  • which-key's native compilation is currently not working
  • emacs-jupyter's native compilation is currently not working
  • cider is disabled
  • magit needs to be loaded after transient

One editor to rule them all, one editor to find them

Here I use the use-package macro to keep Emacs' built-in settings organized in a single block. The variables have self-explanatory names, but I have added a few comments for the sake of clarity.

(use-package emacs
  :straight nil
  :preface
  (defun my-reload-emacs ()
    "Reload the Emacs configuration"
    (interactive)
    (load-file "~/.emacs.d/init.el"))
  (defun revert-buffer-no-confirm ()
    "Revert buffer without confirmation."
    (interactive) (revert-buffer t t))
  :config
  ;; Emacs internal options
  (setq-default initial-scratch-message ";; The purpose of computing is insight, not numbers."
                ring-bell-function #'ignore
                user-full-name "Ramón L. Panadés-Barrueta, PhD"
                inhibit-startup-screen t
                custom-safe-themes t)
  (setq backup-directory-alist
        `(("." . ,(concat user-emacs-directory "backups"))))
  (if init-file-debug
      (setq warning-minimum-level :debug)
    (setq warning-minimum-level :emergency))
  (menu-bar-mode -1)
  (tool-bar-mode -1)
  (show-paren-mode 1)
  (fringe-mode '(0 . 0))
  ;; No scroll bar, always full screen
  (add-to-list 'default-frame-alist
               '(vertical-scroll-bars . nil))
  (add-to-list 'default-frame-alist '(fullscreen . maximized))
  ;; Font
  (add-to-list 'default-frame-alist '(font . "Iosevka-18"))
  ;; Terminal transparency
  (face-spec-set 'default
                 '((((type tty)) :background "unspecified-bg")))
  ;; Line numbers
  (when (version<= "26.0.50" emacs-version)
    (global-display-line-numbers-mode)
    (setq display-line-numbers-type 'relative))
  ;; Remember line number
  (if (fboundp #'save-place-mode)
      (save-place-mode +1)
    (setq-default save-place t))
  ;; Mimetypes
  (setq mailcap-user-mime-data
        '((type . "application/pdf")
          (viewer . pdf-view-mode)))
  ;; Increase memory
  (setq gc-cons-threshold 100000000)
  (setq read-process-output-max (* 1024 1024))
  :bind
  (;; Make ESC close prompts
   ("<escape>" . keyboard-escape-quit)
   ("C-c C-r" . revert-buffer-no-confirm)))

Protecting the configuration from unwanted custom changes

This code reduces clutter and makes the configuration more reproducible.

(let ((customization-file
       (expand-file-name "custom.el" user-emacs-directory)))
  (unless (file-exists-p customization-file)
    (with-temp-buffer
      (insert ";; -*- lexical-binding: t; -*-")
      (write-file customization-file)))
  (setq custom-file customization-file)
  (load custom-file 'noerror))

Precision line highlighting

This code applies line highlighting only in programming and text buffers.

(use-package hl-line
  :straight nil
  :hook
  (prog-mode . hl-line-mode)
  (text-mode . hl-line-mode))

Old habits die hard

Discarding years of honed Vim muscle memory is not an option for me. Still, Emacs keybindings are beginning to feel natural, and in some major modes Evil simply does not make sense.

(use-package evil
  :straight t
  :demand t
  :config
  (evil-mode 1)
  (evil-set-undo-system 'undo-tree)
  (mapc (lambda (mode)
          (evil-set-initial-state mode 'emacs))
        '(bqn-help--mode
        dired-mode
        eldoc-mode
          elfeed-mode
          eshell-mode
          eww-mode
          helpful-mode
          pdf-view-mode
          profiler-report-mode
          realgud-mode
          vterm-mode)))

Streamlining the undo system

Emacs has its quirks, and its default undo system still puzzles me. Perhaps one day I will unlock its full potential; for now, I prefer a linear approach.

(use-package undo-tree
  :straight t
  :init
  (global-undo-tree-mode)
  :config
  (setq undo-tree-history-directory-alist '(("." . "~/.emacs.d/undo")))
  (setq undo-tree-visualizer-timestamps t)
  (setq undo-tree-visualizer-diff t))

Themes

As Nietzsche said, the voice of beauty speaks softly; it creeps only into the most fully awakened souls. That makes a visually pleasant editor important. These are my three favorite themes:

(use-package doom-themes
  :straight t
  :config
  ;; Fix fontlock bug
  (setcdr (assoc 'gnus-group-news-low-empty doom-themes-base-faces)
          '(:inherit 'gnus-group-mail-1-empty :weight 'normal))
  ;; Global settings (defaults)
  (setq doom-themes-enable-bold t
        doom-themes-enable-italic t)
  (load-theme 'doom-vibrant t)
  ;; Enable flashing mode-line on errors
  (doom-themes-visual-bell-config)
  ;; Enable custom neotree theme (all-the-icons must be installed!)
  (doom-themes-neotree-config)
  ;; or for treemacs users
  (setq doom-themes-treemacs-theme "doom-atom")
  (setq doom-themes-treemacs-enable-variable-pitch nil)
  (doom-themes-treemacs-config)
  ;; Corrects (and improves) org-mode's native fontification.
  (doom-themes-org-config))

(use-package gruvbox-theme
  :straight t
  :disabled
  :init (load-theme 'gruvbox-dark-soft t))

(use-package blackboard-theme
  :straight t
  :disabled
  :init (load-theme 'blackboard t))

And, of course, highlighting numbers in source code:

(use-package highlight-numbers
  :straight t
  :hook
  (prog-mode . highlight-numbers-mode))

Handling very long lines

Very long lines are another well-known source of performance problems in Emacs, although they rarely affect me.

(use-package so-long
  :straight nil
  :hook
  (after-init-hook . global-so-long-mode))

Improved parentheses

This helps a lot when working with Lisp.

(use-package smartparens
  :straight t
  :hook (prog-mode . smartparens-mode))

Highlighting parentheses with different colors

This is mostly eye candy, but differently colored parentheses are genuinely helpful in Lisp.

(use-package rainbow-delimiters
  :straight t
  :hook (prog-mode . rainbow-delimiters-mode))

Minimap

This package brings Sublime Text's minimap feature to Emacs. I am reasonably happy with the configuration, although I do not use it regularly yet.

(use-package minimap
  :straight t
  :config
  (setq minimap-window-location 'right)
  (setq minimap-minimum-width 10)
  (setq minimap-dedicated-window t)
  (setq minimap-hide-cursor t)
  (setq minimap-hide-scroll-bar t)
  (setq minimap-hide-fringes t))

An elegant mode line

This excellent contribution from the Doom Emacs community is the best Emacs mode line I have encountered. The companion package all-the-icons provides icons for the mode line, file managers, and many other packages.

(use-package all-the-icons
  ;; Needs a manual `M-x all-the-icons-install-fonts`
  :straight t)

(use-package doom-modeline
  :straight t
  :init (doom-modeline-mode 1)
  :config
  (setq doom-modeline-height 55)
  (setq doom-modeline-buffer-file-name-style 'relative-to-project)
  (setq doom-line-numbers-style 'relative)
  (setq doom-modeline-major-mode-icon t)
  (setq doom-modeline-buffer-state-icon t)
  (setq doom-modeline-major-mode-color-icon t))

Multiple cursors support

This intriguing package offers functionality for handling multiple cursors, although its ergonomics could, in my opinion, use some improvement. Here are some of the most significant commands:

  • C-n creates a cursor at the next match and moves to it
  • C-p creates a cursor at the previous match and moves to it
  • g r q stops all cursors
(use-package evil-mc
  :straight t
  :config
  (global-evil-mc-mode 1))

Tab-bar mode

Tabs are not traditionally part of the standard Emacs workflow, which favors switching between buffers. For completeness, the following configuration adds a tab bar:

  • C-x t f "filename" opens a file in a new tab
  • C-x t 0 closes the current tab
(use-package tabbar
  :straight t
  :bind (("C-<right>" . tabbar-forward)))

Enhanced HTML exports

This package gives Org Babel exports proper syntax highlighting.

(use-package htmlize
  :straight t)

IDE features and documentation

Incremental search and completion with Ivy

Ivy and its companion tools, Swiper and Counsel, provide an effective incremental completion system and fit smoothly into my workflow. Swiper's incremental buffer search functionality, invoked with C-s, outperforms the standard Emacs search.

(use-package ivy
  :straight t
  :diminish
  :bind
  (("C-s" . swiper))
  :config
  (setq ivy-use-virtual-buffers t)
  (ivy-mode 1))

(use-package swiper
  :straight t)

(use-package counsel
  :straight t
  :after ivy
  :hook
  (after-init . counsel-mode)
  :config (counsel-mode)
  :bind
  ("M-x" . counsel-M-x)
  ("C-x b" . counsel-switch-buffer)
  ("C-c f" . counsel-recentf)
  ("C-M-l" . counsel-imenu)
  ("C-x C-f" . counsel-find-file)
  ("<f1> v" . counsel-describe-variable)
  ("<f1> f" . counsel-descbinds-function))

(use-package ivy-prescient
  :straight t
  :after counsel
  :config
  (ivy-prescient-mode 1))

(use-package ivy-rich
  :straight t
  :init
  (ivy-rich-mode 1)
  :after counsel
  :config
  (setq ivy-format-function #'ivy-format-function-line)
  (setq ivy-rich-display-transformers-list
        (plist-put ivy-rich-display-transformers-list
                   'ivy-switch-buffer
                   '(:columns
                     ((ivy-rich-candidate (:width 40))
                      (ivy-rich-switch-buffer-indicators
                       (:width 4 :face error :align right))
                      (ivy-rich-switch-buffer-major-mode
                       (:width 12 :face warning))
                      (ivy-rich-switch-buffer-project
                       (:width 15 :face success))
                      (ivy-rich-switch-buffer-path
                       (:width (lambda (x) (ivy-rich-switch-buffer-shorten-path
                                            x (ivy-rich-minibuffer-width 0.3))))))))))

(use-package all-the-icons-ivy
  :straight t
  :demand t)

Jump anywhere in Emacs with avy

This powerful package offers many ways to jump to visible text using a character-based decision tree. My configuration is minimal because avy-goto-char-timer provides most of the functionality I need:

(use-package avy
  :straight t
  :bind (("M-j" . avy-goto-char-timer)))

Crafting command combos and menus with Hydra

Hydra is a package that simplifies the creation of keybinding combinations using a shared prefix. As a bonus, it also generates a menu displaying all potential commands:

(use-package hydra
  :straight t)

(use-package use-package-hydra
  :straight t)

Here is a nice example for straight.el (the :color keyword symbol has a special purpose here):

(defhydra hydra-straight-helper (:hint nil :color pink)
  "
_c_: check all       _f_: fetch all     _m_: merge all       _n_: normalize all      _u_: push all
_C_: check package   _F_: fetch package _M_: merge package   _N_: normalize package  _U_: push package
_r_: rebuild all     _p_: pull all      _w_: watcher start   _g_: get recipe         _v_: versions freeze
_R_: rebuild package _P_: pull package  _W_: watcher quit    _e_: prune build        _q_: quit
"
  ("c" straight-check-all)
  ("C" straight-check-package)
  ("r" straight-rebuild-all)
  ("R" straight-rebuild-package)
  ("f" straight-fetch-all)
  ("F" straight-fetch-package)
  ("p" straight-pull-all)
  ("P" straight-pull-package)
  ("m" straight-merge-all)
  ("M" straight-merge-package)
  ("n" straight-normalize-all)
  ("N" straight-normalize-package)
  ("u" straight-push-all)
  ("U" straight-push-package)
  ("w" straight-watcher-start)
  ("W" straight-watcher-quit)
  ("g" straight-get-recipe)
  ("e" straight-prune-build)
  ("v" straight-freeze-versions)
  ("q" nil :color blue))

Exploring command options with which-key

Which-key is an indispensable tool for navigating Emacs package keybindings. Its suggestions are especially useful when learning a new package.

(use-package which-key
  :straight (:build (:not native-compile))
  :diminish
  :custom
  (which-key-idle-secondary-delay 0.01)
  (which-key-dont-use-unicode t)
  :config
  (which-key-mode t))

Enhanced help systems with Helpful

While the default Emacs help system is commendable, Helpful elevates it to another level, introducing features such as syntax highlighting and source code exploration for a more intuitive and enriching experience.

(use-package helpful
  :straight t
  :custom
  (counsel-describe-variable-function #'helpful-variable)
  :bind
  ("C-h f" . helpful-function)
  ([remap describe-symbol] . helpful-symbol)
  ([remap describe-variable] . helpful-variable)
  ([remap describe-command] . helpful-command)
  ([remap describe-key] . helpful-key))

Code tags

When navigating large codebases such as electronic-structure packages, tags avoid repetitive grep commands. I use the minimalist gtags-mode, which builds on existing Emacs tools. I have also configured counsel-etags, although its method of parsing the TAGS file tends to be sluggish when used in conjunction with TRAMP.

(use-package gtags-mode
  :straight t
  :hook ((emacs-startup . gtags-mode)))

(use-package counsel-etags
  :straight t
  :disabled
  :bind (("C-]" . counsel-etags-find-tag-at-point))
  :init
  (add-hook 'prog-mode-hook
            (lambda ()
              (add-hook 'after-save-hook
                        'counsel-etags-virtual-update-tags 'append 'local)))
  :config
  (setq counsel-etags-update-interval 60)
  (push "build" counsel-etags-ignore-directories))

Tree-sitter

This package integrates the Tree-sitter incremental parsing library with Emacs.

(use-package tree-sitter
  :straight t)

(use-package tree-sitter-langs
  :straight t
  :after tree-sitter)

Completion with company

Company is a classic completion framework for source code.

(use-package company
  :straight t
  :init
  (add-hook 'after-init-hook 'global-company-mode)
  (setq company-minimum-prefix-length 3
        company-selection-wrap-around t
        company-tooltip-limit 20
        company-tooltip-minimum-width 15
        company-tooltip-align-annotations t))

Language Server Protocol

Microsoft's Language Server Protocol (LSP) brings IDE features to Emacs. I use two major modes for managing LSP servers: lsp-mode and eglot. The latter makes greater use of existing Emacs infrastructure.

(use-package lsp-mode
  :straight t
  :init
  (defun my-lsp-hook ()
    "Do not use lsp-mode with tramp"
    (unless (file-remote-p default-directory)
      (lsp)))
  :config
  (setq lsp-idle-delay 0.5
        lsp-enable-symbol-highlighting t
        lsp-headerline-breadcrumb-enable nil
        lsp-enable-snippet nil)
  :hook ((python-mode . my-lsp-hook)
         (f90-mode . my-lsp-hook)
         (haskell-mode . my-lsp-hook)
         (lsp-mode . lsp-enable-which-key-integration)))

(use-package lsp-ui
  :straight t
  :hook (lsp-mode . lsp-ui-mode)
  :config
  (setq lsp-ui-sideline-enable nil
        lsp-ui-doc-header nil
        lsp-ui-doc-delay 0.5
        lsp-ui-doc-position 'bottom
        lsp-ui-doc-alignment 'frame
        lsp-ui-doc-include-signature t
        lsp-ui-doc-use-childframe t)
  :commands lsp-ui-mode)

(use-package lsp-treemacs
  :straight t)

It is possible to create lsp-mode clients that work over SSH. Here is an example for Fortran and Python:

(lsp-register-client
 (make-lsp-client :new-connection (lsp-tramp-connection "<fortls>")
                  :major-modes '(f90-mode)
                  :remote? t
                  :server-id 'fortls-remote))
(lsp-register-client
 (make-lsp-client :new-connection (lsp-tramp-connection "<pyls>")
                  :major-modes '(python-mode)
                  :remote? t
                  :server-id 'pyls-remote))

In my experience, however, eglot better supports editing with TRAMP. There is little configuration here because most of its defaults already suit my workflow.

(use-package eglot
  :straight t
  :defer t)

Debuggers

The realgud debugger is a complete reimplementation of Emacs' GUD. It supports several debuggers and integrates seamlessly with GDB and PDB.

(use-package realgud
  :straight t
  :disabled)

For an experience closer to mainstream IDEs, you can use dap-mode and the Debug Adapter Protocol:

(use-package dap-mode
  :straight t)

Spell checking with Flyspell

Flyspell does a lot out of the box, so my configuration is minimal.

(use-package flyspell
  :straight nil
  :hook
  ((prog-mode . flyspell-prog-mode)
   (text-mode . turn-on-flyspell))
  :config
  (flyspell-mode +1))

Grammar checking with LanguageTool

This is a free and open-source multilingual grammar checker that supports more than thirty languages. It is indispensable when writing papers in LaTeX or Org. The Emacs package requires LanguageTool and its HTTP server, which can run as a systemd unit.

(use-package langtool
  :straight t
  :init
  (setq langtool-http-server-host "localhost"
        langtool-http-server-port 8081))

It is also available as a language server:

(use-package lsp-ltex
  :straight t
  :commands lsp-ltex-enable
  :init
  (setq lsp-ltex-version "15.2.0")
  (defun lsp-ltex-enable ()
    "Enable lsp-ltex."
    (require 'lsp-ltex)
    (lsp)))

Translator

This package integrates a powerful translation tool with Emacs and supports engines including Google, Bing, and DeepL. I prefer displaying translations in pop-ups, which requires the posframe package. The translation directions can be easily adjusted in the prompt using C-n and C-p.

(use-package gt
  :straight t
  :disabled)

(use-package posframe
  :straight t)

Syntax checking with Flycheck

Flycheck is indeed a valuable asset in the toolkit, despite certain constraints. For instance, the persistent checking might negatively affect performance, and certain checkers can impose severe overheads—this led me to disable mypy. To balance feedback and performance, my current configuration checks files only when they are saved.

(use-package flycheck
  :init
  (setq-default flycheck-disabled-checkers '(python-mypy))
  :config
  (setq flycheck-check-syntax-automatically '(save mode-enable))
  (setq flycheck-scheme-chicken-executable "csc")
  :hook
  (after-init . global-flycheck-mode))

Snippets

Yasnippet offers a comprehensive collection of snippets complemented by an incredibly intuitive syntax. Its notable shortcoming, however, is the conspicuous absence of Scheme snippets. I've crafted custom snippets for the IRPF90 derived mode, which are also included here.

(use-package yasnippet
  :straight t
  :config
  ;; Enable everywhere
  (yas-global-mode 1))
(use-package yasnippet-snippets
  :straight t
  :after yasnippet)
# -*- mode: snippet -*-
# name: ASSERT 
# key: ass
# --
ASSERT ($0)%
# -*- mode: snippet -*-
# name: BEGIN_DOC ... END_DOC
# key: bc
# --
BEGIN_DOC
! $0
END_DOC%   
# -*- mode: snippet -*-
# name: BEGIN_PROVIDER ... END_PROVIDER 
# key: bp
# --
BEGIN_PROVIDER [${1:integer}, ${2:var}]
    $0
END_PROVIDER%  
# -*- mode: snippet -*-
# -*- mode: snippet -*-
# name: BEGIN_SHELL ... END_SHELL 
# key: bsh
# --
BEGIN_SHELL [ ${1:/bin/bash} ]
    $0
END_SHELL%  

Better ediff configuration

Emacs' built-in ediff is more powerful than vimdiff, in my opinion. However, the default configuration can be improved a bit:

(use-package ediff
  :straight nil
  :preface
  (defvar my-ediff-original-windows nil)
  (defun my-store-pre-ediff-winconfig ()
    "Stores the window arrangement before opening ediff."
    (setq my-ediff-original-windows (current-window-configuration)))
  (defun my-restore-pre-ediff-winconfig ()
    "Resets original window arrangement"
    (set-window-configuration my-ediff-original-windows))
  :hook
  ((ediff-before-setup . my-store-pre-ediff-winconfig)
   (ediff-quit . my-restore-pre-ediff-winconfig))
  :config
  (setq ediff-window-setup-function 'ediff-setup-windows-plain
        ediff-split-window-function 'split-window-horizontally))

LLMs

The times they are a-changin'. Large language models are here to stay and, clichéd as it may sound, are likely to shape the future. It is therefore natural to have a convenient Emacs package for querying different models.

(use-package chatgpt-shell
  :straight (:host github
             :repo "xenodium/chatgpt-shell"
             :files ("shell-maker.el" "chatgpt-shell.el" "ob-chatgpt-shell.el"))
  :disabled t
  :config
  (require 'shell-maker)
  (require 'ob-chatgpt-shell)
  :custom
  (chatgpt-shell-openai-key (auth-source-pick-first-password :host "api.openai.com")))

I also add Copilot support, which is effective at generating boilerplate in some languages:

(use-package copilot
  :straight (
           :host github
           :repo "copilot-emacs/copilot.el"
           :files ("*.el"))
  :bind (:map copilot-completion-map
              ("<tab>" . 'copilot-accept-completion)
              ("TAB" . 'copilot-accept-completion)
              ("C-TAB" . 'copilot-accept-completion-by-word)
              ("C-<tab>" . 'copilot-accept-completion-by-word)))

File browsing, terminal emulators, and web browsing

Terminal support with Vterm

This package uses a C library rather than Lisp to emulate a terminal. It is currently my favorite terminal emulator.

(use-package vterm
  :straight t
  :preface
  (defun my/vterm-mode-hook ()
    (hl-line-mode -1)
    (display-line-numbers-mode -1)
    (display-fill-column-indicator-mode -1)
    (auto-fill-mode -1))
  :hook
  ((vterm-mode . my/vterm-mode-hook))
  :custom
  (vterm-kill-buffer-on-exit t)
  (vterm-max-scrollback 10000)
  (vterm-tramp-shells '(("ssh" "/bin/bash")))
  :init
  (which-key-add-key-based-replacements "C-c t" "term")
  :config
  ;; Add find-file-other-window to accepted commands
  (add-to-list 'vterm-eval-cmds
               '("find-file-other-window" find-file-other-window)))

(use-package vterm-toggle
  :straight t
  :bind (("C-`" . vterm-toggle-cd)
         :map vterm-mode-map
         (("<C-return>" . vterm-toggle-insert-cd)
          ("C-M-n" . vterm-toggle-forward)
          ("C-M-p" . vterm-toggle-backward)))
  :custom
  (vterm-toggle-scope 'project)
  (vterm-toggle-project-root t)
  (vterm-toggle-fullscreen-p nil)
  :config
  ;; Show at bottom
  (add-to-list 'display-buffer-alist
               '((lambda(bufname _)
                   (with-current-buffer bufname
                     (equal major-mode 'vterm-mode)))
                 ;; (display-buffer-reuse-window display-buffer-at-bottom)
                 (display-buffer-reuse-window display-buffer-in-direction)
                 ;;display-buffer-in-direction/direction/dedicated is added in emacs27
                 (direction . bottom)
                 (dedicated . t) ;dedicated is supported in emacs27
                 (reusable-frames . visible)
                 (window-height . 0.3))))

A particularly useful vterm feature is message passing, which makes it possible to execute Emacs commands from a vterm buffer, including a remote one. It requires some configuration in .bashrc. For example, the following opens remote files:

vterm_printf(){
    if [ -n "$TMUX" ] && ([ "${TERM%%-*}" = "tmux" ] || [ "${TERM%%-*}" = "screen" ] ); then
        # Tell tmux to pass the escape sequences through
        printf "\ePtmux;\e\e]%s\007\e\\" "$1"
    elif [ "${TERM%%-*}" = "screen" ]; then
        # GNU screen (screen, screen-256color, screen-256color-bce)
        printf "\eP\e]%s\007\e\\" "$1"
    else
        printf "\e]%s\e\\" "$1"
    fi
}

vterm_cmd() {
    local vterm_elisp
    vterm_elisp=""
    while [ $# -gt 0 ]; do
        vterm_elisp="$vterm_elisp""$(printf '"%s" ' "$(printf "%s" "$1" | sed -e 's|\\|\\\\|g' -e 's|"|\\"|g')")"
        shift
    done
    vterm_printf "51;E$vterm_elisp"
}

find_file() {
    echo "/ssh:$(whoami)@$(hostname):$(realpath "${@:-.}")"
    vterm_cmd find-file "/ssh:remotehost:$(realpath "${@:-.}")"
}

Terminal support with Eshell

This command shell is written entirely in Emacs Lisp. Although it is not POSIX-compliant, its native TRAMP support makes it a powerful tool for remote editing. Here I define a custom prompt resembling my Zsh prompt. Use clear 1 to get the same behavior as Bash's clear command.

(use-package eshell
  :straight nil
  :preface
  (defun my-exit ()
    "Remove window when exiting"
    (when (not (one-window-p))
      (delete-window)))
  (defun my-eshell-prompt ()
    "A nice prompt"
    (concat
     "(" (user-login-name) " . "
     (car (reverse (split-string (eshell/pwd) "/"))) ") "))
  :hook
  ((eshell-mode . (lambda ()
                    (add-to-list 'eshell-visual-commands "ssh")
                    (add-to-list 'eshell-visual-commands "tail")
                    (add-to-list 'eshell-visual-commands "top")
                    (eshell/alias "e" "find-file-other-window $1")
                    (eshell/alias "d" "dired-other-window $1") 
                    (eshell/alias "mgu" "magit-diff-unstaged")
                    (eshell/alias "mg" "magit-diff-staged"))))
  :custom
  (eshell-banner-message
   (propertize
    (let ((default-directory "~"))
      (shell-command-to-string "figlet Eshell"))
    'face `(:foreground "#cc5492")))
  (eshell-history-size 10000)
  (eshell-hist-ignore-dups t)
  (eshell-buffer-maximum-lines 10000)
  (eshell-scroll-to-bottom-on-input t)
  (eshell-destroy-buffer-when-process-dies t)
  (eshell-prompt-regexp "^[^\)]*[\)] ")
  (eshell-prompt-function #'my-eshell-prompt)
  :config
  (advice-add 'eshell-life-is-too-much :after 'my-exit)
  (setenv "PAGER" "cat"))

(use-package eshell-toggle
  :straight t
  :custom
  (eshell-toggle-size-fraction 3)
  (eshell-toggle-use-projectile-root nil)
  (eshell-toggle-run-command nil)
  (eshell-toggle-init-function #'eshell-toggle-init-eshell)
  :bind
  ("C-c p" . eshell-toggle))

(use-package fish-completion
  :straight t
  :hook
  (eshell-mode . fish-completion-mode))

TRAMP: Remote file editing made easy

TRAMP (Transparent Remote Access, Multiple Protocols) is an essential package in Emacs that simplifies remote file editing and management. Paired with Dired, TRAMP enables seamless editing of files on remote machines, saving you from the hassle of configuring your editor on obscure supercomputers around the world. With TRAMP, you can open, save, and edit files on remote machines as if they were local files. TRAMP supports various protocols, such as SSH, FTP, and SFTP, allowing you to work with remote files using familiar Emacs commands and keybindings.

(use-package tramp                                        
  :straight nil                                             
  :custom                                                 
  (tramp-default-method "ssh")                           
  :config
  (setq remote-file-name-inhibit-cache nil)
  (setq vc-ignore-dir-regexp               
        (format "%s\\|%s"                  
                vc-ignore-dir-regexp       
                tramp-file-name-regexp))   
  (setq tramp-verbose 1))                  

(use-package tramp-sh                                     
  :straight nil                                             
  :config                                                 
  (add-to-list 'tramp-remote-path 'tramp-own-remote-path))

File browsers

After trying several file browsers, I realized that everything I needed was already available in Dired.

(use-package ranger
  :straight t
  :disabled
  :config
  (setq ranger-preview-file t))

(use-package neotree
  :straight t
  :disabled
  :bind ("<f9>" . 'neotree-toggle)
  :init
  ;; slow rendering
  (setq inhibit-compacting-font-caches t)
  ;; set icons theme
  (setq neo-theme (if (display-graphic-p) 'icons 'arrow))
  (setq neo-smart-open t))

(use-package treemacs
  :straight t
  :defer t
  :bind
  (:map global-map
        ("<f8>" . treemacs)))

(use-package treemacs-evil
  :after (treemacs evil)
  :straight t)

(use-package treemacs-magit
  :after (treemacs magit)
  :straight t)

(use-package all-the-icons-dired
  :straight t)

(use-package dired
  :straight nil
  :custom ((dired-listing-switches "-agho --group-directories-first"))
  :commands (dired dired-jump)
  :bind (("C-x C-j" . dired-jump))
  :hook
  (dired-mode . all-the-icons-dired-mode)
  :config
  (setq dired-kill-when-opening-new-dired-buffer t)
  (evil-define-key 'normal 'dired-mode-map
    "h" 'dired-single-up-directory
    "l" 'dired-single-buffer))

(use-package dired-single
  :straight t)

Web browsers

And because in the Emacs operating system we can do just about anything, I have added the shr-tag-pre-highlight and shrface packages, giving EWW an Org-like appearance. This is useful when comparing programming languages on Rosetta Code.

(use-package shr-tag-pre-highlight
  :straight t
  :init
  (defun shrface-shr-tag-pre-highlight (pre)
    "Highlighting code in PRE."
    (let* ((shr-folding-mode 'none)
           (shr-current-font 'default)
           (code (with-temp-buffer
                   (shr-generic pre)
                   (buffer-string)))
           (lang (or (shr-tag-pre-highlight-guess-language-attr pre)
                     (let ((sym (language-detection-string code)))
                       (and sym (symbol-name sym)))))
           (mode (and lang
                      (shr-tag-pre-highlight--get-lang-mode lang))))
      (shr-ensure-newline)
      (shr-ensure-newline)
      (setq start (point))
      (insert
       (propertize (concat "#+BEGIN_SRC " lang "\n") 'face 'org-block-begin-line)
       (or (and (fboundp mode)
                (with-demoted-errors "Error while fontifying: %S"
                  (shr-tag-pre-highlight-fontify code mode)))
           code)
       (propertize "#+END_SRC" 'face 'org-block-end-line ))
      (shr-ensure-newline)
      (setq end (point))
      (add-face-text-property start end '(:background "#1f2329" :extend t))
      (shr-ensure-newline)
      (insert "\n")))
  :config
  (add-to-list 'shr-external-rendering-functions
               '(pre . shrface-shr-tag-pre-highlight)))

(use-package shrface
  :straight t
  :config
  (shrface-basic)
  (shrface-trial)
  (shrface-default-keybindings)
  (setq shrface-href-versatile t))

(use-package eww
  :straight nil
  :hook
  ((eww-after-render . shrface-mode))
  :config
  (require 'shrface))

And now that we are here, why don't we surf YouTube with this amazing package:

(use-package elfeed
  :straight t)

(use-package elfeed-tube
  :straight t
  :after elfeed
  :config
  (elfeed-tube-setup)
  :bind
  (:map elfeed-show-mode-map
        ("F" . elfeed-tube-fetch)
        ([remap save-buffer] . elfeed-tube-save)
        :map elfeed-search-mode-map
        ("F" . elfeed-tube-fetch)
        ([remap save-buffer] . elfeed-tube-save)))

(use-package elfeed-tube-mpv
  :straight t
  :bind
  (:map elfeed-show-mode-map
        ("C-c C-f" . elfeed-tube-mpv-follow-mode)
        ("C-c C-w" . elfeed-tube-mpv-where)))

IRC and Matrix

Software tinkerers often spend time on IRC, so this configuration is useful both for getting help and for keeping in touch with friends.

(use-package ement
  :straight (:type git
             :host github
             :repo "alphapapa/ement.el"))

PDF support

This package uses another C library and works better than Emacs' default PDF viewer; for example, it supports hyperlinks.

(use-package pdf-tools
  :straight t
  :bind
  (:map pdf-view-mode-map
        ("C-s" . isearch-forward-regexp))
  :hook
  ((pdf-view-mode . (lambda ()
                      (display-line-numbers-mode -1))))
  :config
  (setq-default pdf-view-display-size 'fit-page)
  (setq pdf-annot-activate-created-annotations t)
  (pdf-tools-install))

Multimedia player

For multimedia content, I use emms, a lightweight player written in Emacs Lisp that integrates perfectly with mpv.

(use-package emms
  :straight t
  :config
  (require 'emms-setup)
  (emms-all)
  (add-to-list 'emms-player-list 'emms-player-mpv t)
  (emms-player-set emms-player-mpv
                   'regex
                   (rx (or (: "https://" (* nonl) "youtube.com" (* nonl))
                           (+ (? (or "https://" "http://"))
                              (* nonl)
                              (regexp (eval (emms-player-simple-regexp
                                             "mp4" "mov" "wmv" "webm" "flv" "avi" "mkv"))))))))

Alternatively, I use empv, which provides a more elaborate interface to the mpv player.

(use-package empv
  :straight (:type git
             :host github
             :repo "isamert/empv.el")
  :config
  (setq empv-invidious-instance "https://invidious.tiekoetter.com/api/v1"))

Programming

Here you can find my configuration for different programming languages. I describe it briefly because this section is rather opinionated.

Lisp

The language that gave birth to the "Maxwell's equations of software." Lisp dialects are my favorite programming languages; below is a brief description of what I value in them.

General

Lispy and its Evil companion, Lispyville, improve the overall Lisp experience in Emacs.

(use-package lispy
  :straight t
  :hook ((emacs-lisp-mode . lispy-mode)
         (lisp-mode . lispy-mode)
         (scheme-mode . lispy-mode)))

(use-package lispyville
  :straight t
  :hook (lispy-mode . lispyville-mode))

Common Lisp

A powerful compiler, excellent libraries, and strong object-oriented programming features.

(use-package slime
  :straight t
  :mode ("\\.lisp\\'" . lisp-mode)
  :init
  (setq slime-net-coding-system 'utf-8-unix
        inferior-lisp-program "sbcl")
  (setq slime-contribs '(slime-fancy
                         slime-repl
                         slime-autodoc)))

Scheme and Racket

Minimalism and elegant syntax in their purest form, plus hygienic macros. Racket also has excellent documentation and Scribble.

(use-package geiser
  :straight t
  :config
  (setq geiser-active-implementations '(chez)))

(use-package geiser-chez
  :straight t
  :init
  (setq geiser-chez-binary "scheme"))

(use-package racket-mode
  :straight t
  :mode ("\\.rkt\\'" "\\.scrbl\\'")
  :hook ((racket-mode . racket-xp-mode)))

Clojure

The JVM's enormous library ecosystem, vectors and maps, and a strong focus on functional programming.

(use-package clojure-mode
  :straight t)

(use-package cider
  :straight t
  :disabled t
  :config
  (setq nrepl-log-messages t
        cider-font-lock-dynamically '(macro core function var)
        cider-overlays-use-font-lock t))

Hy

The youngest language on this list is similar in spirit to Clojure but compiles to Python's AST.

(use-package hy-mode
  :straight t
  :mode "\\.hy\\'"
  :commands (hy-mode org-babel-execute:hy)
  :interpreter "hy"
  :hook
  (hy-mode . company-mode)
  (hy-mode . (lambda () (lispy-mode 1)))
  :config
  (add-hook 'hy-mode-hook #'paredit-mode)
  (add-hook 'hy-mode-hook #'rainbow-delimiters-mode))

Sometimes it is very useful to expand Emacs Lisp macros to better understand their functionality. The macrostep package is very useful for this.

(use-package macrostep
  :straight t
  :bind (:map emacs-lisp-mode-map
              ("C-c e" . macrostep-expand)))

Iversonian array languages

I began programming with Fortran, a common initiation for many physicists. The rise of NumPy and SciPy eventually drew me to the Python world. As my interest in Python waned, I turned to C++ but was quickly dissuaded by its aesthetic limitations, which led me to the beauty of Lisp and Haskell. Despite some criticisms of these languages, the core features I value in them have roots in APL (A Programming Language). Programming in Iversonian array languages is a delightful experience.

Dyalog APL

The tool of thought for software solutions.

(use-package dyalog-mode
  :straight t
  :init
  (defun disable-rainbow-delimiters-apl ()
    (rainbow-delimiters-mode -1))
  :hook (dyalog-mode . disable-rainbow-delimiters-bqn))

BQN

An APL for your flying saucer, and the language that interests me most at the moment. I am managing the package myself locally for now:

(use-package bqn-mode
  :straight (:type nil :local-repo "bqn-mode")
  :init
  ;; Rudimentary org-babel support
  (add-to-list 'org-babel-tangle-lang-exts '("bqn" . "bqn"))
  (defun org-babel-expand-body:bqn (body params &optional processed-params)
    "Expand BODY according to PARAMS, return the expanded body."
    (require 'bqn-mode nil t)
    (let ((vars (org-babel--get-vars (or
                                    processed-params
                                    (org-babel-process-params params)))))
      (concat
       (mapconcat
      (lambda (pair)
        (format "%s=%S" (car pair) (org-babel-bqn-var-to-bqn (cdr pair))))
      vars "\n")
       "\n" body "\n")))
  (defun org-babel-execute:bqn (body params)
    "Execute a block of BQN code with org-babel."
    (message "Executing BQN source code block")
    (bqn-comint-evaluate-command body))
  (defun org-babel-bqn-var-to-bqn (var)
    "Convert an elisp var into a string of BQN."
    (format "%S" var))
  (defun org-babel-bqn-initiate-session (&optional session)
    "If there is not a current inferior-process-buffer in SESSION then create."
    (unless (string= session "none")
      (get-buffer-process (bqn-comint-buffer))))

  ;; Fix: disable quail only for BQN
  (defadvice quail-completion (around disable-for-bqn activate)
    (if (string= "BQN-Z" current-input-method)
        nil
      ad-do-it))

  (defun bqn-explain-line ()
    "Explain the current line in BQN, displaying the explanation result in the minibuffer."
    (interactive)
    ;; Capture the current line's content
    (let* ((current-line (thing-at-point 'line t))
           (command-to-explain (concat ")explain " current-line))
         (proc (get-buffer-process (bqn-comint-buffer))))
      ;; Use bqn-comint-evaluate-command to send the command
      (message "%s" (bqn--comint-call-process-silently proc command-to-explain))))

  ;; Too much color in BQN is not good
  (defun disable-rainbow-delimiters-bqn ()
    (rainbow-delimiters-mode -1))

  ;; Modify the default fontlock
  (defface bqn-function
    '((t (:foreground "#3aa548")))
    "Face used for BQN functions."
    :group 'bqn)
  (defface bqn-one-modifier
    '((t (:foreground "#93428b")))
    "Face used for BQN 1-modifiers."
    :group 'bqn)
  (defface bqn-two-modifier
    '((t (:foreground "#b7a31e")))
    "Face used for BQN 2-modifiers."
    :group 'bqn)
  (defface bqn-primitive-function
    '((t (:foreground "#3aa548")))
    "Face used for primitive BQN functions."
    :group 'bqn)
  (defface bqn-primitive-one-modifier
    '((t (:foreground "#93428b")))
    "Face used for primitive BQN 1-modifiers."
    :group 'bqn)
  (defface bqn-primitive-two-modifier
    '((t (:foreground "#b7a31e")))
    "Face used for primitive BQN 2-modifiers."
    :group 'bqn)

  (defun my-bqn-mode-custom-faces ()
    "Customize string face for BQN mode."
    (set (make-local-variable 'font-lock-string-face)
       '(:foreground "#ABB2BF"))
    (font-lock-add-keywords nil
                            '(("\\({\\|}\\)"
                             0 'font-lock-type-face)))
    (font-lock-add-keywords nil
                            '(("\\(\\[\\|\\]\\|\\|\\)"
                               0 '(face (:foreground "cyan"))))))

  :bind (:map bqn-mode-map
            ("M-i" . bqn-help-symbol-info-at-point)
            ("C-c C-e" . bqn-comint-eval-buffer)
            ("C-x e" . bqn-explain-line)
            ("C-x C-e" . bqn-comint-eval-dwim))
  :hook ((bqn-mode . disable-rainbow-delimiters-bqn)
       (bqn-mode . my-bqn-mode-custom-faces)
       (bqn-mode . (lambda ()
                       (set (make-local-variable 'company-backends)
                            '((company-yasnippet company-capf company-keywords)))))))

Uiua

Point-free or die. A stack-based, array-oriented, general-purpose programming language.

(use-package uiua-ts-mode
  :straight t
  :mode "\\.ua\\'")  

Theorem provers

For now, this is only Lean:

(use-package lean4-mode
  :straight (lean4-mode
           :type git
           :host github
           :repo "leanprover/lean4-mode"
           :files ("*.el" "data"))
  :commands (lean4-mode))

My Emacs Lisp packages

Here I plan to add small packages I write from time to time, mainly for non-trivial editing tasks that I encounter at work.

;;; parse-greenx.el --- Utilities to refactor GreenX library

;; Copyright (C) Dr. Ramon L. Panades Barrueta

;; Author: Dr. Ramon L. Panades Barrueta
;; Keywords: parsing, refactoring

;;; Commentary:

;; This module provides some procedures to manipulate the GreenX
;; hard-coded arrays.

;;; Code:

(defun prune-chars-greenx (beginning end)
  "Remove undesired characters in active region from BEGINNING to END."
  (when (use-region-p)
    (mapcar (lambda (spurious-string)
              (replace-string-in-region spurious-string
                                        " "
                                        beginning
                                        end))
            '("," "&"))))

(defun insert-matrix-cols-greenx (matrix-as-list len col-count)
  "Print COL-COUNT columns of length LEN for MATRIX-AS-LIST."
  (when matrix-as-list
    (let ((col-as-string
           (vconcat
            (mapcar
             (lambda (the-numbers)
               (format "%s, " the-numbers))
             (butlast matrix-as-list
                      (- (length matrix-as-list) (- len 1))))
            (list (format "%s" (nth (- len 1) matrix-as-list))))))
      (setq col-count (1+ col-count))
      (insert "\n"
              (format "aw%%aw_erange_matrix(:, %d) = %s" col-count col-as-string)))
    (insert-matrix-cols-greenx (nthcdr len matrix-as-list) len col-count)))

;;;###autoload
(defun matrix-to-cols-greenx (start end len)
  "Print columns of array of shape (-1, LEN) in active region START END."
  (interactive "r\nP")
  (prune-chars-greenx start end)
  (unwind-protect
      (let ((mstart (make-marker))
            (mend (make-marker)))
        (set-marker mstart start)
        (set-marker mend end)
        (goto-char start)
        (let* ((text (buffer-substring start end))
               (elems-matrix
                (mapcar
                 (lambda (srow)
                   (car (read-from-string srow)))
                 (split-string text nil t))))
          (goto-char end)
          (insert-matrix-cols-greenx elems-matrix len 0)
          (delete-region mstart mend)))
    (widen)))

(provide 'parse-greenx)
;;; parse-greenx.el ends here

Fortran

Fortran is a major component of my work as a physicist; this package provides everything I need.

(use-package f90-mode
  :straight nil
  :mode ("\\.f90\\'")
  :hook
  (f90-mode . (lambda () (setq flycheck-gfortran-args "-ffree-form"))))

IRPF90

IRPF90 is an environment for efficient functional programming in Fortran using the Implicit Reference to Parameters (IRP) method. I have developed a derived mode for IRPF90:

;;; irp-mode.el --- A major mode for dealing with IRPF90 files

;;; Commentary:
;; An attempt to support Scemama's IRPF90 in Emacs

;;; Code:

;; Define IRPF90 extended FORTRAN syntax

(defvar irp-font-lock-keywords)

(setq irp-font-lock-keywords
      (let* (
             ;; Define different keywords
             (x-keywords '("BEGIN_PROVIDER" "END_PROVIDER" "ASSERT"
                           "FREE" "PROVIDE" "BEGIN_TEMPLATE"
                           "END_TEMPLATE" "BEGIN_SHELL"
                           "END_SHELL" "IRP_IF" "IRP_ELSE" "TOUCH"
                           "SOFT_TOUCH"))
             (x-types '("double precision" "integer"))
             (x-comments '("BEGIN_DOC" "END_DOC"))

             ;; Generate regex
             (x-keywords-regexp (regexp-opt x-keywords 'words))
             (x-types-regexp (regexp-opt x-types 'words))
             (x-comments-regexp (regexp-opt x-comments 'words)))

        `(
          (,x-types-regexp . font-lock-type-face)
          (,x-keywords-regexp . font-lock-preprocessor-face)
          (,x-comments-regexp . font-lock-comment-face)
          )))

;;;###autoload
(define-derived-mode irp-mode f90-mode "irp mode"
  "Major mode for editing IRPF90 files."
  :syntax-table nil
  :abbrev-table nil
  (font-lock-add-keywords nil irp-font-lock-keywords))

(provide 'irp-mode)
;;; irp-mode.el ends here

This mode can also be configured with use-package:

(use-package irp-mode
  :straight nil
  :mode ("\\.irp.f\\'")
  :load-path "~/.emacs.d/lib")

C and C++

(use-package ccls
  :straight t
  :defer t
  :hook ((c-mode c++-mode objc-mode cuda-mode) . lsp)
  :commands lsp)

Rust

(use-package rust-mode
  :straight t)

Makefile

Here it is important to mention that the following use-package declaration exists solely for aesthetic and organizational purposes. If we macrostep-expand it, we can convince ourselves:

(use-package make-mode
  :straight nil)

which expands to

(progn
  (use-package-ensure-elpa 'make-mode
                           '(nil)
                           'nil)
  (defvar use-package--warning79
    (function
     (lambda
       (keyword err)
       (let
           ((msg
             (format "%s/%s: %s" 'make-mode keyword
                     (error-message-string err))))
         (display-warning 'use-package msg :error)))))
  (condition-case-unless-debug err
      (if
          (not
           (require 'make-mode nil t))
          (display-warning 'use-package
                           (format "Cannot load %s" 'make-mode)
                           :error))
    (error
     (funcall use-package--warning79 :catch err))))

CMake

(use-package cmake-mode
  :mode ("CMakeLists\\.txt\\'" "\\.cmake\(.in\)?\\'")
  :config
  (add-to-list 'company-backends 'company-cmake))

(use-package eldoc-cmake
  :after company
  :hook (cmake-mode . eldoc-cmake-enable))

(use-package cmake-font-lock
  :straight t
  :preface
  (defun my-cmake-font-lock ()
    (let ((auto-refresh-defaults (boundp 'font-lock-keywords)))
      (cmake-font-lock-activate)
      (when auto-refresh-defaults
        (font-lock-refresh-defaults))))
  :init
  (add-hook 'cmake-mode-hook #'my-cmake-font-lock))

CUDA

(use-package cuda-mode
  :mode "\\.cu\\'")

Python

(use-package python
  :straight nil
  :commands python-mode
  :config
  (setq python-shell-interpreter "jupyter"
        python-shell-interpreter-args "console --simple-prompt"
        python-shell-prompt-detect-failure-warning nil)
  (add-to-list 'python-shell-completion-native-disabled-interpreters "jupyter"))

(use-package ein
  :straight t)

(use-package pyvenv
  :straight t
  :init
  (setq pyvenv-workon "emacs")
  (pyvenv-tracking-mode 1))

(use-package py-autopep8
  :straight t
  :hook (python-mode . py-autopep8-mode))

(use-package lsp-pyright
  :straight t
  :hook (python-mode . (lambda ()
                         (require 'lsp-pyright)
                         (lsp)))
  :config
  (setq lsp-pyright-auto-import-completions t
        lsp-pyright-typechecking-mode "basic"))

I also heavily use the emacs-jupyter package to communicate with Jupyter kernels.

(use-package jupyter
  :straight (:build (:not native-compile))
  :config
    (setq jupyter-use-zmq t
      jupyter-org-auto-connect nil))

Julia

(use-package julia-mode
  :straight t
  :mode "\\.jl\\'"
  :hook (julia-mode . lsp-mode))

(use-package lsp-julia
  :straight t
  :custom
  (lsp-julia-default-environment "~/.julia/environments/v1.9"))

(use-package julia-vterm
  :straight t
  :hook (julia-mode . julia-vterm-mode)
  :custom
  (julia-vterm-repl-program "julia -t 4"))

Computer algebra systems

Maxima

Maxima is a CAS written in Common Lisp that has been around for quite some time. It provides capabilities for symbolic and numerical computations. The following package enhances the default Emacs experience with Maxima.

(use-package maxima
  :straight t
  :init
  (setq org-format-latex-options (plist-put org-format-latex-options :scale 2.0)
        maxima-display-maxima-buffer nil)
  (add-to-list 'auto-mode-alist
               (cons "\\.mac\\'" 'maxima-mode))
  (add-to-list 'interpreter-mode-alist
               (cons "maxima" 'maxima-mode)))

SageMath

SageMath is a comprehensive alternative to Mathematica, occasionally surpassing it in performance. However, it's written in Python, which lacks the advanced functional programming capabilities that I generally prefer in CASs.

(use-package sage-shell-mode
  :straight t
  :mode
  ("\\.sage\\'" . sage-shell-mode))

(use-package ob-sagemath
  :straight t)

Graph visualization

This package provides support for Graphviz's dot command-line tool.

(use-package graphviz-dot-mode
  :straight t
  :disabled
  :config
  (setq graphviz-dot-indent-width 4))

Alternative shells

Nushell

This shell draws a lot of inspiration from functional languages:

(use-package nushell-mode
  :straight t)

Xonsh

The best of Python and Bash, in a single shell:

(use-package xonsh-mode
  :straight t)

Haskell

(use-package haskell-mode
  :straight t
  :init
  (setq flymake-allowed-file-name-masks nil)
  :custom
  (haskell-process-load-or-reload-prompt t)
  (haskell-process-auto-import-loaded-modules t)
  (haskell-process-log t)
  (haskell-tags-on-save t))

(use-package lsp-haskell
  :straight t)

Perl

(use-package cperl-mode
  :straight nil
  :mode ("\\.\\([pP][Llm]\\|al\\)\\'" . cperl-mode))

Raku

(use-package raku-mode
  :straight t
  :defer t
  :mode "\\.raku\\'")

Lua

(use-package lua-mode
  :straight t)

Org-mode

Org mode makes this literate configuration possible, while Org Superstar makes Org files look gorgeous.

(use-package org
  :straight t
  :init
  (defun display-ansi-colors ()
    "Fixes kernel output in emacs-jupyter"
    (ansi-color-apply-on-region (point-min) (point-max)))
  :hook
  (org-mode . (lambda () (progn
                           (add-hook 'after-save-hook #'org-babel-tangle :append :local)
                           (add-hook 'org-babel-after-execute-hook #'display-ansi-colors))))
  :config
  (require 'ox-beamer)
  (require 'ol-bibtex)
  (add-to-list 'org-modules 'org-tempo)
  (org-babel-do-load-languages
   'org-babel-load-languages '((awk . t)
                               (C . t)
                               (clojure . t)
                               (dot . t)
                               (emacs-lisp . t)
                               (eshell . t)
                               (fortran . t)
                               (gnuplot . t)
                               (haskell . t)
                               (julia . t)
                               (latex . t)
                               (lisp . t)
                               (maxima . t)
                               (org . t)
                               (python . t)
                               (scheme . t)
                               (shell . t)
                               (jupyter . t)))
  (add-to-list 'org-src-lang-modes '("jupyter-apl" . dyalog))
  (setq org-babel-clojure-backend 'cider)
  (setq org-latex-pdf-process '("latexmk -shell-escape -pdf -outdir=%o %f"))
  (setq org-preview-latex-default-process 'imagemagick)
  (setq org-file-apps '(("\\.pdf\\'" . "evince %s")))
  (setq org-src-window-setup 'current-window)
  (setq org-startup-indented t)
  (setq org-highlight-latex-and-related '(native))
  (setq org-latex-listings 'minted
        org-latex-packages-alist '(("" "minted")))
  (setq org-startup-with-inline-images t)
  (setq org-todo-keywords
        '((sequence "TODO(t)" "BROKEN(b)" "RUNNING(r)" "VERIFY(v)" "URGENT(u)" "PARTIAL(p)"
                    "|" "DONE(d)" "OPTIONAL(o)" "DELEGATED(e)" "IRRELEVANT(i)")))
  (setq org-todo-keyword-faces
        '(("BROKEN" . "red") ("RUNNING" . "yellow")
          ("VERIFY" . "light goldenrod") ("URGENT" . "orange") ("PARTIAL" . "burlywood")
          ("OPTIONAL" . "green") ("IRRELEVANT" . "LightBlue1")
          ("DELEGATED" . "aquamarine3")))
  (plist-put org-format-latex-options :scale 3.0)
  (add-hook 'org-babel-after-execute-hook 'org-redisplay-inline-images))

(use-package org-superstar
  :straight t
  :config
  (add-hook 'org-mode-hook (lambda () (org-superstar-mode 1))))

Some additional export formats:

(use-package ox-reveal
  :straight t
  :after org
  :custom
  (org-reveal-root "file:///home/loren/bin/reveal.js")
  :config
  (setq org-reveal-mathjax t))

(use-package ox-hugo
  :straight t
  :after org)

(use-package ox-ipynb
  :straight (:host github :repo "jkitchin/ox-ipynb")
  :disabled t
  :config (advice-remove 'org-display-inline-images 'font-lock-fontify-buffer)
  :after org)

Add support for encrypted sections:

(use-package org-crypt
  :straight nil
  :after org
  :config
  (org-crypt-use-before-save-magic)
  (setq org-tags-exclude-from-inheritance (quote ("crypt")))
  :custom
  (org-crypt-key "rpana92@gmail.com"))

And, while we are at it, convert everything else to Org:

(use-package org-pandoc-import
  :straight (:host github
             :repo "tecosaur/org-pandoc-import"
             :files ("*.el" "filters" "preprocessors")))

The current Geiser implementation does not play well with org-babel, so I have decided to use ob-racket for evaluating the Scheme code blocks:

(use-package ob-racket
  :after org
  :config
  (add-hook 'ob-racket-pre-runtime-library-load-hook
            #'ob-racket-raco-make-runtime-library)
  :straight (ob-racket
             :type git :host github :repo "hasu/emacs-ob-racket"
             :files ("*.el" "*.rkt")))

Finally, I set up my personal knowledge management system with org-roam. This package leverages the power of org-mode to create knowledge networks using Niklas Luhmann's Zettelkasten method.

(use-package org-roam
  :straight t
  :init
  (setq org-roam-database-connector 'sqlite-builtin)
  :custom
  (org-roam-directory (file-truename "~/Documents/Arbeit/WIKI/notes"))
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n g" . org-roam-graph)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n c" . org-roam-capture)
         ("C-c n j" . org-roam-dailies-capture-today))
  :config
  (setq org-roam-node-display-template
        (concat "${title:*} "
                (propertize "${tags:10}" 'face 'org-tag)))
  (org-roam-db-autosync-mode)
  (require 'org-roam-protocol))

(use-package org-roam-ui
  :straight (:host github
             :repo "org-roam/org-roam-ui"
             :branch "main"
             :files ("*.el" "out"))
    :after org-roam
    :config
    (setq org-roam-ui-sync-theme t
          org-roam-ui-follow t
          org-roam-ui-update-on-save t
          org-roam-ui-open-on-start t))

LaTeX

AUCTeX provides everything you could need for working with LaTeX, and more.

(use-package auctex
  :straight t
  :hook ((LaTeX-mode . LaTeX-preview-setup)
         (LaTeX-mode . LaTeX-math-mode)
         (LaTeX-mode . flyspell-mode)
         (LaTeX-mode . turn-on-reftex))
  :mode ("\\.tex\\'" . latex-mode)
  :config
  (setq TeX-auto-save t
        TeX-parse-self t
        TeX-save-query nil
        TeX-PDF-mode t
        TeX-source-correlate-mode t
        TeX-source-correlate-method 'auto
        TeX-source-correlate-start-server t))

Web

(use-package php-mode
  :mode ("\\.php\\'"))

(use-package web-mode
  :straight t
  :defer t
  :mode ("\\.html\\'" "\\.htm\\'" "\\.css\\'")
  :bind (("C-c C-v" . browse-url-of-buffer))
  :init
  (defun webmd-hooks-mine ()
    "Some hooks for web mode."
    (setq web-mode-markup-indent-offset 2)
    (setq web-mode-css-indent-offset 2)
    (setq web-mode-code-indent-offset 2)
    (setq web-mode-enable-current-column-highlight t)
    (setq web-mode-enable-current-element-highlight t)
    (set-face-attribute 'web-mode-html-tag-bracket-face nil :foreground "red"))

  (defun web-mode-flyspell-verify ()
    "Make flyspell behave correctly in web mode."
    (let ((f (get-text-property (- (point) 1) 'face)))
      (not (memq f '(web-mode-html-attr-value-face
                     web-mode-html-tag-face
                     web-mode-html-attr-name-face
                     web-mode-doctype-face
                     web-mode-keyword-face
                     web-mode-function-name-face
                     web-mode-variable-name-face
                     web-mode-css-property-name-face
                     web-mode-css-selector-face
                     web-mode-css-color-face
                     web-mode-type-face)
                 ))))
  (put 'web-mode 'flyspell-mode-predicate 'web-mode-flyspell-verify)
  :hook
  ((web-mode . company-mode)
   (web-mode . emmet-mode)
   (web-mode . (lambda () (flyspell-mode 1)))
   (web-mode . webmd-hooks-mine)))

(use-package emmet-mode
  :straight t)

(use-package js-mode :straight nil
  :mode ("\\.js\\'"))

(use-package simple-httpd
  :straight t)

YAML

(use-package yaml-mode
  :straight t
  :mode "\.ya?ml\'")

Markdown

(use-package markdown-mode
  :straight t
  :init
  (setq-default markdown-hide-markup t))

reStructuredText

(use-package rst
  :straight nil
  :mode ("\\.rst\\'" . rst-mode)
  :bind (:map rst-mode-map
              ("M-a" . rst-backward-section)
              ("M-e" . rst-forward-section))
  :init
  (setq rst-indent-width 2))

AsciiDoc

(use-package adoc-mode
  :straight t)

JSON

(use-package json-mode
  :mode "\\.json\\'")

(use-package flymake-json
  :hook (json-mode . flymake-json-load))

Git

A porcelain as glorious as the one from Meißen:

(use-package transient
  :straight t)

(use-package magit
  :straight t
  :after transient)

(use-package git-modes
  :straight t
  :mode (("\\.gitattributes\\'" . gitattributes-mode)
         ("\\.gitconfig\\'" . gitconfig-mode)
         ("\\.gitignore\\'" . gitignore-mode)))

GitLab CI

This package is very useful when writing GitLab pipelines:

(use-package gitlab-ci-mode
  :straight t
  :mode
  ("\\.gitlab-ci.yaml\\'" "\\.gitlab-ci.yml\\'"))

(use-package gitlab-ci-mode-flycheck
  :straight t
  :after flycheck gitlab-ci-mode
  :init
  (gitlab-ci-mode-flycheck-enable))

The heretical Vim script

(use-package vimrc-mode
  :straight t)

Nix expression language

Syntax highlighting for Nix expression language files:

(use-package nix-mode
  :straight t
  :mode "\\.nix\\'")

GNU Guix package manager

Guix is a functional package manager inspired by Nix, but it uses Guile Scheme instead of the Nix expression language. This package offers an Emacs interface to it.

(use-package guix
  :straight t)

Postlude

At last, the closing parenthesis. An Emacs configuration is never finished (only briefly free of obvious defects) so fork it, borrow whatever proves useful, and send back any improvement clever enough to survive your own init file. For now, however, Emacs expects a proper ending:

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