~kl/.emacs.d

my Org-based Emacs config
use JetBrains Mono and zenburn on Linux
temp fix for init.org
fix init.el

refs

main
browse  log 

clone

read-only
https://git.sr.ht/~kl/.emacs.d
read/write
git@git.sr.ht:~kl/.emacs.d

You can also use your local clone with git send-email.

#An Org-based Emacs configuration

#+BABEL: :cache yes
#+PROPERTY: header-args :tangle yes

#About

This is my Emacs configuration, written in Org mode, which is mostly so I can look through it and remember how all of this works. Also, if it's going to be public, it might as well be well-documented. A lot of this setup (at least in spirit, and literally in the case of the tangle-and-compile bits) is borrowed from Lars Tveito's Emacs/Org setup.

#Meta

All changes to the configuration should be done in init.org{.verbatim}, not in init.el{.verbatim}. Any changes in the init.el{.verbatim} will be overwritten by saving init.org{.verbatim}. The init.el{.verbatim} in this repo should not be tracked by git, and is replaced the first time Emacs is started (assuming it has been renamed to ~/.emacs.d{.verbatim}).

Emacs can't load .org{.verbatim}-files directly, but org-mode{.verbatim} provides functions to extract the code blocks and write them to a file. There are multiple ways of handling this; like suggested by this StackOverflow post, one could just use org-babel-load-file{.verbatim}, but I had problems with byte-compilation. Previously I tracked both the org.{.verbatim}- and el.{.verbatim}-files, but the git commits got a little messy. So here is a new approach.

When this configuration is loaded for the first time, the init.el is the file that is loaded. It looks like this:

;; This file replaces itself with the actual configuration at first run.

