My GNU Emacs Configuration

Iā€™ve been using Emacs for a long time, and have kept my configuration updated while expanding it as Emacs improved.

contents

introduction

Iā€™ve been using GNU Emacs for most of my adult life. Iā€™m nowhere near being any sort of guru, and Iā€™ll never be a saint in the Church of Emacs, but I manage well enough. I use Emacs mainly for the following purposes:

I use Emacs on GNU/Linux, BSD, and macOS.

This is my configuration. Its source is an Org Mode file from which I extract my config using org-babel. I use the same file to generate this web page.

initialization

This section handles low-level settings and dependencies needed in other sections.

package mangler

I want to be able to pull packages from the main Emacs repositories, and have the latest Org Mode when I decide to use Org (like for this config file). I also want to use use-package to install and configure packages.

(require 'package) 
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
                         ("org" . "https://orgmode.org/elpa/")
                         ("elpa" . "https://elpa.gnu.org/packages/")))
(package-initialize)
(unless package-archive-contents
  (package-refresh-contents))

use-package

If we donā€™t have the use-package macro installed, grab it, load it, and tweak some of its settings.

(unless (package-installed-p 'use-package)
  (package-install 'use-package))
(require 'use-package)
(setq use-package-always-ensure t)

I want use-package to always use the ensure function to guarantee that a package is installed and loaded, lest I forget to specify it for a given package.

diminish

I also want the diminish package because my modeline is intolerably cluttered.

(unless (package-installed-p 'diminish)
  (package-install 'diminish))
(require 'diminish)

custom variables

These are some variables I use elsewhere in my configuration. They can also be modified through the ā€œCustomizeā€ interface because I defined them with defcustom instead of defvar.

(defcustom mg-local-config
  "~/.emacs.d/local.el"
  "location of a local config with custom settings")
(defcustom mg-name
  "Matthew Graybosch"
  "redundant custom variable for setting user name")
(defcustom mg-email
  "contact@starbreaker.org"
  "redundant custom variable for setting mail address")
(defcustom mg-default-font
  ""
  "custom variable for default/fixed-width font name")
(defcustom mg-default-variable-font
  ""
  "custom variable for variable-width font name")
(defcustom mg-default-font-size
  200
  "custom variable for default/fixed-width font size")
(defcustom mg-default-variable-font-size
  200
  "custom variable for variable-width font size")
(defcustom mg-dired-path
  "/tmp"
  "custom variable for default dired path")
(defcustom mg-shell
  "sh"
  "custom variable for vterm shell")

local settings

I may want to customize certain settings for a particular machine, like font size.

(when (file-exists-p mg-local-config)
  (load-file mg-local-config))

putting customizations in a separate file

Apparently the Emacs package mangler concatenates customizations into the main init file. That doesnā€™t strike me as desirable behavior, but we can change it.

(setq custom-file (concat user-emacs-directory "custom.el"))
(load custom-file t)

personal information

I want Emacs to know my name and email address. Among other things, this is useful with the magit package. Iā€™m using variables Iā€™ve set earlier on.

(setq user-full-name mg-name
      user-mail-address mg-email)

custom functions

Defining functions here for use elsewhere.

mg-text-scale-reset

A little function to reset text scaling after adjusting it

(defun mg-text-scale-reset ()
  "reset text size after adjusting scaling"
  (interactive)
  (text-scale-set 0))

mg-emms-track-description

I cribbed this function from EmacsWiki. It was originally written by Filipp Gunbin. I decided I donā€™t need the year display.

(defun mg-emms-track-description (track)
  "Return a somewhat nice track description."
  (let ((artist (emms-track-get track 'info-artist))
        (year (emms-track-get track 'info-year))
        (album (emms-track-get track 'info-album))
        (tracknumber (emms-track-get track 'info-tracknumber))
        (title (emms-track-get track 'info-title)))
    (cond
     ((or artist title)
      (concat (if (> (length artist) 0) artist "Unknown artist") " - "
              (if (> (length album) 0) album "Unknown album") " - "
              (if (> (length tracknumber) 0)
                  (format "%02d" (string-to-number tracknumber))
                "XX") " - "
                (if (> (length title) 0) title "Unknown title")))
     (t
      (emms-track-simple-description track)))))

mg-recent-file-open

I cribbed this from Mastering Emacs, but I donā€™t really need it when I could just use counsel-recentf instead.

(defun mg-recent-file-open ()
"Use `ido-completing-read' to \\[find-file] a recent file"
(interactive)
(if (find-file (ido-completing-read "Find recent file: " recentf-list))
    (message "Opening file...")
  (message "Aborting")))

key bindings

Some custom key bindings here. Not necessarily Stallman-approved, but thatā€™s how Free Software works. šŸ’©

wait; this isnā€™t vi

C-g is the cancel key in Emacs, but if Iā€™m drunk I might forget and hit ESC instead. This binding makes that key cancel as well. Donā€™t use this if youā€™re used to using ESC as your meta key because you use Emacs in a terminal.

(global-set-key (kbd "<escape>") 'keyboard-escape-quit)

get a word count for the current buffer/region

When Iā€™m writing text, it helps to be able to easily get a word count. So letā€™s bind a key that will give me a count for the current buffer/region.

(bind-key "C-c w" 'count-words)

joining lines

I might want to join ā€œfilledā€ paragraphs into a single line for ease of editing.

(bind-key "C-c j" 'join-line)

text scaling

Depending on how Iā€™m feeling, and whether or not my wife has her glasses on when looking over my shoulder, I may need to enlarge the text on my screen.

(bind-key "M-+" 'text-scale-increase)
(bind-key "M-=" 'text-scale-increase)
(bind-key "M--" 'text-scale-decrease)
(bind-key "M-0" 'mg-text-scale-reset)

replace find-file-read-only with a recentf function

I canā€™t remember the last time I bothered opening a file in read-only mode, but thereā€™s always M-x find-file-read-only if I really need it.

(global-set-key (kbd "C-x C-r") 'counsel-recentf)

custom windmove bindings

The default windmove bindings use C-<up>, C-<down>, etc. and donā€™t necessarily play nicely with general movement or Org mode. So Iā€™m remapping that functionality to the C-c prefix, which is traditionally used for custom keybindings.

(global-set-key (kbd "C-c <up>") 'windmove-up)
(global-set-key (kbd "C-c <down>") 'windmove-down)
(global-set-key (kbd "C-c <left>") 'windmove-left)
(global-set-key (kbd "C-c <right>") 'windmove-right)

package installation

Iā€™m installing a bunch of additional packages, so letā€™s try to keep them in one section.

projectile

Not sure why I didnā€™t get around to installing this before, but whatever. This should help me deal with files in repositories more easily. Weā€™re also going to install packages for fd and ripgrep and make sure Emacs knows about them.

(use-package projectile
  :ensure t
  :init
  (setq projectile-project-search-path '("~/texts/" ("~/git" . 1)))
  (projectile-mode +1)
  :bind (:map projectile-mode-map
              ("s-p" . projectile-command-map)
              ("C-c p" . projectile-command-map)))

(use-package ripgrep
  :ensure t)

(use-package projectile-ripgrep
  :ensure t)

Youā€™ll also want to install fd-find and ripgrep via your operating systemā€™s package mangler to avoid dependency errors.

circadian

This will let me use a light theme during the day and a dark theme at night, regardless of OS. I would just to give it my ICBM address.

(use-package circadian
  :diminish
  :config
  (setq calendar-latitude 40.28)
  (setq calendar-longitude -76.85)
  (setq circadian-themes '((:sunrise . modus-operandi)
                           (:sunset  . modus-vivendi)))
  (circadian-setup))

editor config

I havenā€™t had much use for this in personal projects, but I want it available if I ever work on somebody elseā€™s stuff and they care enough about a common coding style to create a standard configuration.

(use-package editorconfig
  :diminish
  :init
  (editorconfig-mode 1))

org mode

Org Mode fans find it useful for all sorts of work; maybe I should take a hint and dig deeper to see what more I can do with it in my own work.

In the meantime, letā€™s get the current org from the repository and make sure the Lisp modules included with it are part of Emacsā€™ load-path.

(use-package org
  :load-path ("lisp/org-mode/lisp" "lisp/org-mode/lisp/contrib/lisp"))
(require 'ox-md)

I donā€™t remember why I explicitly required the module for Org-to-Markdown export, but I suspect I wanted it for something. Doesnā€™t seem to be doing any harm so Iā€™ll leave it for now.

Even though I have all text modes set to use variable-pitch-mode, I still want Org to use fixed-pitch text when displaying code and other pre-formatted text. I cargo-culted the following from zzamboni.org.

(custom-theme-set-faces
 'user
 '(org-block ((t (:inherit fixed-pitch))))
 '(org-code ((t (:inherit (shadow fixed-pitch)))))
 '(org-document-info ((t (:foreground "dark orange"))))
 '(org-document-info-keyword ((t (:inherit (shadow fixed-pitch)))))
 '(org-indent ((t (:inherit (org-hide fixed-pitch)))))
 '(org-link ((t (:foreground "royal blue" :underline t))))
 '(org-meta-line ((t (:inherit (font-lock-comment-face fixed-pitch)))))
 '(org-property-value ((t (:inherit fixed-pitch))) t)
 '(org-special-keyword ((t (:inherit (font-lock-comment-face fixed-pitch)))))
 '(org-table ((t (:inherit fixed-pitch :foreground "#83a598"))))
 '(org-tag ((t (:inherit (shadow fixed-pitch) :weight bold :height 0.8))))
 '(org-verbatim ((t (:inherit (shadow fixed-pitch))))))

markdown mode

I think it might be nice to get away from Markdown and just use Org Mode for everything. Itā€™s built into Emacs and pandoc can work as easily with Org markup as it does with Markdown. But Iā€™m familiar with Markdown, so not yetā€¦

(use-package markdown-mode
  :init
  (setq markdown-command "/usr/bin/pandoc")
  (autoload 'markdown-mode "markdown-mode" "Major mode for editing Markdown files" t)
  (add-to-list 'auto-mode-alist
               '("\\.\\(?:md\\|markdown\\|mkd\\|mdown\\|mkdn\\|mdwn\\)\\'" . markdown-mode))
  (autoload 'gfm-mode "markdown-mode" "Major mode for editing GitHub Flavored Markdown files" t)
  (add-to-list 'auto-mode-alist
               '("README\\.md\\'" . gfm-mode)))

Just like with Org mode, I want pre-formatted text and code to use fixed-pitch when everything else is variable-pitch.

(custom-theme-set-faces
 'user
 '(markdown-pre-face ((t (:inherit fixed-pitch))))
 '(markdown-code-face ((t (:inherit fixed-pitch)))))

async

I mainly use this for the dired-async module; the others arenā€™t relevant to my use case, but I donā€™t want dired freezing Emacs during a long-running operation.

(use-package async
  :diminish
  :init
  (dired-async-mode 1))

web mode

This is a major mode for writing HTML and CSS. It can be set to work in PHP, Handlebars, ERB, and other templating languages, but I donā€™t need those at the moment.

(use-package web-mode
  :bind (
         ("C-c s p p" . sgml-pretty-print))
  :init
  (add-to-list 'auto-mode-alist '("\\.html\\'" . web-mode))
  (add-to-list 'auto-mode-alist '("\\.htm\\'" . web-mode))
  (setq web-mode-ac-sources-alist
        '(("css" . (ac-source-css-property))
          ("html" . (ac-source-words-in-buffer ac-source-abbrev)))))

Letā€™s include Emmet mode, too. For shits und giggles.

(use-package emmet-mode
  :init
  (add-hook 'sgml-mode-hook 'emmet-mode) ;; Auto-start on any markup modes
  (add-hook 'html-mode-hook 'emmet-mode)
  (add-hook 'web-mode-hook 'emmet-mode)
  (add-hook 'css-mode-hook  'emmet-mode))

el-fly-indent-mode

I donā€™t work with Emacs Lisp often, and mainly to adjust my Emacs configuration, but when I do I want to be able to automatically indent my code.

(use-package el-fly-indent-mode
  :diminish
  :init
  (add-hook 'emacs-lisp-mode-hook #'el-fly-indent-mode))

smartparens

Itā€™s embarrassing to find that your code doesnā€™t work because a cat stole your closing parenthesis, bracket, curly brace, etc. Fortunately, thereā€™s a package for that.

(use-package smartparens
  :diminish
  :init
  (setq show-paren-delay 0)
  (show-paren-mode t)
  (add-hook 'prog-mode-hook #'smartparens-mode))

During setup I enable show-paren-mode to highlight which set of parentheses Iā€™m interacting with. Itā€™s handy when dealing with nested parentheses. I also have a hook to activate smartparens-mode whenever Iā€™m working on code instead of prose.

rainbow-delimiters

Nest parentheses deeply enough (easily done when youā€™re working with Emacs Lisp) and itā€™s hard to tell which parentheses are paired. Again, thereā€™s a package for that.

(use-package rainbow-delimiters
  :diminish
  :init
  (add-hook 'prog-mode-hook #'rainbow-delimiters-mode))

Thereā€™s a hook to activate these whenever Iā€™m editing code.

vterm

Iā€™ve been getting into eshell lately, but there are situations where it isnā€™t suitable and ansi-term is kinda slow and janky. So I use vterm.

(use-package vterm
  :commands vterm
  :bind (
         ("C-c t v" . vterm)
         ("C-c t a" . ansi-term))
  :config
  (setq term-prompt-regexp "^[^#$%>\n]*[#$%>] *")
  (setq vterm-kill-buffer-on-exit 't)
  (setq vterm-shell mg-shell)
  (setq vterm-max-scrollback 10000))

Iā€™v got a custom keybinding to launch vterm and a few other settings. Details are in the manual. Shell depends on operating system; macOS uses zsh and GNU/Linux uses bash. Iā€™ve also taken the opportunity to create bindings to launch ansi-term and eshell.

If you think thatā€™s insane, get this. If I was using Emacs on my corporate-issue machine I might look into making vterm work on Windows and use PowerShell.

Incidentally, when you first install vterm it tries to compile a little C library to integrate into Emacs. If thatā€™s a problem for you, then you might want to use ansi-term or a separate terminal emulator.

magit

This is supposed to be a nice way to do git in Emacs.

(use-package magit
  :commands magit
  :bind (
         ("C-c g g" . magit-status)
         ("C-c g c" . magit-commit)
         ("C-c g f" . magit-pull)
         ("C-c g p" . magit-push)
         ("C-c g s" . magit-stage)))

ivy mode

By default, Emacs will list possible completions horizontally. This probably made sense to RMS but I prefer to scroll up and down through the list. This is especially the case when Iā€™m searching for a string in a file with C-s.

(use-package ivy
  :diminish
  :bind (("C-s" . swiper)
         ("C-r" . swiper-backward)
         :map ivy-minibuffer-map
         ("TAB" . ivy-alt-done)   
         ("C-l" . ivy-alt-done)
         ("C-j" . ivy-next-line)
         ("C-k" . ivy-previous-line)
         :map ivy-switch-buffer-map
         ("C-k" . ivy-previous-line)
         ("C-l" . ivy-done)
         ("C-d" . ivy-switch-buffer-kill)
         :map ivy-reverse-i-search-map
         ("C-k" . ivy-previous-line)
         ("C-d" . ivy-reverse-i-search-kill))
  :config
  (ivy-mode 1))

ivy gives me an improved incremental search: swiper, which I bind to C-s to replace the built-in isearch function. Iā€™ve also got some navigation functions to bind within minibuffers, and I make sure that ivy-mode is always available.

However, I might eventually consider setting up helm instead, if it will let me do on its own what I currently need additional packages to do with ivy.

ivy-rich

This package is supposed to pretty up the ivy interface.

(use-package ivy-rich
  :diminish
  :init
  (ivy-rich-mode 1)
  :config
  (setcdr (assq t ivy-format-functions-alist)
          #'ivy-format-function-line)
  (setq ivy-rich-path-style 'abbrev))

counsel

In addition to ivy, Iā€™ve also got counsel set up to provide various completion functions using the ivy-modified minibuffer.

(use-package counsel
  :diminish
  :bind (("M-x" . counsel-M-x)
         ("C-x b" . counsel-ibuffer)
         ("C-x C-f" . counsel-find-file)
         ("s-SPC" . counsel-linux-app)
         :map minibuffer-local-map
         ("C-r" . 'counsel-minibuffer-history))
  :config
  (setq counsel-linux-app-format-function
        #'counsel-linux-app-format-function-name-pretty))  

EMMS

EMMS seems to be the most current and feature-rich package for music playback in Emacs. Some people have theirs configured to work somewhat like iTunes did back in the day, but I havenā€™t taken things that far.

Iā€™ve got cmus-style keybindings for various functions beginning with C-c e. Rather than create a huge main playlist, Iā€™m content to generate ad-hoc playlists from directory trees or by picking files with dired.

(use-package emms
  :init
  (add-hook 'emms-player-started-hook 'emms-show)
  :config
  (require 'emms-setup)
  (require 'emms-mode-line)
  (require 'emms-playing-time)
  (require 'emms-info)
  (emms-all)
  (emms-mode-line 0)
  (emms-playing-time 0)
  (setq emms-source-file-default-directory "~/Music/")
  (setq emms-player-list '(emms-player-mpv))
  (setq emms-player-mpv-update-metadata t)
  (setq emms-player-mpv-environment '("PULSE_PROP_media.role=music"))
  (setq emms-player-mpv-parameters '("--quiet" "--really-quiet" "--no-audio-display" "--force-window=no" "--vo=null"))
  (setq emms-track-description-function 'mg-emms-track-description)
  (setq emms-info-asynchronously t)
  (add-to-list 'emms-info-functions
               'emms-info-native
               'emms-info-exiftool)
  (setq emms-show-format "[EMMS] now playing: %s")
  :bind (
         ("C-c e e" . emms)
         ("C-c e v" . emms-stop)
         ("C-c e c" . emms-pause)
         ("C-c e z" . emms-previous)
         ("C-c e b" . emms-next)
         ("C-c e x" . emms-start)
         ("C-c e i" . emms-show)
         ("C-c e r" . emms-toggle-repeat-playlist)
         ("C-c e s" . emms-toggle-random-playlist)
         ("C-c e R" . emms-toggle-repeat-track)
         ("C-c e S" . emms-toggle-single-track)
         ("C-c e d" . emms-add-directory-tree)
         ("C-c e D" . emms-add-dired)
         ("C-c e p d" . emms-play-directory-tree)
         ("C-c e p D" . emms-play-dired)))

If you use my configuration, youā€™ll want to make sure youā€™ve got mpv and exiftool installed for playback and tag extraction.

Ogg Vorbis fans should be advised that the emms-info-native function does not like embedded cover art, and will balk at reading metadata from files where embedded cover art is present.

If you have a large collection of Ogg Vorbis files, manually removing embedded cover art will not be fun. Fortunately, itā€™s also unnecessary. You can use the following command if youā€™ve got the vorbis-tools package installed and vorbiscomment is in $PATH:

find ~/Music/ -type f -name '*.ogg' -print0 \
    | xargs -0 -n1 -P8 vorbiscomment -d 'metadata_block_picture'

If you have eyeD3 installed and want to remove embedded cover art from MP3s, too, use this:

find ~/Music/ -type f -name '*.mp3' -print0 \
    | xargs -0 -n1 -P8 eyeD3 --remove-all-images

The commands above use find and xargs to find all files of a given type within your ~/Music/ directory and run them through vorbiscomment or eyeD3 concurrently to take advantage of multicore processors. I got this from ā€œCommand-line Tools can be 235x Faster than your Hadoop Clusterā€ by Adam Drake.

Incidentally, if you have MPEG4 (*.m4a) files, the emms-info-native function wonā€™t retrieve metadata. Youā€™ll need an additional function. emms-info-exiftool seems to work, but as I mentioned earlier youā€™ll need to install exiftool separately from Emacs.

uniquify

When Iā€™m editing different pages in my website, it would be easy to end up with a shitload of buffers named ā€œindex.mdā€. Fortunately, Emacs has a package for this.

(require 'uniquify)
(setq uniquify-buffer-name-style 'forward)

csv mode

Hereā€™s a handy package for editing CSV (comma-separated values) and TSV (tab-separated values) files in Emacs. I use the latter when working on my website. Running TSV through awk beats the shit out of dicking around with JSON and JavaScript.

(use-package csv-mode)

wc-mode

I might be able to do a little better than this, though. Maybe I can get a word count in my mode line. Though I should set a delay so that Emacs isnā€™t constantly trying to update the count while Iā€™m typing.

;; (use-package wc-mode
;;   :config
;;   (setq wc-idle-wait 30
;;         wc-modeline-format "(Wc: %tw)")
;;   (add-hook 'text-mode-hook #'wc-mode))

Unfortunately, even with wc-idle-wait set, wc-mode slows down Emacs when the active buffer contains more than 10,000 words. This makes it unsuitable for use when writing longform prose like novellas or full-on novels.

Maybe thereā€™s something Iā€™m missing. Perhaps wc-mode doesnā€™t play nicely with olivetti-mode? Regardless, letā€™s not use this. Donā€™t forget to manually remove the package with M-x package-delete.

olivetti

When writing text, letā€™s center it and soft-wrap it at 72 characters. The olivetti package (named for the typewriter) makes this easy.

(use-package olivetti
  :diminish
  :config
  (add-hook 'text-mode-hook #'olivetti-mode)
  (setq-default olivetti-body-width 72))

eshell

Hereā€™s some configuration for eshell. Gonna use a Lisp command line.

(use-package eshell
  :commands eshell
  :bind (
         ("C-c t e" . eshell))
  :init
  (setq eshell-directory-name "~/.emacs.d/eshell/"
        eshell-history-file-name "~/.emacs.d/eshell/history"
        eshell-aliases-file "~/.emacs.d/eshell/aliases"
        eshell-last-dir-ring-file-name "~/.emacs.d/eshell/lastdir"
        eshell-banner-message "Welcome to GNU Emacs. This is eshell.\n\n")
  ;; Visual commands
  (setq eshell-visual-commands '("htop" "neofetch" "vim" "lynx"))
  (setq eshell-visual-subcommands '("git" "log" "diff" "show")))
;; aliases
(defalias 'ff 'find-file)

esxml

This seems to be a dependency for nov.el.

(use-package esxml
  :diminish)

nov.el

This is a package for reading ebooks (in EPUB format) in Emacs

(use-package nov
  :config
  (add-to-list 'auto-mode-alist '("\\.epub\\'" . nov-mode))
  (add-hook 'nov-mode-hook 'olivetti-mode))

BBCode

Iā€™ve been frequenting more forums that allow BBCode formatting, and Friendica also seems to do BBCode internally, but who wants to type in a browser? Letā€™s make Emacs speak BBCode.

(use-package bbcode-mode)

Org Mode ā†’ BBCode

If I do more writing on my website with Org Mode, but want to syndicate to a forum or Friendica, why not extend Orgā€™s publisher to output BBCode?

(use-package ox-bb)

Unfortunately, quite a few Org features arenā€™t implemented. I guess begin_html is considered a ā€œspecial-blockā€ in ox-bb.el, and begin_verse isnā€™t implemented either.

I might have to start using straight.el instead of the built-in package.el so I can fork the ox-bb repository and implement some of this shit myself. Dammit.

Apple Dictionary integration

Thereā€™s a package I can use with Appleā€™s Dictionary on macOS. Iā€™ll implement dictd integration separately, since thatā€™s mainly relevant to GNU/Linux.

(cond ((eq system-type 'darwin)
       (use-package osx-dictionary
         :bind (("C-c d l" . osx-dictionary-search-word-at-point)
                ("C-c d i" . osx-dictionary-search-input)))))

dictd integration on GNU/Linux

(cond ((eq system-type 'gnu/linux)
       (use-package dictionary
         :bind (("C-c d l" . dictionary-lookup-definition))
         :config
         (dictionary-server "localhost"))))

unfill

This is a package for hard-breaking paragraphs at an arbitrary column or unbreaking them.

(use-package unfill
    :diminish
    :bind (("C-c u r" . unfill-region)
           ("C-c u p" . unfill-paragraph)
           ("C-c u t" . unfill-toggle)))

UI settings

This section contains UI settings that donā€™t require external packages.

tweaks based on OS type

Some settings are only relevant for macOS. For example, I have to install GNU coreutils via homebrew and I want Emacs to use the tools from that package instead of the BSD-based macOS userland. I also want to use the Command key as a Meta key in Emacs (though this requires adjusting the key for one of macOSā€™ screenshot tools in system settings).

GNU/Linux might have different fonts or a different default shell. I account for these differences here using the custom variables I defined earlier.

(cond ((eq system-type 'darwin)
       (setq insert-directory-program "/opt/homebrew/bin/gls"
             dired-use-ls-dired t
             mg-default-font "Menlo"
             mg-default-variable-font "Georgia"
             mg-dired-path "/Users/starbreaker/"
             mg-shell "zsh"
             ns-alternate-modifier 'alt
             ns-command-modifier 'meta
             ns-right-alternate-modifier 'super
             exec-path (append exec-path '("/opt/homebrew/bin"))
             explicit-shell-file-name "/bin/zsh")
       (setenv "PATH" (concat (getenv "PATH") ":/opt/homebrew/bin")))
      ((eq system-type 'gnu/linux)
       (setq mg-default-font "Noto Mono"
             mg-default-variable-font "Noto Serif"
             mg-dired-path "/home/starbreaker"
             mg-shell "bash"
             explicit-shell-file-name "/bin/bash")))

font settings

Letā€™s set up default, fixed-width, and variable-width fonts using the variables defined and set above.

(set-face-attribute 'default nil
                    :font mg-default-font
                    :height mg-default-font-size
                    :weight 'regular)
(set-face-attribute 'fixed-pitch nil
                    :font mg-default-font
                    :height mg-default-font-size
                    :weight 'regular)
(set-face-attribute 'variable-pitch nil
                    :font mg-default-variable-font
                    :height mg-default-variable-font-size
                    :weight 'regular)

adjusting screen elements

I donā€™t think I need a toolbar or scrollbar on a graphics display. I do want to see how big the file is, and I want column numbers displayed in the modeline along with the current line. I donā€™t need a blinking cursor, though. Not when Iā€™m highlighting the current line.

(size-indication-mode)
(column-number-mode)
(when (display-graphic-p)
  (tool-bar-mode 0)
  (scroll-bar-mode 0)
  (blink-cursor-mode 0)
  (setq scroll-margin 0
        scroll-conservatively 100000
        scroll-preserve-screen-position 1)
  (if (version<= "29.1" emacs-version)
      (pixel-scroll-precision-mode 1)))

`pixel-scroll-precision-mode` is new in Emacs 29.1. Iā€™ve got to check `emacs-version` because Iā€™m still using Emacs 28.2 on Debian.

modus themes

Letā€™s set a different theme from the default. The built-in modus themes are pretty sweet.

(require-theme 'modus-themes)
(setq modus-themes-bold-constructs t
      modus-themes-italic-constructs t)
(load-theme 'modus-operandi)
(define-key global-map (kbd "<f5>") #'modus-themes-toggle)

Iā€™m not going to explicitly load the theme yet, because the modus themes have light and dark variants and I want to use both at the appropriate time:

  • modus-operandi during the day
  • modus-vivendi at night

Thereā€™s a package I can use to automate theme switching, but I also have the theme toggle bound to F5.

current bufferā€™s filename as frame title

This is probably most useful to people using multiple Emacs frames. Thatā€™s usually not me, though.

(setq frame-title-format
      '((:eval (if (buffer-file-name)
                   (abbreviate-file-name (buffer-file-name)) "%b"))))

whitespace visibility settings

Here are some settings that should make text editing easier.

(add-hook 'text-mode-hook (lambda ()
                            (setq show-trailing-whitespace t
                                  indicate-empty-lines t
                                  indicate-buffer-boundaries 'left)))
(add-hook 'prog-mode-hook (lambda ()
                            (setq show-trailing-whitespace t
                                  indicate-empty-lines t
                                  indicate-buffer-boundaries 'left)))

I donā€™t want these settings active in eshell or vterm.

newline at the end

Since I live in UNIX, I want to make sure thereā€™s a newline at the end of the file when I save. Just in case.

(setq-default require-final-newline t)

single space after sentences

Apparently Emacs thinks Iā€™m going to type two spaces after a sentence. Like hell I willā€¦

(setq sentence-end-double-space nil)

some indentation settings

Iā€™m not happy with the way Emacs handles tabs and indentation. Maybe this will help.

(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq c-basic-offset 4)
(setq js-indent-level 2)
(setq css-indent-offset 2)

decluttering the working directory

Iā€™m fine with Emacs creating temp files, but it would be nice if they all went into temp directory.

(make-directory "~/.emacs.d/auto-save/" t)
(make-directory "~/.emacs.d/backup/" t)
(make-directory "~/.emacs.d/eshell/" t)
(setq auto-save-file-name-transforms '((".*" "~/.emacs.d/auto-save/" t)))
(setq backup-directory-alist '(("." . "~/.emacs.d/backup/")))
(setq backup-by-copying t)
(setq create-lockfiles nil)

kill a whole line

If Iā€™m at the start of a line (column 0) and kill a line, I want it to take the newline at the end, too.

(setq kill-whole-line t)

dired tweaks

I like using dired in Emacs as my file manager, even if my OS or desktop environment provides its own file mangler. The interface is consistent across platforms.

(with-eval-after-load 'dired
  (require 'dired-x)
  (define-key dired-mode-map "K" 'kill-this-buffer))
(setq dired-recursive-deletes 'always
      dired-recursive-copies 'always
      dired-deletion-confirmer 'y-or-n-p
      dired-clean-up-buffers-too t
      delete-by-moving-to-trash t
      dired-dwim-target t
      dired-listing-switches "-hal --group-directories-first")

Iā€™ve got a few quality-of-life settings in place.

  1. If Iā€™m deleting a directory, I definitely want recursive delete.
  2. If Iā€™m copying a directory, I likewise want recursive copy.
  3. A simply ā€˜yā€™ or ā€˜nā€™ should be answer enough; donā€™t make me type ā€˜yesā€™ or ā€˜noā€™.
  4. If Iā€™m deleting a directory but have subdirectories open in dired buffers, I want the buffers killed off, too.
  5. When I delete, I want to move to Trash. Just in case I fucked up.
  6. When permitted, dired does a reasonably good job of figuring out where I want to put things when I go to copy or move files, so Iā€™ll let it pick out a default destination.
  7. dired uses ls to generate listings, so I pass switches that will list one item per line, show hidden files/directories, and give sizes in human-readable units. I also want directories listed first.
  8. Pressing ā€œKā€ should kill a dired buffer. Pressing ā€œqā€ only buries the buffer.

donā€™t make me type yes/no

ā€™Nuff said.

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

automatic reload

If a file changes outside of Emacs (because I pulled a new version from a remote git repository), I want Emacs to reload it.

(global-auto-revert-mode t)

show line numbers on the left when coding

If Iā€™m working on a code file, I want line numbers on the left.

(dolist (mode '(prog-mode-hook))
  (add-hook mode (lambda () (display-line-numbers-mode 1))))

garbage collection when not focused

If Iā€™ve switched to another app, like a browser, I want Emacs to do garbage collection.

(add-hook 'focus-out-hook 'garbage-collect)

time and date in modeline?

If Iā€™m going to run Emacs full-screen, having the time and date in the modeline would be cool. I only want the time and date, though. Not the load average. Nor a mail notification. And weā€™ll update the modeline every 10 seconds to avoid running down the battery.

(setq display-time-day-and-date t
      display-time-interval 10
      display-time-format "%H:%M"
      display-time-default-load-average nil
      display-time-use-mail-icon nil)
(display-time-mode 1)

highlight current line

I want to highlight the current line. I know itā€™s silly.

(global-hl-line-mode 1)

go big or go $HOME

On macOS I want Emacs to run full-screen. On GNU/Linux maximized will do.

(setq frame-inhibit-implied-resize t)
(cond ((eq system-type 'darwin)
       (add-hook 'window-setup-hook 'toggle-frame-fullscreen t))
      ((eq system-type 'gnu/linux)
       (add-hook 'window-setup-hook 'toggle-frame-maximized t)
       (menu-bar-mode 0)))

Iā€™m not disabling the menu on macOS because Emacs plays nicely with the menu bar.

diminish minor modes

Minor modes tend to clutter the modeline unless diminished.

(diminish 'subword-mode)
(diminish 'visual-line-mode)
(diminish 'buffer-face-mode)
(diminish 'eldoc-mode)

recent files

It might be handy to have Emacs track recently opened files as well as maintaining session state.

(require 'recentf)
(recentf-mode t)
(setq recentf-max-saved-items 50)
(add-to-list 'ivy-sort-functions-alist
             '(counsel-recentf . file-newer-than-file-p))

preserve session

Emacs has a module called desktop that I can use to save my current session in case I need to restart Emacs for whatever reason. Ideally, any files have open in a given session will re-open when I restart the session.

(require 'desktop)

(defvar mg-desktop-path "~/.emacs.d/session/")
(make-directory mg-desktop-path t)
(setq desktop-path (list mg-desktop-path))

(when (file-exists-p (desktop-full-file-name mg-desktop-path))
  (desktop-read mg-desktop-path))
(desktop-save-mode 1)

(add-hook 'kill-emacs-hook (lambda () (desktop-save mg-desktop-path)))
(add-hook 'focus-out-hook (lambda () (desktop-save mg-desktop-path)))

The potential downsides are as follows:

  1. The more buffers I had open, the longer it will take to reopen them.
  2. Emacs will try to open any windows and frames I had open.
  3. If I have EMMS playing, Emacs might try to start that up again, too.

start in eshell

It looks like desktop doesnā€™t keep my eshell buffer open, and I always want it available.

(eshell)

using this configuration

You are welcome to take what you will from it for your own use. Youā€™re likewise welcome to use the whole thing, but Iā€™d advise against it unless you know what youā€™re doing and are much closer to Emacs guruhood than I am.

As Sacha Chua suggests in her own config, you might want to take small individual pieces and test them in your *scratch* using M-x eval-region to see if you like how Emacs behaves afterward before adding them to your config.

You are also welcome to email me if you have questions or want to suggest an improvement that might help me.