Skip to content

Latest commit

 

History

History
1595 lines (1319 loc) · 54.4 KB

File metadata and controls

1595 lines (1319 loc) · 54.4 KB

Emacs configuration

Literate Emacs configuration. This file is intended to be tangled with the make.el script in this repository, and installed with GNU stow.

This file requires the variable “system” to be set and a few functions/macros created by calling the relevant block in dotfiles.org.

Packaging setup

Package.el is really slow and MELPA only tracks the latest git version; if it has unreasonable dependencies the package becomes uninstallable.

Let’s try switching to elpaca; it’s really fast and in principle allows us to install from arbitrary Git commits. (But it didn’t work when I tried it with paperless, the aforementioned bad-dependencies-MELPA package…)

This is the boilerplate to install elpaca if unavailable and configure it:

(defvar elpaca-installer-version 0.11)
(defvar elpaca-directory (expand-file-name "elpaca/" user-emacs-directory))
(defvar elpaca-builds-directory (expand-file-name "builds/" elpaca-directory))
(defvar elpaca-repos-directory (expand-file-name "repos/" elpaca-directory))
(defvar elpaca-order '(elpaca :repo "https://github.com/progfolio/elpaca.git"
                              :ref nil :depth 1 :inherit ignore
                              :files (:defaults "elpaca-test.el" (:exclude "extensions"))
                              :build (:not elpaca--activate-package)))