;; We can't tangle without org!
(require 'org)
;; Open the configuration
(find-file (concat user-emacs-directory "init.org"))
;; tangle it
(org-babel-tangle)
;; load it
(load-file (concat user-emacs-directory "init.el"))
;; finally byte-compile it
(byte-compile-file (concat user-emacs-directory "init.el"))

It tangles the org-file, so that this file is overwritten with the actual configuration.

There is no reason to track the init.el{.verbatim} that is generated; by running the following command git{.verbatim} will not bother tracking it:

git update-index --assume-unchanged init.el

If one wishes to make changes to the repo-version of init.el{.verbatim} start tracking again with:

git update-index --no-assume-unchanged init.el

The init.el{.verbatim} should (after the first run) mirror the source blocks in the init.org{.verbatim}. We can use C-c C-v t{.verbatim} to run org-babel-tangle{.verbatim}, which extracts the code blocks from the current file into a source-specific file (in this case a .el{.verbatim}-file).

To avoid doing this each time a change is made we can add a function to the after-save-hook{.verbatim} ensuring to always tangle and byte-compile the org{.verbatim}-document after changes.

(defun tangle-init ()
  "If the current buffer is init.org the code-blocks are
tangled, and the tangled file is compiled."
  (when (equal (buffer-file-name)
               (expand-file-name (concat user-emacs-directory "init.org")))
    ;; Avoid running hooks when tangling.
    (let ((prog-mode-hook nil))
      (org-babel-tangle)
      (byte-compile-file (concat user-emacs-directory "init.el")))))

(add-hook 'after-save-hook 'tangle-init)

#Packages

To load downloaded packages we need to initialize package{.verbatim}.

(require 'cl-lib)
(require 'package)
(if (< emacs-major-version 27) (package-initialize))

Packages can be fetched from different mirrors; melpa is the largest archive and is well maintained.

(setq package-archives
      '(("gnu" . "http://elpa.gnu.org/packages/")
        ("MELPA" . "http://melpa.org/packages/")))

The configuration assumes that the packages listed below are installed. Missing packages will be downloaded and installed on startup.

(let* ((packages '(
      use-package          ; Lazy loading and requirements for Emacs packages
      anti-zenburn-theme   ; anti-zenburn color scheme
      auctex               ; sophisticated document creation via LaTeX
      auto-compile         ; automatically compile Emacs Lisp libraries
      counsel              ; find things in the filesystem using ivy
      csharp-mode          ; Major mode for editing C# in Emacs
      csv-mode             ; For working with .csv files intelligently
      expand-region        ; Increase selected region by semantic units
      general              ; "convenience wrappers for keybindings"
      ivy                  ; abo-abo completion framework
      magit                ; an Emacs git porcelain
      markdown-mode        ; Major mode for editing Markdown files
      org                  ; Outline-based notes manager
      org-bullets          ; Fancy UTF-8 bullets for org
      ox-pandoc            ; org exporter for pandoc
      olivetti             ; Minor mode for a nice writing environment.
      pandoc-mode          ; Minor mode for interacting with pandoc
      paredit              ; Minor mode for Lisp editing goodness
      plan9-theme          ; Plan 9-inspired light theme, nice to have
      request              ; something URL related
      request-deferred     ; not really sure what this does
      swiper               ; find things in an Emacs buffer using ivy
      toml-mode            ; mode for working with TOML files
      wc-goal-mode         ; Set goal wordcounts (minor mode)
      which-key            ; show available keybindings in popup
      zenburn-theme))      ; can't have anti-zenburn without zenburn, right?

     ;; Remove all packages already installed
  (packages (cl-remove-if 'package-installed-p packages)))
     (when packages
       (ignore-errors (package-refresh-contents)
         (mapc 'package-install packages)
         ;; This package is only relevant for Mac OS X; it fixes some $PATH stuff.
         (when (memq window-system '(mac ns))
           (package-install 'exec-path-from-shell)))))

  (require 'use-package) ;; make sure this is already loaded.

#Configurations

#Defaults

Always start the server so that emacsclient{.verbatim} works from the command line.

(server-start) ;; start the server on startup

Answering yes and no to each question from Emacs can be tedious, a single y or n will suffice.

(fset 'yes-or-no-p 'y-or-n-p)

To avoid file system clutter we put all auto saved files in a single directory. We also set up our backup-saving parameters here.

(defvar --backup-directory (concat user-emacs-directory "backups"))
(if (not (file-exists-p --backup-directory))
        (make-directory --backup-directory t))
(setq backup-directory-alist `(("." . ,--backup-directory)))
(setq make-backup-files t               ; backup of a file the first time it is saved.
      backup-by-copying t               ; don't clobber symlinks
      version-control t                 ; version numbers for backup files
      delete-old-versions t             ; delete excess backup files silently
      delete-by-moving-to-trash t
      kept-old-versions 6               ; oldest versions to keep when a new numbered backup is made (default: 2)
      kept-new-versions 9               ; newest versions to keep when a new numbered backup is made (default: 2)
      auto-save-default t               ; auto-save every buffer that visits a file
      auto-save-timeout 20              ; number of seconds idle time before auto-save (default: 30)
      auto-save-interval 200            ; number of keystrokes between auto-saves (default: 300)
      )

These are some other "sane" defaults:

Silently delete excess backup versions of files:

(setq delete-old-versions -1)

Use UTF-8 for everything by default (if this isn't already happening)

(setq coding-system-for-read 'utf-8)
(setq coding-system-for-write 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-language-environment 'utf-8)
(set-keyboard-coding-system 'utf-8)
(prefer-coding-system 'utf-8)
(setq locale-coding-system 'utf-8)
(set-default-coding-systems 'utf-8)
;; Treat clipboard input as UTF-8 string first; compound text next, etc.
(setq x-select-request-type '(UTF8_STRING COMPOUND_TEXT TEXT STRING))

Garbage collect on focus-out, which should make Emacs feel a bit snappier, especially on Windows:

(add-function :after after-focus-change-function #'garbage-collect)

;; this method is deprecated as of Emacs 27.1, but saving the code for posterity:
;; (add-hook 'focus-out-hook #'garbage-collect)

We also want to empty the initial *scratch*{.verbatim} buffer.

(setq-default initial-scratch-message "")

Finally, we want to confirm before exiting Emacs, just in case.

(setq-default confirm-kill-emacs 'yes-or-no-p)

Here are some other useful defaults borrowed from the default setup for emax64

(setq-default
 ad-redefinition-action 'accept                   ; Silence warnings for redefinition
 cursor-in-non-selected-windows t                 ; Hide the cursor in inactive windows
 delete-by-moving-to-trash t                      ; Delete files to trash
 display-time-default-load-average nil            ; Don't display load average
 display-time-format "%H:%M"                      ; Format the time string
 fill-column 80                                   ; Set width for automatic line breaks
 help-window-select t                             ; Focus new help windows when opened
 indent-tabs-mode nil                             ; Stop using tabs to indent
 inhibit-startup-screen t                         ; Disable start-up screen
 initial-scratch-message ""                       ; Empty the initial *scratch* buffer
 left-margin-width 1 right-margin-width 1         ; Add left and right margins
 mode-require-final-newline 'visit                ; Add a newline at EOF on visit
 mouse-yank-at-point t                            ; Yank at point rather than pointer
 ns-use-srgb-colorspace nil                       ; Don't use sRGB colors
 recenter-positions '(5 top bottom)               ; Set re-centering positions
 redisplay-dont-pause t                           ; don't pause display on input
 debug-on-error t
 jit-lock-defer-time 0
 frame-resize-pixelwise t
 fast-but-imprecise-scrolling t
 scroll-conservatively 10000                      ; Always scroll by one line
 scroll-margin 1                                  ; scroll N lines to screen edge
 scroll-step 1                                    ; keyboard scroll one line at a time
 scroll-preserve-screen-position 1
 select-enable-clipboard t                        ; Merge system's and Emacs' clipboard
 sentence-end-double-space nil                    ; End a sentence after a dot and a space
 show-trailing-whitespace nil                     ; Display trailing whitespaces
 split-height-threshold nil                       ; Disable vertical window splitting
 split-width-threshold nil                        ; Disable horizontal window splitting
 tab-width 4                                      ; Set width for tabs
 uniquify-buffer-name-style 'forward              ; Uniquify buffer names
 window-combination-resize t                      ; Resize windows proportionally
 x-stretch-cursor t)                              ; Stretch cursor to the glyph width
(delete-selection-mode)                           ; Replace region when inserting text
(setq line-number-mode t)                         ; Enable line numbers in the mode-line
(setq column-number-mode t)                       ; Enable column numbers in the mode-line
(size-indication-mode 1)                          ; Enable size status in the mode-line
(display-time-mode)                               ; Enable time in the mode-line
(fringe-mode 0)                                   ; Hide fringes
(global-hl-line-mode)                             ; Hightlight current line
(show-paren-mode t)

#Customized key bindings

Here are some keybindings I want to use, provided by general.el{.verbatim}.

  • Map the Meta key to also be invoked by C-x C-m (and C-c C-m) so that you don't have to reach all over the place for them.
  • Map C-w to backward-kill-word{.verbatim} for easier fixing of typos (which means less reaching for backspace) and then map kill-region{.verbatim} to C-x C-k so that we can still get to it easily.

I want to start creating new keybindings using general{.verbatim} instead of this old-school way, so I will make sure that it's installed using use-package{.verbatim}, and then bind my keys:

(use-package general :ensure t
  :config 
  (general-define-key 
  "C-w" 'backward-kill-word 
  "\C-x\C-k" 'kill-region)

  (general-define-key
  ;; use M-o to switch windows
  "M-o" 'other-window)

  (general-define-key
  ;; replace default keybindings 
  "C-s" 'swiper)             ; search for string in current buffer

(general-define-key
 :prefix "C-c"
 ;; bind to simple key press
  "b"   'ivy-switch-buffer  ; change buffer, chose using ivy
  "/"   'counsel-git-grep   ; find string in git project
  ;; bind to double key press
  "f"   '(:ignore t :which-key "files")
  "ff"  'counsel-find-file
  "fr"  'counsel-recentf
  "p"   '(:ignore t :which-key "project")
  "pf"  '(counsel-git :which-key "find file in git dir")
  ))

(use-package which-key :ensure t)

#Theme, Fonts, and Appearance

#Theme

After experimenting with using the borland theme, I switched back to zenburn{.verbatim}. On the Mac I feel like the anti-zenburn{.verbatim} light theme works better. Since we have counsel{.verbatim} installed already, all it takes to switch themes is M-x counsel-load-theme{.verbatim}.

In the terminal (including on Windows Subsystem for Linux), I like to use wheatgrass{.verbatim} instead because it works much better there.

(if (window-system)
    (cond
      ((eq system-type 'darwin)(load-theme 'anti-zenburn t nil))
      (t (load-theme 'zenburn t nil))) 
      (load-theme 'wheatgrass t nil))

#Fonts

On Mac systems, I like to use IBM Plex Mono, and on all the others I use JetBrains Mono. It needs to be a different size depending on what OS I'm on.

(cond
  ((eq system-type 'darwin)(set-face-attribute 'default nil :font "IBM Plex Mono" :height 175))
  ((eq system-type 'berkeley-unix)(set-face-attribute 'default nil :font "JetBrains Mono" :height 85))
  (t (set-face-attribute 'default nil :font "JetBrains Mono" :height 120))) ; default is Windows or Linux

#GUI Appearance

We want to suppress the (hideous) Emacs splash screen, and also hide the icon-based toolbar, but not the menubar.

(when window-system
   (setq inhibit-startup-message t) ;; disable the startup screen
   (tool-bar-mode 0)                ;; disable the tool bar
   (tooltip-mode 0))                ;; disable the tooltips

We also want to maximize by default when the GUI version opens:

(add-hook 'window-setup-hook 'toggle-frame-maximized t)

#Load custom.el{.verbatim}

We want to take the customization interface stuff from Emacs and load it, but keep it out of this file since adding stuff to this file might cause unpredictable behavior. We want Emacs to check for a custom.el{.verbatim} file in the Emacs directory and then load it if it exists.

(setq-default custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (file-exists-p custom-file)
  (load custom-file))

#Load local.el{.verbatim}

We also want to allow for a local.el{.verbatim} file that isn't tracked by version control, to allow for settings like API keys and such to be loaded without having to be stored in a public place, and to allow machine-specific settings to be stored somewhere out of the Git repo.

(let ((local.el (expand-file-name "local.el" "~/.emacs.d/")))
  (when (file-exists-p local.el)
    (load local.el)))

#Mode-specific

#counsel

Use counsel{.verbatim} for finding stuff, and let M-x use it.

(global-set-key (kbd "M-x") 'counsel-M-x)             ;; Give M-x counsel features
(global-set-key (kbd "C-x C-f") 'counsel-find-file)   ;; Give C-x C-f counsel features
(global-set-key (kbd "C-c g") 'counsel-git)          ;; Use C-c g for counsel-git globally

#Markdown-mode

This is specific stuff for markdown-mode{.verbatim} that makes things better.

We want to load pandoc-mode{.verbatim} and wc-goal-mode{.verbatim} every time we load markdown-mode{.verbatim} so that we can export to everything and also keep track of word counts.

;; set up markdown-mode with the proper minor modes
(add-hook 'markdown-mode-hook 'pandoc-mode)
(add-hook 'markdown-mode-hook 'wc-goal-mode)

Whenever a file ends in text{.verbatim}, markdown{.verbatim}, md{.verbatim}, or mmd{.verbatim}, automatically load markdown-mode{.verbatim}.

;; autoload these filetypes as markdown-mode
(autoload 'markdown-mode "markdown-mode"
   "Major mode for editing Markdown files" t)
(add-to-list 'auto-mode-alist '("\\.text\\'" . markdown-mode))
(add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode))
(add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode))
(add-to-list 'auto-mode-alist '("\\.mmd\\'" . markdown-mode))

Set up a special keyboard shortcut (that only works on the Mac, but for some reason I'm defining it everywhere) so that C-c m opens the current Markdown file in Marked.app for previewing.

;; C-c m opens the current file in Marked.app
(defun markdown-preview-file ()
   "run Marked on the current file and revert the buffer"
   (interactive)
   (shell-command
    (format "open -a /Applications/Marked\\ 2.app %s"
            (shell-quote-argument (buffer-file-name))))
   )
 (global-set-key "\C-cm" 'markdown-preview-file)

#Visual-line-mode

I want to automatically use visual-line-mode{.verbatim} if I'm in a mode that is derived from text-mode{.verbatim} or from org-mode{.verbatim}.

;; use visual line mode while in anything derived from Text mode or Org
(add-hook 'text-mode-hook 'visual-line-mode)
(add-hook 'org-mode-hook (lambda () (visual-line-mode 1)))

#Org mode

#File associations

Automatically load Org for org{.verbatim} files.

(use-package org
  :mode ("\\.org$" . org-mode)
  :config (setq org-log-done t)) ; don't remember what this does

#org-refile{.verbatim} settings

We want to allow creation of new headers when refiling current headers.

(setq org-refile-allow-creating-parent-nodes 'confirm)

We also want to be able refile to any of the first three levels of an outline, on the local file and on any of the files in the org-agenda-files{.verbatim} list (which we're currently not populating with anything, but might in the future).

(setq org-refile-targets `((nil :maxlevel . 3) (org-agenda-files :maxlevel . 3)))

#org-bullets

Make sure the fancy org-bullets{.verbatim} is installed for fancy UTF-8 bullets in Org by adding it to the org-mode-hook{.verbatim}.

(add-hook 'org-mode-hook (lambda () (org-bullets-mode 1)))

#ox-pandoc

Set everything to be a standalone pandoc{.verbatim} export. Typically on one of my systems luatex{.verbatim} is installed, so we want to use that for PDF output from Org mode.

(setq org-pandoc-options '((standalone . t)))
(setq org-pandoc-options-for-latex-pdf '((latex-engine . "luatex")))

#olivetti-mode

We want to add a hook for olivetti-mode{.verbatim} so that whenever we're in Org, we'll automatically start olivetti-mode{.verbatim}. I usually end up launching it anyway.

(add-hook 'org-mode-hook (olivetti-mode))

#nXML mode

A function borrowed from someone else borrowing it from Benjamin Ferrari for pretty-printing a region of XML being edited with nxml-mode{.verbatim}.

(defun bf-pretty-print-xml-region (begin end)
  "Pretty format XML markup in region. You need to have nxml-mode
http://www.emacswiki.org/cgi-bin/wiki/NxmlMode installed to do
this.  The function inserts linebreaks to separate tags that have
nothing but whitespace between them.  It then indents the markup
by using nxml's indentation rules."
  (interactive "r")
  (save-excursion
      (nxml-mode)
      (goto-char begin)
      (while (search-forward-regexp "\>[ \\t]*\<" nil t) 
        (backward-char) (insert "\n"))
      (indent-region begin end))
    (message "Ah, much better!"))

#wc-goal-mode

Set the display for wc-goal-mode{.verbatim} in the modeline.

;; Set wc-goal-mode modeline display
(setq wc-goal-modeline-format "WC[%w;%tw/%gw]")

#csv-mode

We want csv-mode{.verbatim} to automatically load when we open a file with the .csv extension.

;; use use-package to associate .csv files with csv-mode
(use-package csv-mode
  :mode ("\\.csv\\'" . csv-mode))

#AUCTeX

Since I do a lot of writing using the novel{.verbatim} document class, I want AUCTeX to always use LuaLaTeX as the TeX engine.

(setq TeX-engine 'luatex)

#Platform-specific configurations

#Windows configurations

Windows being what it is, there are configurations we need to make to make Emacs (the emax distribution for W64) behave in a Windows environment.

#Root directories

We need to set all the right root directories, and then make sure emax{.verbatim} knows that we're supposed to be using UTF-8 everywhere:

(when (string-equal system-type "windows-nt") ; test whether we're on Windows

  ;; set variables for all of the various paths
  (defvar emax-root (concat (expand-file-name "~") "/emax"))
  (defvar emax-bin (concat emax-root "/bin"))
  (defvar emax-bin64 (concat emax-root "/bin64"))
  (defvar emax-mingw64 (concat emax-root "/mingw64/bin"))
  (defvar emax-lisp (concat emax-root "/lisp"))

  ;; add the various paths to the $PATH (get it?)
  (setq exec-path (cons emax-bin exec-path))
  (setenv "PATH" (concat emax-bin ";" (getenv "PATH")))

  (setq exec-path (cons emax-bin64 exec-path))
  (setenv "PATH" (concat emax-bin64 ";" (getenv "PATH")))

  (setq exec-path (cons emax-mingw64 exec-path))
  (setenv "PATH" (concat emax-mingw64 ";" (getenv "PATH")))

  (setenv "PATH" (concat "C:\\msys64\\usr\\bin;C:\\msys64\\mingw64\\bin;" (getenv "PATH"))))

The other important thing, since we're on Windows, where things get weird sometimes, is to make sure Emacs always knows everything should be UTF-8 instead of some weird Windows thing.

(when (string-equal system-type "windows-nt") ; test whether we're on Windows
  ;; make sure everything is expecting UTF-8
  (set-language-environment 'utf-8)
  (setq locale-coding-system 'utf-8)
  (set-default-coding-systems 'utf-8)
  (set-terminal-coding-system 'utf-8)
  (prefer-coding-system 'utf-8))

#Garbage collection fixes

There's an issue with garbage collection on Windows as of Emacs 25 that can cause org-mode{.verbatim} and visual-line-mode{.verbatim} to run very slowly when moving from line to line. This is a hack to make the GC run less often on Windows than it would by default (not until 500MB have been allocated, and then only when Emacs is idle for 5s).

(Credit for this fix goes to reddit user /u/DrSpotter.)

(when (string-equal system-type "windows-nt") ; test whether we're on Windows
  (setq gc-cons-threshold (* 511 1024 1024)) ; put off GC until 500MB of allocation
  (setq gc-cons-percentage 0.5)
  (run-with-idle-timer 5 t #'garbage-collect) ; GC only when idle for 5s
  (setq garbage-collection-messages t))

#Other Windows slowness mitigations

From this post:

(setq-default w32-pipe-read-delay 0
  inhibit-compacting-font-caches t
  bidi-display-reordering nil)

#macOS configurations

These are the bits of the configuration which are only necessary on macOS. exec-path-from-shell{.verbatim} includes environment variables from the shell (things like $PATH{.verbatim} and such).

;; code to run only if we're on an OS X system
(when (memq window-system '(mac ns))
  ;; use GNU coreutils for dired (installed from Homebrew)
  (setq insert-directory-program (executable-find "gls")) 
  ;; use Command instead of Option as Meta
  ; (setq mac-command-modifier 'meta) 
  ;; don't do anything with Option
  ; (setq mac-option-modifier nil))
  )

#License

My Emacs configurations written in Org mode. Format based on the Emacs configuration of Lars Tveito.

Copyright (c) 2015-2023 Kevin Lipe

Copyright (c) 2013-2015 Lars Tveito

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.