(let* ((repo  (expand-file-name "elpaca/" elpaca-repos-directory))
       (build (expand-file-name "elpaca/" elpaca-builds-directory))
       (order (cdr elpaca-order))
       (default-directory repo))
  (add-to-list 'load-path (if (file-exists-p build) build repo))
  (unless (file-exists-p repo)
    (make-directory repo t)
    (when (<= emacs-major-version 28) (require 'subr-x))
    (condition-case-unless-debug err
        (if-let* ((buffer (pop-to-buffer-same-window "*elpaca-bootstrap*"))
                  ((zerop (apply #'call-process `("git" nil ,buffer t "clone"
                                                  ,@(when-let* ((depth (plist-get order :depth)))
                                                      (list (format "--depth=%d" depth) "--no-single-branch"))
                                                  ,(plist-get order :repo) ,repo))))
                  ((zerop (call-process "git" nil buffer t "checkout"
                                        (or (plist-get order :ref) "--"))))
                  (emacs (concat invocation-directory invocation-name))
                  ((zerop (call-process emacs nil buffer nil "-Q" "-L" "." "--batch"
                                        "--eval" "(byte-recompile-directory \".\" 0 'force)")))
                  ((require 'elpaca))
                  ((elpaca-generate-autoloads "elpaca" repo)))
            (progn (message "%s" (buffer-string)) (kill-buffer buffer))
          (error "%s" (with-current-buffer buffer (buffer-string))))
      ((error) (warn "%s" err) (delete-directory repo 'recursive))))
  (unless (require 'elpaca-autoloads nil t)
    (require 'elpaca)
    (elpaca-generate-autoloads "elpaca" repo)
    (let ((load-source-file-function nil)) (load "./elpaca-autoloads"))))
(add-hook 'after-init-hook #'elpaca-process-queues)
(elpaca `(,@elpaca-order))

Install use-package support and block the queue to ensure it is available in subsequent setup.

The dependency management for elpaca-use-package seems to be broken for MELPA version as key-chord version is not set correctly. To work around this I made a fork of key-chord which sets the Version metadata properly. I suppose when the other config bugs are sorted out here it may be worth opening a PR to fix it, but I’m not sure if there is anyone at “emacsorphanage” to accept bug fixes?

We also demand that hydra is available at this stage, so we can use it in other package configurations.

(elpaca use-package)
(elpaca-wait)

(elpaca elpaca-use-package
        (elpaca-use-package-mode)
        (setq elpaca-use-package-by-default t))

(elpaca '(key-chord :host github :repo "ajjackson/key-chord" :branch "0.6-versioned"))

(elpaca-wait)
(use-package use-package
  :ensure nil
  :custom
  (use-package-verbose nil)
  (use-package-compute-statistics nil)
  (use-package-always-defer t)
  (use-package-expand-minimally t)
  (use-package-enable-imenu-support t))

(use-package use-package-chords
  :demand t
  :config
  (key-chord-mode 1)
  (setq key-chord-typing-detection t))

(use-package hydra)

(elpaca-wait)

(use-package ships with bind-key, which is actually a pretty neat tool its own right and I should use in this config. It provides some nice tools for overriding commands more explicitly and/or limiting keybindings to certain modes. See source header

Paths, environment

Set up another directory ~/.emacs.d/lisp which can be used for packages that aren’t managed with elpaca. (i.e. experiments or simple personal packages.)

(add-to-list 'load-path "~/.emacs.d/lisp")

Appearance

Native tweaks

Hide toolbar, scrollbars

(if (display-graphic-p)
    (progn
      (tool-bar-mode -1)
      (scroll-bar-mode -1)))

Don’t blink the cursor

(blink-cursor-mode -1)

Silent bell

(setq visible-bell t)

Scroll sensibly with mouse

(setq mouse-wheel-follow-mouse 't)
(setq mouse-wheel-scroll-amount '(1 ((shift) . 1)))

Pretty fringe indicators for visual line mode

(setq visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow))

The next two settings are particularly relevant for Python programming, but are usually what I want: never use tab characters and wrap text at 79 characters. It’s easy to tweak the fill-column with C-x f (not to be confused with find-file, C-x C-f!)

;; Prevent Extraneous Tabs
(setq-default indent-tabs-mode nil)

;; PEP 8 standard fill width
(setq fill-column 79)

Fonts

Fonts are specified on a per-machine basis to account for different monitors, installations etc.

Nice bit of lisp here: let* allows the variables assigned in the “let” statement to refer to previous variables. First we bind an alist of preferred fonts, then use assoc to locate the relevant entry and bind it to a new variable, then we use this to set up our Emacs hooks. When we leave the let* the new variables are gone so we avoid cluttering the namespace.

(let* ((system-preferred-fonts
        '(("Angel.local" . "Menlo-18")
          ("Angel.lan" . "Menlo-18")
          ("Angel" . "Menlo-18")
          ("ajj-mbp-1" . "Input Mono-16")
          ("ajj-mbp-1.local" . "Input Mono-16")
          ("dock-ajj-mbp-1" . "Input Mono-16")
          ("dock-ajj-mbp-1.esc.rl.ac.uk" . "Input Mono-16")
          ("Arctopus" . "Inconsolata-12")
          ("SCLT452Mac". "Inconsolata:weight=Medium:width=SemiCondensed:size=18")
          ))
       (preferred-font
        (cdr (assoc system-name system-preferred-fonts))))
  (if preferred-font
      (progn
        (add-to-list 'default-frame-alist (cons 'font preferred-font))
        (set-frame-font preferred-font nil t))))

For reference, the elisp to change font sizes (including the mode line and minibuffer) is e.g.

;; Set font size to 40. Note scale factor of 10x!
(set-face-attribute 'default nil :height 300)

It could be nice to figure out a decent binding/shortcut for this. There’s a neat setup here using hydra: https://emacs.stackexchange.com/a/7584

Themes

Use similar logic as for font sizes to set different theme defaults for different machines. Fallback to tango-dark as this should be generally available (and is pretty nice!)

If solarized is chosen, use elpaca to install it. The project warns that MELPA should be treated as unstable, so we prefer to use “non-gnu-elpa” instead. This is lower priority than MELPA in elpaca so we get the recipe and pass it explicitly.

Because elisp macros do not evaluate their arguments before running, it is slightly tricky to make sure elpaca sees its arguments as a recipe in the appropriate format. The trick used here is selective quoting/unquoting with ` and ,: by wrapping in an eval we ensure the macro does not run until the inner function has been evaluated.

(let* ((system-preferred-themes
        '(("Angel.local" . solarized)
          ("Angel.lan" . solarized)
          ("Angel" . solarized)
          ("ajj-mbp-1" . solarized)
          ("scpc041.esc.rl.ac.uk" . tango-dark)
          ("Arctopus" . deeper-blue)))
       (preferred-theme
        (cdr (assoc system-name system-preferred-themes)))
       )
  (cond
   ;; No theme if running in a terminal; follow terminal colours
   ((not (display-graphic-p)) nil)

   ;; Install solarized if necessary, then load
   ((equal preferred-theme 'solarized)
    (progn
      (use-package solarized-theme :config (require 'solarized))
      (elpaca-wait)
      (load-theme 'solarized-dark t)
      )
    )

   ;; ;; If not solarized, load by name
   (preferred-theme (load-theme preferred-theme t))

   ;; Fall back to a nice theme if none specified
   (t (load-theme 'tango-dark t))))

If running i3 window manager, get some cool transparent theming up and running.

(if (string-equal (getenv "XDG_CURRENT_DESKTOP") "i3")
    (progn (set-frame-parameter (selected-frame) 'alpha '(90 80))
           (add-to-list 'default-frame-alist '(alpha 90 85))))

Mode-line

Smart-mode-line is more functional than the defaults and keeps this config simple.

(Initially I was having some trouble with the use-package calls here. I think the problem is that elpaca-process-queues isn’t getting called when it should? May need to stick it in this file. I can see that it is indeed on after-init-hook, but maybe this is somehow too late? Running it manually in the new Emacs session also doesn’t seem to do much.)

(use-package smart-mode-line
  :ensure t
  :config
  (setq sml/theme 'dark)
  (setq sml/no-confirm-load-theme t)
  (sml/setup))

“Volatile highlights” give a bit of visual feedback when you paste in a block of text; I find this less disorientating.

(use-package volatile-highlights
  :config
  (volatile-highlights-mode t))

Workstations

Workstations get a bit more bling than servers.

Indicating your line number with a flying cat is the most Emacs thing ever

(use-package nyan-mode
  :config
  (nyan-mode))

Hamburger menu puts a little menu on the mode line so we don’t need the top bar any more.

(use-package hamburger-menu
  :config
  (setq mode-line-front-space 'hamburger-menu-mode-line)
  (menu-bar-mode -1))

Navigation, buffers and files

Bookmarks

Bookmarks are a great and fundamental feature I always forget to use. Maybe initialising the session with them will help?

(setq inhibit-splash-screen t)
(require 'bookmark)
(bookmark-bmenu-list)

(setq initial-buffer-choice (lambda () (get-buffer "*Bookmark List*")))

The Elpaca README says this hook should really go in elpaca-after-init-hook but that doesn’t seem to work.

Quick kill buffer

99% of the time if I kill a buffer without changes I want to kill this buffer. The other 1% of the time I can use `C-x B` for the buffer menu.

(Confusingly, “kill-this-buffer” doesn’t always work so we use “kill-current-buffer”.)

(global-set-key (kbd "C-x k") 'kill-current-buffer)

Zoxide

This zoxide package looks pretty nice but I don’t fully understand all the functions… https://github.com/emacsmirror/zoxide

zoxide-find-file seems a worthy contender for the precious C-c C-f binding, while not quite a drop-in for C-x C-f. (It creates a two-step process of finding the directory and then the file; it’s fine really but different workflow/muscle-memory!)

(use-package zoxide
  :bind (("C-c C-f" . zoxide-find-file)))

Aliases

Quick version of commands that are often accessed with M-x but not worth giving their own binding.

(defalias 'rb 'revert-buffer)
(defalias 'lm 'display-line-numbers-mode)
(defalias 'wsm 'whitespace-mode)
(defalias 'wsc 'whitespace-cleanup)
(defalias 'db 'diff-buffer-with-file)
  

Rebind common commands

C-h should be used for deleting things. The most logical binding for help then becomes C-?, although this is occasionally problematic as it can have other hard bindings.

(global-set-key (kbd "C-?") 'help-command)
(global-set-key (kbd "M-?") 'mark-paragraph)
(global-set-key (kbd "C-h") 'delete-backward-char)
(global-set-key (kbd "M-h") 'backward-kill-word)
  

The fastest way to type an uppercase word is to type it lowercase then convert to allcaps. I do this a lot, so let’s add a shift option to avoid the M-B “backward-word” part. Might as well do the same for lowercase in case this is hit accidentally.

(global-set-key (kbd "M-U") (lambda () (interactive) (upcase-word -1)))
(global-set-key (kbd "M-L") (lambda () (interactive) (downcase-word -1)))
  

Key chords are pretty cool, but take a little getting used to. At the moment I really just use them for some of the standard C-x commands, as well as the easy “smush” hj as a M-x alternative.

The key-chord package was already enabled as part of use-package so we just some preferences.

(key-chord-mode 1)
(setq key-chord-two-keys-delay 0.05)
(key-chord-define-global "xk" 'kill-current-buffer)
(key-chord-define-global "xb" 'switch-to-buffer)
(key-chord-define-global "xf" 'find-file)
(key-chord-define-global "hj" 'execute-extended-command)
(key-chord-define-global "xo" 'ace-window)
  

Zoom

Based on hydra docs and https://ericjmritz.wordpress.com/2015/10/14/some-personal-hydras-for-gnu-emacs/

A little interactive mode for text size, opened with double-tap on “z”. The letters correspond to the positions of +, - and 0 on my custom keyboard layers.

(use-package hydra
  :config
(key-chord-define-global
 "zz"
 (defhydra hydra-zoom ()
           "zoom"
           ("+" text-scale-increase "in")
           ("h" text-scale-increase "in")
           ("-" text-scale-decrease "out")
           ("k" text-scale-decrease "out")
           ("j" (text-scale-adjust 0) "reset")
           ("q" nil "quit" :color blue)))
  )

Backups

Backup files are ugly but occasionally useful. Keep them out of sight.

(setq backup-directory-alist
          `((".*" . ,"~/.emacs-backups")))
(setq auto-save-file-name-transforms
          `((".*" ,"~/.emacs-backups" t)))
  

Completion (menus)

Currently I favour ivy for completion.

Ivy itself provides a completing-read function which is setup by enabling ivy-mode. We also turn off ido-mode here, as it’s annoying when C-j does the wrong thing.

(use-package ivy
    :config
    (ivy-mode)
    (setq ivy-use-virtual-buffers t)
    (setq ivy-count-format "(%d/%d) ")
    (ido-mode -1))

;; Make sure ivy initialises and takes over completing-read
(add-hook 'elpaca-after-init-hook
          (lambda () (ivy-mode 1)))

Counsel sets up a bunch of ivy-completion functions. It also provides Swiper, a fancy isearch.

In most situations, counsel-buffer-or-recentf is equivalent or better than the usual switch-buffer.

(use-package swiper :ensure t :bind (("C-s" . swiper)))
(elpaca-wait)  ;; counsel doesn't install properly until swiper is in

(use-package counsel
  :ensure t
  :bind (("C-x b" . counsel-buffer-or-recentf))
  :config
  (counsel-mode)
  )

But, infuriatingly, it excludes Scratch, Messages etc. So to access those I use list-buffers and then shuffle around a bit. To save some keystrokes, let’s automate the usual workflow.

(defun list-buffers-and-search ()
  (interactive)
  (list-buffers)
  (select-window (get-buffer-window "*Buffer List*" 0))
  (swiper)
  )

(global-set-key (kbd "C-x C-b") 'list-buffers-and-search)

All-the-icons adds pretty pictures to everything.

Workstation bling

(use-package all-the-icons-ivy
  :config (all-the-icons-ivy-setup))

avy and ace-window

Avy provides some interesting commands for jumping around. An initial “search” brings up short keys which are used to jump to the desired match. Quicker than C-s when you are already looking at the work you want to jump to.

I don’t really use the default M-g bindings for much, let’s try this hydra from a blog post I’ve added “next-error” and “previous-error” as these are default bindings to n/p and are used with org-occurs as well as M-x compile, M-x grep. The hydra access is way more convenient than C-x `!

  (use-package avy
    :bind (("C-;" . avy-goto-word-or-subword-1)
           ("M-g" . hydra-avy/body)
           )
    :commands (hydra-avy/body)
    :config
    (use-package link-hint)
    (defhydra hydra-avy (:color blue)
      "avy-goto"
      ("g" avy-goto-line "line")
      ("c" avy-goto-char "char")
      ("C" avy-goto-char-2 "char-2")
      ("w" avy-goto-word-1 "word")
      ("s" avy-goto-subword-1 "subword")
      ("u" link-hint-open-link "open-URI")
      ("U" link-hint-copy-link "copy-URI")
      ;; Need to explicitly return to hydra or they escape...
      ("n" (progn (next-error) (hydra-avy/body)) "next-error")
      ("p" (progn (previous-error) (hydra-avy/body)) "previous-error")
)
    (global-set-key (kbd "M-g") 'hydra-avy/body)
    )

Ace-window is a less annoying way of changing window pane; if there are more then two, you are given a choice of numbers to enter.

(use-package ace-window
  :bind (("C-x o" . ace-window))
  )

ripgrep

The ripgrep package provides a really fast way to fly around fils searching for phrases. Just use C-s to bring up the transient interface. I addded a custom command for searching my org folder (including org-roam notes).

If rg-menu-transient-insert is run again it removes the item from the menu; so if it’s missing, try evaluating that expression again.

(use-package rg
  :init (rg-enable-default-bindings)
  :config
  (defun search-notes (regex) (interactive "sregex: ") (rg regex "*.org" "~/org"))
  (rg-menu-transient-insert "Search" "n" "notes" 'search-notes)
  )

Completion (content)

Electric pair mode is fine, I want this for pretty much everything other than lisp where paredit is better.

(electric-pair-mode 1)

Apparently all the cool kids have moved from auto-complete to Company these days. Should probably look into LSP at the same time. Maybe I’ll go without for a bit first…

Yasnippet

This is a really nice customisable completion system. For now I am avoiding external packages full of completions. Let’s keep it lean and personalised.

(use-package yasnippet
  :hook ((python-mode . yas-minor-mode))
  :config
  (yas-recompile-all)
  (yas-reload-all)
  )

Snippets

# -*- mode: snippet -*-
# name: inp
# key: <inp
# --
import numpy as np
$0
# -*- mode: snippet -*-
# name: iplt
# key: <iplt
# --
import matplotlib.pyplot as plt
# -*- mode: snippet -*-
# name: ipth
# key: <ipth
# --
from pathlib import Path

File management

I used to lean heavily on sunrise-commander, but haven’t used it much lately. (Partly because I’ve been enjoying nnn from the terminal!)

Still, it doesn’t install nicely with package.el or elpaca right now, so if I want to use it the answer is to clone to ~/.emacs.d/lisp. Maybe I can copy the bootstrap code from elpaca, but a better solution would be to figure out a way to elpaca it…

Shells

vterm is a more “native” shell from emacs; it should be fast and work better with interactive programs like btop.

(use-package vterm :ensure t)

Programming languages and syntax highlighting

Flycheck initial setup

Create a variable to collect flycheck mode hooks

(setq imrae/flycheck-modes nil)

utf-8

Stop freaking out if a file specifies UTF-8 encoding in capitals:

(define-coding-system-alias 'UTF-8 'utf-8)

Git

I use Magit for 90% of Git stuff, and CLI for some troubleshooting. MELPA version has some dependency problems (needs a very new Emacs maybe?) so pin a stable version from source.

(use-package transient
  ;; :ensure (:host github :repo "magit/transient" :tag "v0.4.3")
  )

(use-package magit
  ;; :ensure (:host github :repo "magit/magit" :tag "v3.3.0")
  :chords (("dg" . magit-status)))

We can highlight changes in the “gutters” with a few more packages. The “fringe” version is nicer as it doesn’t clash with e.g. linum mode – but it only works in graphical sessions.

(use-package git-gutter
  :hook (prog-mode . git-gutter-mode)
  :config (setq git-gutter:update-interval 0.1))
(use-package git-gutter-fringe
  :if  (display-graphic-p))

Elisp

Aggressive indent means you don’t have to think about indentation.

(use-package aggressive-indent
  :hook (emacs-lisp-mode . aggressive-indent-mode))

Paredit forces paired parentheses and provides commands to directly edit the Lisp abstract syntax tree.

(use-package paredit
  :hook ((emacs-lisp-mode . paredit-mode)
         (emacs-lisp-mode . (lambda () (electric-pair-local-mode -1)))
         ))

Highlight parens except when editing contents.

(defun show-parens-elisp ()
  (progn
    (setq show-paren-style 'mixed
          show-paren-delay 0.02
          show-paren-when-point-in-periphery t
          show-paren-when-point-inside-paren t)
    (show-paren-mode nil)))
(add-hook 'emacs-lisp-mode-hook 'show-parens-elisp)

Bash

I used to have a lot of code adding stuff to PATH especially on MacOS. Not clear how necessary that still is, let’s try dropping it for a bit.

If shellcheck and flycheck are available we can use these for checking Bash scripts.

(if (executable-find "shellcheck")
    (push 'sh-mode imrae/flycheck-modes))

Fish

I’ve been using the fish shell lately and really like it. Fish-mode gets syntax highlighting to work.

(if (executable-find "fish")
    (use-package fish-mode))

Snakemake

(use-package snakemake-mode :ensure t)

Python

This has been through a few iterations and I’ve had some frustrating times with Jedi, Elpygen and other packages. For now keep things simple-ish.

First some global things

;; Don't open annoying *python-help* buffer
(global-eldoc-mode -1)

;; Don't use Python2
(setq python-shell-interpreter "python3")
(setq py-python-command "python3")

Now set up python-mode

(use-package python-mode
  :config
  (setq py-smart-indentation t)
  (setq python-shell-interpreter "python3")
  (setq py-python-command "python3")
  (add-to-list 'auto-mode-alist
               '("\\.ipy$" . python-mode))
  (add-to-list 'auto-mode-alist
               '("\\.py$" . python-mode))
  (add-to-list 'interpreter-mode-alist '("python3" . python-mode))
  )

Fill-column indicator is mandatory, and now included in Emacs!

(add-hook 'python-mode-hook 'display-fill-column-indicator-mode)
(add-hook 'python-mode-hook (lambda () (set-fill-column 79)))

As are highlighted matching parentheses.

(defun show-parens-python ()
  (progn
    (setq show-paren-style "mixed"
          show-paren-delay 0.05
          show-paren-when-point-in-periphery t)
    (show-paren-mode nil)))
(add-hook 'python-mode-hook 'show-parens-python)

Line number is also useful

(add-hook 'python-mode-hook 'display-line-numbers-mode)

Eldoc is not (for now, anyway); clear this binding to use with zoxide instead.

(add-hook 'python-mode-hook (lambda () (keymap-unset python-mode-map "C-c C-f")))

If we have ruff, flycheck becomes viable

(if (executable-find "ruff")
    (push 'python-mode imrae/flycheck-modes))

Linting and formatters

It is useful to call a linter directly from the file. I used to rely on the flake8 function which seems to have vanished, but python-check is available with a useful shortcut (C-c C-v). It defaults to pylint, which I find a bit overkill so let’s use flake8 again. With an absolute path to ~/.local/bin, we can find linters installed with condax.

We also need to setup a colour terminal so that the special characters don’t look terrible.

(setq python-check-command "~/.local/bin/flake8")

(require 'ansi-color)
(defun colorize-compilation-buffer ()
  (ansi-color-apply-on-region compilation-filter-start (point)))
(add-hook 'compilation-filter-hook 'colorize-compilation-buffer)

Maybe a smarter setup could look for pre-commit or ruff and run those if available.

As I now interact with a couple of projects that favour Black, perhaps it would be worth looking into python-black.el which has the useful feature that it can use the black-macchiato package to reformat a selected region.

(use-package python-black
  :ensure t
  :init
  (setq
   python-black-command "~/.condax/black/bin/black"
   python-black-macchiato-command "~/.condax/black/bin/black-macchiato"
   ))

Scheme

Currently I’m playing with Chicken Scheme

(setq scheme-program-name "csi -:c")

Gibbs2

I made a little syntax-highlighting mode for Gibbs2. Not that I’ve used Gibbs2 for a while, but it doesn’t hurt to keep it around!

Set up file associations. (Hmm, .gin is also the GULP input extension, but I don’t have a package for that.)

(use-package gibbs2
  :ensure nil
  :config
  (add-to-list 'auto-mode-alist '("\\.gin\\'" . gibbs2-mode))
  (add-to-list 'auto-mode-alist '("\\.ing\\'" . gibbs2-mode))
  )

The code lives in ~/.emacs.d/lisp

;;;;; Custom colouring for GIBBS2 files (from guide at http://ergoemacs.org/emacs/elisp_syntax_coloring.html)

;; define keyword classes
(setq gibbs2-keywords
      '("title" "nat" "vfree" "mm" "nelectrons" "einf" "pressure" "endpressure" "volume"
        "temperature" "endtemperature" "freqg0" "interpolate" "activate" "printfreq"
        "printfreqs" "eoutput" "drhouse" "end" "phase" "endphase"))
(setq gibbs2-phase-keywords
      '("file" "u" "using" "Z" "poisson" "laue" "fit" "reg" "fix" "tmodel"
        "prefix" "elec" "nelec" "eec" "pvdata" "units" "interpolate"
        "fstep"))
;; create regex for each class
(setq gibbs2-keywords-regexp (regexp-opt gibbs2-keywords 'words))
(setq gibbs2-phase-keywords-regexp (regexp-opt gibbs2-phase-keywords 'words))
;; clear lists from memory
(setq gibbs2-keywords nil)
(setq gibbs2-phase-keywords nil)
;; set up font lock
(setq gibbs2-font-lock-keywords
      `((,gibbs2-keywords-regexp . font-lock-keyword-face)
        (,gibbs2-phase-keywords-regexp . font-lock-function-name-face)
        ))

;; syntax table
(defvar gibbs2-syntax-table nil "Syntax table for `gibbs2-mode'.")
(setq gibbs2-syntax-table
      (let ((synTable (make-syntax-table)))

        ;; bash style comment: “# …”
        (modify-syntax-entry ?# "< b" synTable)
        (modify-syntax-entry ?\n "> b" synTable)

        synTable))

;; define the mode
(define-derived-mode gibbs2-mode fundamental-mode
  "GIBBS2 mode"
  "Major mode for editing GIBBS2 input files"
  :syntax-table gibbs2-syntax-table

  (setq font-lock-defaults '((gibbs2-font-lock-keywords)))
  ;; clear memory
  (setq gibbs2-keywords-regexp nil)
  (setq gibbs2-phase-keywords-regexp nil)
)

(provide 'gibbs2)

flycheck

(if imrae/flycheck-modes
    (let ((hooks-list (mapcar (lambda (mode) (cons mode 'flycheck-mode)) imrae/flycheck-modes)))
      (use-package flycheck
        :hook hooks-list
        :bind (("M-n" . flycheck-next-error)
               ("M-p" . flycheck-previous-error)))
     )
    )

Pretty symbols

prettify-symbols does some cute replacement of character combinations with unicode glyphs. Still playing around with it, really. For now we put some config here but activate manually with prettify-symbols-mode; it seems a bit dangerous to enable globally.

(add-hook 'python-mode-hook
          (lambda ()
            (setq prettify-symbols-alist
                  '(("->" . ?→)
                    (">=" . ?≥)
                    ("<=" . ?≤)
                    ("lambda" . ))
                  )))

(add-hook 'emacs-lisp-mode-hook
          (lambda ()
            (setq prettify-symbols-alist
                  '(("lambda" . )))))

Remote servers

TRAMP is pretty great. I only use it with SSH, really, so default to that.

(setq tramp-default-method "ssh")
  

Backups

Backup files are ugly but occasionally useful. Keep them out of sight.

(setq backup-directory-alist
          `((".*" . ,"~/.emacs-backups")))
(setq auto-save-file-name-transforms
          `((".*" ,"~/.emacs-backups" t)))

Org

Org config is big, so we use noweb to manage the sections and pull them together.

(setq org-directory "~/org")

(use-package org
  :config
  <<org-agenda()>>
  <<org-babel>>
  <<org-hydra>>
  ;; Remove this annoying shortcut that takes priority over my zoxide search.
  (keymap-unset org-mode-map "C-c C-f")
  :bind
  <<org-bindings>>
  :hook
  (org-mode . org-indent-mode)
  :chords
  (("df" . hydra-org/body ))
)

Agenda

On my main workstation/laptop machines, use org-mode to manage agenda, TODO items etc. I fell off the task-management wagon a couple of years again, time for a fresh start.

I hard-code the location of org-agenda-files, then this can be written to point to the appropriate place on a given machine.

This bit of lisp checks if we are using this block, and in drags the configuration in from another blocks with noweb if so. Otherwise, we return nothing so that servers etc. can skip this code entirely in their init.el file.

(This does mean that the code will be passed as a Lisp AST so doesn’t get nicely formatted in the resulting file.)

(if-workstation
     '(progn
        <<org-agenda-setup>>)
  ""
    )
(defun set-if-exists (variable file &optional fallback)
 (set variable
   (if (file-exists-p file) file fallback)))

(set-if-exists 'org-agenda-files "~/org/agenda-files.txt" org-directory)

(setq org-log-done t
      org-export-backends '(latex md ascii html beamer)
      ;; some other interesting backends are icalendar, reveal
      ;; For more formats there is ox-pandoc package.
      org-enforce-todo-dependencies t
      org-agenda-dim-blocked-tasks 'invisible
      org-todo-keywords
      '((sequence "TODO(t)" "STARTED(s)" "WAITING(w)" "|" "DONE(d)")
        (sequence "RUNNING(r)" "|")
        (sequence "|" "DELEGATED(l)" "CANCELLED(c)"))
      )

;; This function is handy but I should assign a hotkey or something...
(defun org-agenda-cycle-blocked-visibility ()
    (interactive)
    (setq org-agenda-dim-blocked-tasks
          (cond
           ((eq org-agenda-dim-blocked-tasks 'invisible) nil)
           ((eq org-agenda-dim-blocked-tasks nil) t)
           (t 'invisible)
           )
          )
    (org-agenda-redo)
    (message "Blocked tasks %s"
             (cond
              ((eq org-agenda-dim-blocked-tasks 'invisible) "omitted")
              ((eq org-agenda-dim-blocked-tasks nil) "included")
              (t "dimmed"))))

Capture templates: quickly create TODO items and notes and file them. Hopefully notes.org will soon be replaced by org-roam stuff, but I need to get the basics cleaned up first.

(let ((todo-file (concat org-directory "/todo.org"))
      (notes-file (concat org-directory "/notes.org"))
      (shopping-file (concat org-directory "/shopping.org")))
  (setq org-capture-templates
        `(
          ;; Entries for work
          ("w" "work")
          ("ws" "todo list: reSearch" entry
           (file+headline ,todo-file "RESEARCH") "** TODO %? %^g %i \n" :empty-lines 1)
          ("wo" "Office work" entry
           (file+headline ,todo-file "OFFICE") "* TODO %? %^g %i \n" :empty-lines 1)
          ("wa" "Abins" entry
           (file+headline ,(concat org-directory "/roam/projects/20230605111955-Abins.org") "To-do")
           "** TODO %? %^G"
           :empty-lines 1)

          ("m" "Meta: emacs, org, etc." entry
           (file+headline ,todo-file "META") "* TODO %? %^g %i \n" :empty-lines 1)
          ("n" "General notes" entry
           (file ,notes-file) "")

          ;; Entries for LIFE keyword
          ("l" "life")
          ("lt" "Todo list" entry
           (file+headline ,todo-file "LIFE") "* TODO %? %^g %i \n")
          ("ls" "Shopping list" entry
           (file+headline ,shopping-file "Unfiled")
           "** DECIDING %^{item}")
          )))

Agenda views: lets me easily narrow focus to work or non-work and pick up dangling TODO items.

(setq org-agenda-custom-commands
      (quote (
              ("h" "\"home\": Agenda and unscheduled non-work TODOs"
               ((agenda "" nil) (tags-todo "-work-SCHEDULED={.+}" nil)) nil nil)
              ("H" "\"Home\": Non-work unscheduled TODOs" tags "-work-SCHEDULED={.+}" nil)
              ("w" "Unscheduled :work:" tags "work-SCHEDULED={.+}" nil)
              ("n" "Agenda and all TODOs" ((agenda "" nil) (alltodo "" nil)) nil))))

Org-babel

Org-babel allows for inline code execution as well as literate files such as this one. We need to explicitly enable the languages we want to allow.

(setq org-babel-load-languages (quote ((python . t)
                                       (emacs-lisp . t)
                                       (shell . t)
                                       (C . t)
                                       (scheme . t)
                                       ;; Not used lately, set t if so
                                       (ditaa . nil)
                                       (gnuplot . nil)
                                       (julia . nil)
                                       (haskell . nil)
                                       (awk . nil)
                                       (clojure . nil)
                                       (dot . nil)
)))

(org-babel-do-load-languages 'org-babel-load-languages org-babel-load-languages)

Make the source blocks work a bit more fluidly with a few more flags.

(setq org-src-tab-acts-natively t
      org-src-fontify-natively t)

Run code blocks on `C-c C-c` without confirmation.

(setq org-confirm-babel-evaluate nil)

Use Python3 because it isn’t 2015 any more

(setq org-babel-python-command "python3")

Appearance

Make a pretty arrow instead of ellipsis for expandable headlines.

(setq org-ellipsis "")
(add-hook 'org-mode-hook (lambda () (set-face-underline 'org-ellipsis nil)))

Use org-superstar for nice symbols (successor to org-bullets)

(use-package org-superstar
     :after org
     :hook (org-mode . (lambda () (org-superstar-mode 1)))
  )

Make LaTeX previews (enabled with C-c C-x C-l) a bit bigger than default

(setq  org-format-latex-options
       '(:foreground default :background default :scale 2.0
         :html-foreground "Black" :html-background "Transparent"
         :html-scale 1.0 :matchers ("begin" "$1" "$" "$$" "\\(" "\\[")))

Key bindings

  (("C-c l" . org-store-link)
   ("C-c a" . org-agenda)
   ("C-c t" . org-capture)
   ("C-c b" . org-ido-switchb)
   ("C-c C-f" . zoxide-find-file)  ;; overriding org-mode default!
)

Structure templates

We can’t do the tab-completion from < shortcut any more for creating blocks in org-mode; now we use C-c C-,. Ah well!

Create a template for python code as use that one the most. (Well, outside of these files…)

(add-to-list 'org-structure-template-alist '("p" . "src python"))

org-present

Org-present is a handy mode for simple presentations from an org outline

(use-package org-present
  :hook
  ((org-present-mode .
                     (lambda ()
                       (org-present-big)
                       (org-display-inline-images)
                       (org-present-hide-cursor)
                       (org-present-read-only)))
   (org-present-mode-quit .
                          (lambda ()
                            (org-present-small)
                            (org-remove-inline-images)
                            (org-present-show-cursor)
                            (org-present-read-write)))))

Roam

I’m still new to org-roam, best start with basics.

The capture template scheme is borrowed from the creator of roam’s personal scheme, but I have re-added the date metadata to filenames. https://jethrokuan.github.io/org-roam-guide/

Dailies setup is taken from https://systemcrafters.net/build-a-second-brain-in-emacs/keep-a-journal/

(setq org-roam-directory (concat org-directory "/roam"))
(make-directory org-roam-directory t)

(use-package org-roam
  :demand t
  :bind (("C-c n l" . org-roam-buffer-toggle)
         ("C-c n f" . org-roam-node-find)
         ("C-c n i" . org-roam-node-insert)
         ("C-c n c" . org-roam-capture)
         :map org-roam-dailies-map
         ("Y" . org-roam-dailies-capture-yesterday)
         ("T" . org-roam-dailies-capture-tomorrow))
  :bind-keymap
  ("C-c n d" . org-roam-dailies-map)
  ;; :after citar ;; Reference citation template relies on Citar
  :config
  (org-roam-setup)
  (org-roam-db-autosync-mode)
  (require 'org-roam-dailies)
  (setq org-roam-capture-templates
        '(("m" "main" plain
           "%?"
           :target (file+head "main/%<%Y%m%d%H%M%S>-${slug}.org"
                              "#+title: ${title}\n")
           :immediate-finish t
           :unnarrowed t)
          ("r" "reference" plain "%?"
           :target
           (file+head "reference/%<%Y%m%d%H%M%S>-${title}.org" "#+title: ${title}\n")
           :immediate-finish t
           :unnarrowed t)
          ("a" "article" plain "%?"
           :target
           (file+head "articles/%<%Y%m%d%H%M%S>-${title}.org" "#+title: ${title}\n#+filetags: :article:\n")
           :immediate-finish t
           :unnarrowed t)
          ("p" "project" plain "%?"
           :target
           (file+head "projects/%<%Y%m%d%H%M%S>-${title}.org" "#+title: ${title}\n#+filetags: :project:")
           :immediate-finish t
           :unnarrowed t)
          ("P" "project TODO" entry "** TODO %?"
           :target
           (file+head+olp
            "projects/%<%Y%m%d%H%M%S>-${title}.org" "#+title: ${title}\n#+filetags: :project:"
            ("To-do"))
           :immediate-finish t
           :unnarrowed t)
          ("n" "people" plain "%?"
           :target
           (file+head "people/%<%Y%m%d%H%M%S>-${title}.org" "#+title: ${title}\n#+filetags: :people:\n")
           :immediate-finish t
           :unnarrowed t)
          ))

  (cl-defmethod org-roam-node-type ((node org-roam-node))
    "Return the TYPE of NODE."
    (condition-case nil
        (file-name-nondirectory
         (directory-file-name
          (file-name-directory
           (file-relative-name (org-roam-node-file node) org-roam-directory))))
      (error "")))

  (setq org-roam-node-display-template
        (concat "${type:15} ${title:*} " (propertize "${tags:10}" 'face 'org-tag)))
  )

Navigation hydra

From https://ericjmritz.wordpress.com/2015/10/14/some-personal-hydras-for-gnu-emacs

This encourages some features I don’t use much: org-goto, and then in turn org-occur.

(defhydra hydra-org (:color red :columns 3)
  "Org Mode Movements"
  ("n" outline-next-visible-heading "next heading")
  ("p" outline-previous-visible-heading "prev heading")
  ("N" org-forward-heading-same-level "next heading at same level")
  ("P" org-backward-heading-same-level "prev heading at same level")
  ("u" outline-up-heading "up heading")
  ("g" org-goto "goto" :exit t))

Citation

Basics

Use citar to insert citations from system-wide bibtex file

(use-package citar
  :config
  (setq org-cite-insert-processor 'citar)
  (setq org-cite-follow-processor 'citar)
  (setq org-cite-activate-processor 'citar)
  (setq citar-bibliography '("~/braindump/zotero_lib.bib"))
  (require 'citar-format)
  (require 'citar)
  (require 'citar-org)
  )

Zotero links

Set up zotero links to open in browser. Confusingly and annoyingly, I then have to go to Firefox, click the address bar and hit enter. That isn’t necessary for other link types?

On linux you may also need to run xdg-mime default zotero.desktop x-scheme-handler/zotero

(add-hook 'elpaca-after-init-hook
          (lambda ()
  (org-link-set-parameters "zotero" :follow
                       (lambda (zpath)
                         (browse-url
                          (format "zotero:%s" zpath))))
            )
          )

Now we can create a function that makes an org-roam node and links it to Zotero with a select link:

(defun imrae/org-roam-node-from-cite (key)
  (interactive (list (citar-select-ref)))
  (let ((title (citar-format--entry "${author editor} :: ${title}"
                                    key
                                    ))
        (zoteroselect (citar-format--entry "[[${zoteroselect}][@${=key=}]]" key)))
    (message zoteroselect)
    (org-roam-capture-
     :keys "r"
     :templates
     `(("r" "reference" plain "%?" :if-new
        (file+head "reference/${citekey}.org\n"
                   ,(concat
                     "    :PROPERTIES:\n"
                     "    :ROAM_REFS: " zoteroselect "\n"
                     "    :END:\n"
                     "    #+title: ${title}\n"))
        :immediate-finish t
        :unnarrowed t))
     :info (list :citekey key)
     :node (org-roam-node-create :title title)
     :props '(:finalize find-file))
    ))

This relies on a “zoteroselect” key being present. I should really code in some fallback logic in case there isn’t…

Anyway, to get that key we have to use the “better bibtex” Zotero plugin to export our library, and add this code to the “postscript”

if (Translator.BetterBibTeX) {
tex.add({ name: 'zoteroselect', value: zotero.uri.replace(/http:\/\/zotero.org\/users\/\d+\/items\/(\w+)/, 'zotero://select/library/items/$1')});
}

Cleanup

If use-package configs don’t seem to be launching, try putting this code above a suspicious part of the config and see if everything above that point comes back to life…

;; (elpaca-process-queues)