Skip to content

farlado/dotemacs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Farlado’s Illiterate GNU Emacs 🐉

Personally, I feel inspired whenever I open Emacs. Like a craftsman entering his workshop, I feel a realm of possibility open before me. I feel the comfort of an environment that has evolved over time to fit me perfectly – an assortment of packages and keybindings which help me bring ideas to life day after day. – Daniel Higginbotham

Table of Contents

About this configuration

This file is an attempt at a literate GNU Emacs (henceforth “Emacs”) configuration.

Literate programming is a method in which programming is worked on in parallel with the development of documentation specifically pertaining to how said program was written, alongside how to use the program at times. The end result is a file that can be tangled into source code and weaved into documentation. The benefits of this practice are far easier maintenance of a rapidly growing project, and far greater control in the flow of when code is introduced in documentation and where it is included in resultant source code. This allows code to be introduced to the reader in an intuitive manner when reading documentation or working on code while compiling in the right order for a successful build.

This configuration is incredibly opinionated, and it is very likely that most changes will not be friendly towards the typical Emacs user. Hopefully this file will contain enough commentary for you to understand everything within it. Regardless, the idea is that this file, when tangled, generates my entire configuration. I would highly recommend you NOT try to read the .el files on their own. It’s a jungle, especially since all of the commentary regarding the configuration is in this file and this file alone.

Okay, that’s pretty neat. Why though?

Before I used a fully literate file, I had an init.el which tangled blocks from a config.org on the fly (read: on startup). It turns out this method is abysmally slow, making for a startup time of around 4.5 seconds when loading into my desktop environment, even if saving was much faster since no tangling took place when saving the file, much less use of noweb causing massive slowdowns in the tangling process.

Tangling blocks from a literate-emacs.org into byte-compiled early-init.el and init.el, alongside the use of portable dumping and multiple other efficiency improvements, cuts this time down to around 1.1 seconds to load into my desktop environment. This also comes with the advantage of being a hub for all things related to my Emacs configuration, where previously I had to check multiple files when I thought I might have changed something for the worse.

There has been some degree more complexity in managing this configuration, since I am having to juggle many more moving parts which may be in different places of the file, but the rewards for this kind of configuration are significantly outweighing the minor inconveniences which come with it, as having things ordered as they are means shorter blocks and code and more documentation about what each block does.

Installation

  1. Clone the repo into where you store your Emacs configuration.
  2. Make sure you have all the right dependencies. See below for more details.

Dependencies

THIS CONFIGURATION IS MEANT FOR EMACS 27 AND LATER. IT WILL LIKELY /NOT/ LOAD PROPERLY ON EMACS 26 OR EARLIER. THE BRANCH FOR EMACS 26 OR EARLIER IS HERE.

Everything has different dependencies so make sure you have what you need. The quick and dirty route to getting all these dependencies installed and configured is to deploy my dotfiles.

For EXWM

  • xorg: For obvious reasons.
  • dunst: Notification daemon.
  • font-awesome: For workspace names.
  • xcompmgr: My compositor of choice.
  • arandr: For monitor configuration.
  • nm-connection-editor: For network configuration.
  • pavucontrol: For volume mixing.
  • Various X applications: Launched by Emacs.

For desktop-environment

  • alsa-utils: For volume adjustment.
  • brightnessctl: For laptop backlight adjustment.
  • maim: For screenshots.
  • xclip: For copying screenshots to the clipboard.
  • i3lock-color: For the lock screen.

Other

  • aspell: For spell-checking.
  • mpd: For playing music with emms.
  • ebook-tools: For reading ebooks with nov.
  • pylint: For syntax checking within Python.
  • python-jedi: For Python auto-complete.
  • curl: For getting weather with wttrin.
  • graphviz: For creating diagrams.
  • stack: Haskell tools.
  • sudo: Duh.

License

Farlado’s Illiterate GNU Emacs is licensed under version 3 of the GNU General Public License. This is just a general practice for anything related to Emacs, so I see no reason not to break from this practice.

;; 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 <https://www.gnu.org/licenses/>.

Giving files their headers

In order to make the files look at least somewhat decent for documentation linters, and to warn those who are unfortunate enough to think they’ll just mosey on into one of them if they want to understand the config, we create headers that tell people the reality of the files.

pdumper.el

;;; pdumper.el --- Making a portable dump image

;; This file is not part of GNU Emacs.

<<license>>



;;; Commentary:

;; This file has been automatically tangled from `literate-emacs.org'.
;; If you don't have a copy of that file, it is best not to use this file!
;; All relevant commentary is in `literate-emacs.org', not here.
;; There may not be any comments past this point.
;; Abandon all hope, ye who enter here.



;;; Code:

early-init.el

;;; early-init.el --- Early startup for Farlado's Illiterate GNU Emacs

;; This file is not part of GNU Emacs.

<<license>>



;;; Commentary:

;; This file has been automatically tangled from `literate-emacs.org'.
;; If you don't have a copy of that file, it is best not to use this file!
;; All relevant commentary is in `literate-emacs.org', not here.
;; There may not be any comments past this point.
;; Abandon all hope, ye who enter here.



;;; Code:

init.el

;;; init.el --- Initializing Farlado's Illiterate GNU Emacs

;; This file is not part of GNU Emacs.

<<license>>



;;; Commentary:

;; This file has been automatically tangled from `literate-emacs.org'.
;; If you don't have a copy of that file, it is best not to use this file!
;; All relevant commentary is in `literate-emacs.org', not here.
;; There may not be any comments past this point.
;; Abandon all hope, ye who enter here.



;;; Code:

Making Emacs start quickly

This is everything related to starting Emacs quickly. First things first is setting up a batch script used to create a custom portable dump image, followed by what to execute at startup to make initialization faster.

Starting Emacs FAST

Even with the “small” amount I ask of Emacs, it’s a lot of beef to start up as fast as I demand it to start up. The portable dumper is an amazing thing. This is just a minimal script for utilizing the portable dumper added to Emacs 27 to make Emacs load faster. Every single require that doesn’t create a LispObject incompatible with the portable dumper can now be skipped while loading. Before I started using the portable dumper, I saw start times of around 2.5 seconds. Now I am down 1.1 seconds, having cut about half of the start time out.

This script must be run while Emacs is not open, otherwise it will crash Emacs and (if you’re using vterm or another virtual terminal inside of Emacs to run the script) the dump image will be corrupted. Currently Emacs is unable to create a portable dump image outside of a batch script. To run the script, from the shell enter the following, substituting $USER_EMACS_DIR for wherever you store your Emacs configuration:

emacs --batch -q -l $USER_EMACS_DIR/pdumper.el

Load packages

Because we are wanting to load packages, first the package manager must be initialized. Because creating a portable dump image is in a batch script, package management as a feature must be loaded manually.

(require 'package)
(package-initialize)

Store load-path

For some reason, the dump image doesn’t store load-path, so it needs to be stored here, to be restored when early-init.el is loaded. A boolean is also set to indicate a portable dump image was used when Emacs is loaded, so that other fixes to erratic behavior can be applied. See further down for details.

(setq pdumper-load-path load-path
      pdumper-dumped t)

require features

This is really the most important section of the script: where all the features in use are loaded, save those with functionality that behave erratically if loaded in this way.

(dolist (feature `(;; Core
                   use-package
                   async
                   auto-package-update
                   try

                   ;; Looks
                   dracula-theme
                   mood-line
                   dashboard
                   page-break-lines
                   display-line-numbers
                   paren
                   rainbow-mode
                   rainbow-delimiters

                   ;; Functionality
                   server
                   which-key
                   company
                   counsel
                   company-emoji
                   ibuffer
                   buffer-move
                   sudo-edit

                   ;; Editing
                   markdown-mode
                   graphviz-dot-mode
                   flyspell
                   swiper
                   autorevert
                   popup-kill-ring
                   hungry-delete
                   avy
                   elec-pair

                   ;; Programming
                   haskell-mode
                   lisp-mode
                   company-jedi
                   flycheck
                   flycheck-package
                   flycheck-posframe
                   avy-flycheck

                   ;; org
                   org
                   toc-org
                   org-bullets
                   epresent
                   org-tempo

                   ;; Other
                   nov
                   wdired
                   term
                   wttrin
                   emms
                   emms-setup

                   ;; games
                   yahtzee
                   sudoku
                   tetris
                   chess
                   2048-game

                   ;; Desktop Environment
                   exwm
                   exwm-xim
                   exwm-randr
                   exwm-config
                   exwm-systemtray
                   minibuffer-line
                   system-packages
                   desktop-environment
                   wallpaper))
    (require feature))

Pre-load the theme

A HUGE amount of time is spent loading the theme during startup. Loading the theme in the portable dump image saves a sizeable chunk of time.

(load-theme 'dracula t t)

Write the dump image

This is where the magic happens. Sit back and relax, this can take a minute or few to finish up. If it crashes here, the dump image will come out corrupted.

(dump-emacs-portable (locate-user-emacs-file "emacs.pdmp"))

Do these things ASAP

Emacs 27 has introduced the file early-init.el, allowing configuration of multiple items before Emacs has graphically loaded. Either I want these configured as soon as possible, or they are related to Emacs starting up. Which are which is left as an exercise to the reader.

Prepare GUI (Part 1)

I want to get GUI elements out of my face as soon as I possibly can. They just take up space. If I’m running Emacs as my desktop environment (see further below), I want Emacs to immediately take on the background color of the theme I use to make startup marginally more aesthetically pleasing.

Seriously, who among us Emacsers even uses any of the GUI bits of Emacs regularly anyway? Why are these here to begin with? The scroll bar makes some sense but still I find it pointless, since Emacs is so centered on keyboard use…

(menu-bar-mode -1)
(tool-bar-mode -1)
(scroll-bar-mode -1)

(when (getenv "_RUN_EXWM")
  (set-face-background 'default "#282a36"))

Handling portable dumping

For some reason, the portable dumper has odd behaviors. This block aims to address each of these behaviors so that using a custom dump image does not behave any different from not using one.

This block is supposed to:

  • Recover load-path from the dump image
  • Restore modes not preserved in the dump image
  • Fix the scratch buffer
  • Create a function to require a feature only if pdumper-dumped is nil
(defvar pdumper-dumped nil
  "Non-nil if a custom dump image was loaded.")

(defvar pdumper-load-path nil
  "Contains `load-path' if a custom dump image was loaded.")

(defun pdumper-require (feature &optional filename noerror)
  "Call `require' to load FEATURE if `pdumper-dumped' is nil.

FILENAME and NOERROR are also passed to `require'."
  (unless pdumper-dumped
    (require feature filename noerror)))

(defun pdumper-fix-scratch-buffer ()
  "Ensure the scratch buffer is properly loaded."
  (with-current-buffer "*scratch*"
    (lisp-interaction-mode)))

(when pdumper-dumped
  (add-hook 'after-init-hook #'pdumper-fix-scratch-buffer)
  (setq load-path pdumper-load-path)
  (global-font-lock-mode 1)
  (transient-mark-mode 1)
  (blink-cursor-mode 1))

Byte-compile on first run

Byte-compiling the init file is surprisingly effective and helps speed up startup quite a bit. It’s done after after-init-hook so that we don’t actually do it in the middle of loading files. That would be disastrous.

(defun farl-init/compile-user-emacs-directory ()
  "Recompile all files in `user-emacs-directory'."
  (byte-recompile-directory user-emacs-directory 0))

(unless (file-exists-p (locate-user-emacs-file "init.elc"))
  (add-hook 'after-init-hook #'farl-init/compile-user-emacs-directory))

Prefer the newest files

If there’s a difference in time between a file and its byte-compiled counterpart, prefer the newer one. This ensures if I pull changes from GitHub but forget to recompile, I will still get to enjoy the changes I made.

(setq load-prefer-newer t)

More complete apropos

Even if it is slower, this ensures that apropos will look through everything when run. This is helpful for ensuring that when looking for something, every potential option shows up.

(setq-default apropos-do-all t)

File name handling setup

For whatever reason, setting file-name-handler-alist to nil helps Emacs load faster. After Emacs finishes loading, it’s reverted to its original value. I don’t even know how it works, but it does.

(defvar startup/file-name-handler-alist file-name-handler-alist
  "Temporary storage for `file-name-handler-alist' during startup.")

(defun startup/revert-file-name-handler-alist ()
  "Revert `file-name-handler-alist' to its default value after startup."
  (setq file-name-handler-alist startup/file-name-handler-alist))

(setq file-name-handler-alist nil)
(add-hook 'emacs-startup-hook #'startup/revert-file-name-handler-alist)

Garbage collection setup

Garbage collection shouldn’t happen during startup, as that will slow Emacs down. Do it later. This is also where more ideal garbage collection settings are chosen. The functions used to defer and restore garbage collection are used later on, so they use more general names. If my desktop environment is to be loaded, do not restore garbage collection too soon.

(defun garbage-collect-defer ()
  "Defer garbage collection."
  (setq gc-cons-threshold most-positive-fixnum
        gc-cons-percentage 0.6))

(defun garbage-collect-restore ()
  "Return garbage collection to normal parameters."
  (setq gc-cons-threshold 16777216
        gc-cons-percentage 0.1))

(garbage-collect-defer)
(add-hook 'emacs-startup-hook #'garbage-collect-restore)

Early package management

Because I am writing this configuration to be as independent/portable as possible (e.g. I should be able to dump this onto any machine and run it), I manage all packages through Emacs. All of this is done leading up to the call of package-initialize between loading early-init.el and init.el, which makes for faster loading. Packages cannot be installed at this stage, so what is present here is everything leading up to the installation of packages.

Disable customize, keep package-autoremove working

I hate customize. I hate it with a burning passion. I configure everything in this file, so I don’t need anything messing with my init.el, much less changing settings on me. Even though I do not use customize, I really like protecting packages used in my configuration from package-autoremove, so I need to still set the variable package-selected-packages so that it’ll work. Packages are listed in the order in which they are mentioned in this configuration, though this isn’t guaranteed since things change all the time.

(setq custom-file "/tmp/custom.el"
      package-selected-packages '(;; Core
                                  async
                                  use-package
                                  auto-package-update
                                  try

                                  ;; Looks
                                  dracula-theme
                                  mood-line
                                  dashboard
                                  page-break-lines
                                  rainbow-mode
                                  rainbow-delimiters

                                  ;; Functionality
                                  which-key
                                  counsel
                                  company
                                  company-emoji
                                  buffer-move
                                  sudo-edit

                                  ;; Text Editing
                                  graphviz-dot-mode
                                  markdown-mode
                                  swiper
                                  popup-kill-ring
                                  hungry-delete
                                  avy

                                  ;; Programming
                                  magit
                                  haskell-mode
                                  company-jedi
                                  flycheck
                                  flycheck-package
                                  flycheck-posframe
                                  avy-flycheck

                                  ;; `org-mode'
                                  toc-org
                                  org-bullets
                                  epresent

                                  ;; Extend
                                  nov
                                  wttrin

                                  ;; Games
                                  yahtzee
                                  sudoku
                                  chess
                                  2048-game

                                  ;; Other
                                  emms

                                  ;; Desktop Environment
                                  exwm
                                  minibuffer-line
                                  system-packages
                                  desktop-environment
                                  wallpaper))

Disable an annoying customize function

Since I don’t use customize, we don’t need to mess with it every time a package is installed or uninstalled. This also ensures that package-saved-packages will never be altered by installing or removing packages. Because of this, I need to first load everything related to package management.

(pdumper-require 'package)
(defun package--save-selected-packages (&rest opt)
  "Return nil, ignoring OPT.

This function was altered to inhibit a specific undesired behavior."
  nil)

Configure package repositories

Next, we have to add our package repositories to the list. The GNU and MELPA repositories should be enough to last me decades. This is multiple thousands of packages, basically everything of a quality high enough to be worth installing in the now.

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

Later package management

This part of package management is meant to be done after package-initialize has been called. At this point, we can leave early-init.el and move into init.el to continue Emacs startup. This is where packages can actually start being installed, but here we only install things directly related to installing and updating packages.

Easy configuration

Since I manage all Emacs packages in Emacs itself, use-package makes it much easier to manage the packages I need. It also means I can see what packages take the longest to load, alongside configure packages in a significantly more declarative manner rather than the weird way I’ve seen packages configured in other configurations.

(unless (package-installed-p 'use-package)
  (package-refresh-contents)
  (package-install 'use-package))

(pdumper-require 'use-package)
(setq use-package-compute-statistics t)

Asynchronous execution

Asynchronous bytecode compilation and various other actions makes Emacs lock SIGNIFICANTLY less often. This is a very good thing. Every time Emacs locks up that halts my whole desktop environment, so minimizing that happening is extremely important.

(use-package async
  :ensure t
  :defer t
  :init
  (dired-async-mode 1)
  (async-bytecomp-package-mode 1)
  :custom (async-bytecomp-allowed-packages '(all)))

Automatic package updates

I don’t want to have to manually update my stuff. This solution is literally plop-and-forget, and updates packages on a regular interval of two days. It has never caused me a single problem ever. I haven’t even modified the package settings since I introduced it to the configuration.

(use-package auto-package-update
  :ensure t
  :defer t
  :custom ((auto-package-update-interval 2)
           (auto-package-update-hide-results t)
           (auto-package-update-delete-old-versions t))
  :hook (after-init . auto-package-update-maybe))

Trying packages on the fly

Sometimes I just want to give a package a shot before including it in my configuration and having to entirely redo my portable dump file. This package allows the temporary inclusion of a package in my Emacs environment for a single session.

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

Making Emacs much less ugly

Stock Emacs is ugly. Just straight up ugly. Suffice to say it leaves much to be desired. This ranges from unimportant things to things which make my eyes burn. This section is specifically meant for fixing Emacs visually and making it much more desirable for everyday use.

Font

Of course, a text editor needs to be able to display text well. These settings are specifically meant to make text not only display properly, but also make it look good.

Use UTF-8 encoding

This makes for a much easier time editing files and working with text. Why isn’t this the default to begin with since it’s basically standard for everything?

(prefer-coding-system 'utf-8)
(setq locale-coding-system 'utf-8)
(set-language-environment "UTF-8")
(set-default-coding-systems 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)

Setting the font style

Originally I had this set up by means of custom-set-faces, but frankly that is less easily configured than this method. Every part of customize simply isn’t all that useful when trying to make things easier to edit directly from the configuration files.

(when (member "Iosevka" (font-family-list))
  (set-face-attribute 'default nil
                      :font "Iosevka"
                      :height 100))

Getting emoji to work properly

This one feel great to have now that I use an Emacs version that can handle it! Emoji now render properly in documents! 🐲

(when (member "Noto Color Emoji" (font-family-list))
  (set-fontset-font t 'symbol
                    (font-spec :family "Noto Color Emoji")
                    nil 'prepend))

Don’t unload fonts when not in use

This solves a number of hanging issues related to a number of different packages and symbols. Emacs gets annoyingly slow if this is not set. This is also known to prevent issues at times with other packages, so that’s good.

(setq inhibit-compacting-font-caches t)

Theme

For the longest time I thought Leuven was seriously my best choice. As time has gone by, I’ve gotten less and less fond of Leuven. It served me well at the beginning of my exploration, but after so long using dark themes and having wallpapers behind my Emacs, Leuven stopped cutting it. Currently the theme I am using is Dracula. This is probably the most comfortable theme I have found, and there are GTK themes that match it very well.

(use-package dracula-theme
  :if window-system
  :ensure t
  :defer t
  :init
  (if pdumper-dumped
      (enable-theme 'dracula)
    (load-theme 'dracula t))
  <<theme-init>>)

Fringes

Having fringes helps keep things looking good and gives the opportunity to have nice indicators on the edges of buffers. I prefer when fringes are the same color as the rest of the window, a choice which many themes seem not to agree with for some reason.

(set-face-background 'fringe (face-background 'default))
(fringe-mode 10)

Line numbers

For some reason, some themes like to give line numbers a different color background from the rest of a window. I hate that. It distracts me and looks extremely tacky. Keeping the line numbers’ background color the same color as the background of the rest of the window leaves little in the way of distractions.

(set-face-background 'line-number (face-background 'default))

Window dividers

Windows dividers make Emacs look far less sloppy, and provide divisions between windows that are significantly more visible. The color is grabbed from the mode line for consistency. Three pixels seems to be the best looking width for window dividers across all my screens.

(setq window-divider-default-right-width 3)
(let ((color (face-background 'mode-line)))
  (dolist (face '(window-divider-first-pixel
                  window-divider-last-pixel
                  window-divider))
    (set-face-foreground face color)))
(window-divider-mode 1)

Transparent frames

If there’s a gimmick I never realized I can’t get enough of, it’s having a transparent frame. At first, I thought it would look dumb, that it would make things hard to read since Emacs doesn’t seem to differentiate between foreground and background when setting alpha values, but now that I have used it for a while, going back to an opaque frame seems tacky.

(dolist (frame (frame-list))
  (set-frame-parameter frame 'alpha 90))
(add-to-list 'default-frame-alist '(alpha . 90))

Better Org-mode headers

For some reason, theme creators don’t really think of formatting Org-mode past colors, so I have instead taken matters into my own hands. This way, I can use whatever color scheme I want with some peace of mind that at the least I don’t have to look for Org-aware themes. It also means I can override some of the dumber choices of Org-aware themes. This block is tangled into Org-mode’s use-package form rather than here.

(set-face-attribute 'org-document-title nil
                    :weight 'extra-bold
                    :height 1.8)
(set-face-attribute 'org-level-1 nil
                    :height 1.3)
(set-face-attribute 'org-level-2 nil
                    :height 1.1)
(set-face-attribute 'org-level-3 nil
                    :height 1.0)
(set-face-attribute 'org-code nil
                    :inherit 'font-lock-string-face)

Mode line

I hate the default mode line with a burning passion. This mode line is sleek and minimalist for a quick startup and a compact look and saved frustration. I have considered a number of other mode lines, but this one seems to be the most fitting for what I want. The package mini-modeline was considered but comes with a number of pitfalls including but not exclusively cases in which the echo area will clash with the mode line. I considered doom-modeline but frankly I find the icons to look weird and it is a rather tall mode line. At the end of the day, mood-line provides the minimalism of doom-modeline but does not require the weird symbols.

(use-package mood-line
  :ensure t
  :defer t
  :init
  (mood-line-mode 1)
  <<mode-line-init>>)

Show line/column numbers on the mode line

Why isn’t this enabled by default on a text editor? What line and column the point is on should always be visible on the mode line. I don’t know if I even need to toggle these for my given mode line, but it still seems productive to enable them.

(line-number-mode 1)
(column-number-mode 1)

Show clock and battery level on mode line

I used to use fancy-battery for battery level but it constantly disappeared on my teeny tiny screens so I just decided not to bother with it. Plus it’s one less package to configure lol. The clock should be in 24-hour time, and the date should also be shown.

(display-time-mode 1)
(display-battery-mode 1)
:custom ((display-time-format "%a %m/%d %H:%M")
         (display-time-day-and-date t)
         (display-time-24hr-format t))

Start screen

The default screen is nice when you are first using Emacs, and it contains many useful links , but personally I want something with more options to customize. This package provides that, and works incredibly well. A custom banner is displayed and recent files are shown.

(use-package dashboard
  :ensure t
  :defer t
  :init
  <<dashboard-init>>
  (dashboard-setup-startup-hook)
  :custom ((inhibit-start-screen t)
           (dashboard-set-footer nil)
           (dashboard-startup-banner (locate-user-emacs-file "logo.png"))
           (dashboard-items '((recents . 10)))
           (initial-buffer-choice #'dashboard-or-scratch)
           (dashboard-banner-logo-title
            "Welcome to Farlado's Illiterate GNU Emacs!"))
  :hook (dashboard-mode . dashboard-immortal))

Show dashboard or scratch initially

When Emacs or emacsclient starts, the first buffer shown should be either dashboard or a scratch buffer. To prevent use of a lambda (something I have come to try to avoid where I can for a number of good reasons).

(defun dashboard-or-scratch ()
  "Open either dashboard or the scratch buffer."
  (or (get-buffer "*dashboard*")
      (get-buffer "*scratch*")))

Make the dashboard buffer immortal

The dashboard buffer itself should be immortal. I used to close it all the time, and this is meant to prevent that by hooking emacs-lock-mode into dashboard-mode-hook to lock the buffer from being killed.

(defun dashboard-immortal ()
  "Make the dashboard buffer immortal."
  (emacs-lock-mode 'kill))

Other elements

Word wrapping

This is a more point of convenience than aesthetic, even in programming language buffers. Wrapping words makes for a heck of a lot more readability of any kind of text, whether a program or just normal language.

(global-visual-line-mode 1)

However, I don’t want to stop there. I want to keep certain limits to how wide lines can go, so I also use auto-fill-mode so words will wrap as I type. I personally prefer a fill-column value of 80 by default. This rule should only apply to modes involving prose. Configuration and programming buffers should not include this limitation.

(setq-default fill-column 80)
(add-hook 'text-mode-hook #'turn-on-auto-fill)

Other GUI items

These settings would be great to include in early-init.el, but they don’t seem to go into effect unless they are in init.el instead. What a bummer. Showing tooltips and dialog boxes outside of the minibuffer is utter nonsense and I really don’t like that idea.

(tooltip-mode -1)
(setq use-dialog-box nil
      use-file-dialog nil)

Make the cursor a bar

For some reason, the point as a block is just aesthetically unpleasing to me, and it seems to make more sense to have a thinner point so that it really demonstrates where precisely symbols will be inserted, instead of directly being on a character.

(setq-default cursor-type 'bar)

Turn ^L into pretty lines

This is used in a number of places, and it makes Emacs Lisp orders of magnitude easier to read and organize while showing page breaks in other modes, or just showing some fancy lines on the screen. Better to have it on all the time than never on. I didn’t know it was an external package until I uninstalled dashboard for a short time.

(use-package page-break-lines
  :ensure t
  :defer t
  :hook (after-init . global-page-break-lines-mode))

Line numbers (on most buffers)

I like having line numbers and indicators for lines past the EOF. However, I don’t like line numbers in modes where it breaks the mode. Having relative lines numbers felt weird for me at first, but it’s genuinely a nice thing to have, as it allows me to count how many lines there are between some item and some other item without having to do math in my head.

(use-package display-line-numbers
  :defer t
  :custom ((indicate-empty-lines t)
           (display-line-numbers-type 'relative))
  :hook ((text-mode
          prog-mode
          conf-mode) . display-line-numbers-mode))

Highlight matching parentheses

This mode helps keep parentheses in order. Not all themes have good defaults for the mode, so I have to change some settings so the highlighted parentheses always stand out. I also want the highlighting to show immediately rather than after a longer delay.

(use-package paren
  :defer t
  :init
  (show-paren-mode 1)
  :custom-face (show-paren-match ((t (:weight extra-bold
                                      :underline t))))
  :custom ((show-paren-style 'parentheses)
           (show-paren-delay 0.00000001)))

Color the background of text based on the color/hex typed

When developing or dealing with colors, this mode is incredibly useful to have around, so that colors codes pop out specifically with the color they directly represent. However, it really only works well if loaded in a graphical environment.

(use-package rainbow-mode
  :if window-system
  :ensure t
  :defer t
  :hook (prog-mode . rainbow-mode))

Change the color of various delimiters based on how deep they go

This mode makes reading Lisp and other languages so much easier and helps show where mismatches exist. It also makes the buffer look a little more colorful and in Lisp especially makes every sexp way easier to separate from others.

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

Making Emacs more comfortable

Anyone who has used Emacs for any period of time can attest to the fact that it can take a lot to make Emacs comfortable for one’s use. That is not to say that Emacs is bad, but it definitely isn’t the most usable piece of software straight out of the box. These settings make some of the things I personally dislike about defaults in Emacs somewhat better.

General functionality

These are items which improve Emacs overall, but aren’t specific to editing text or major enough to go into other categories. This includes things from stopping undesired behaviors to making Emacs run generally smoother.

Emacs server

Having the Emacs server running allows for a lot of neat integration with other parts of my desktop environment. I don’t want to try to start a server if one is already running, though.

(pdumper-require 'server)
(unless (server-running-p)
  (server-start))

No more training wheels

I’m a big boy now, no need for anyone to hold my hand. Since I do not use customize, this has to be set every time Emacs starts.

(setq disabled-command-function nil)

Don’t hang the minibuffer

When using the minibuffer, never do garbage collection. I’m not entirely certain if it’s actually causing any kind of major change in how the minibuffer behaves, but it definitely feels a little more responsive.

(add-hook 'minibuffer-setup-hook #'garbage-collect-defer)
(add-hook 'minibuffer-exit-hook #'garbage-collect-restore)

Always confirm closing Emacs

I constantly kill Emacs on accident when running it in terminals, so this prevents me from doing that as easily. Having to always confirm when I quit Emacs is way better than accidentally killing Emacs when I don’t want to.

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

Make scrolling a little less crazy

I have no clue why the mouse wheel gets acceleration, but thankfully I don’t have to worry about that anymore. The goal is to make scrolling more friendly, e.g. it always scrolls one line at a time and the cursor stays where it is on the display.

(setq scroll-margin 0
      auto-window-vscroll nil
      scroll-preserve-screen-position 1
      scroll-conservatively most-positive-fixnum
      mouse-wheel-scroll-amount '(1 ((shift) . 1))
      mouse-wheel-progressive-speed nil
      mouse-wheel-follow-mouse t)

Use a visual bell instead of making noise

Sound is obnoxious and it should be visibly obvious without flashing the frame or mode line that something has gone wrong.

(setq ring-bell-function 'ignore)

Replace “yes or no” prompts with “y or n” prompts

Beauty in brevity, the less keystrokes the better.

(defalias 'yes-or-no-p #'y-or-n-p
  "Use `y-or-n-p' instead of a yes/no prompt.")

Completion helpers

There are many different features to help complete everything from commands to sentences to paths to input method names. These different completion helpers are configured in this section.

which-key (small menus to help with commands)

Even as I’ve gotten used to Emacs key bindings, it is always nice to have this around so that if I want to know, I can easily see what’s what. I also want to see what key sequences are being entered instantaneously.

(use-package which-key
  :ensure t
  :defer t
  :custom (echo-keystrokes 0.00000001)
  :hook (after-init . which-key-mode))

Auto-complete in documents

This is the base package. I changed some key bindings to make it more pleasant to use. It’s not just for programming anymore, as seen in the next block. I don’t want it to start recommending things unless I’ve typed more than three characters and let it sit for a little under a second.

(use-package company
  :ensure t
  :defer t
  :custom ((company-idle-delay 0.75)
           (company-minimum-prefix-length 3))
  :hook (after-init . global-company-mode)
  :bind (:map company-active-map
         ("M-n" . nil)
         ("M-p" . nil)
         ("C-n" . company-select-next)
         ("C-p" . company-select-previous)
         ("SPC" . company-abort)))

Auto-complete in commands

I love ido-mode, but sometimes it just didn’t cut it. Instead, I use counsel, which provides a fancier completion experience than ido-mode does currently. It’s also way more ubiquitous than ido-mode, and is used by a many other packages which are useful.

(use-package counsel
  :ensure t
  :defer t
  :init
  (ivy-mode 1)
  (counsel-mode 1)
  (setq ivy-initial-inputs-alist nil))

Typing Emoji using Emacs

Thanks to company above, this is possible now! Putting a colon and typing something will give suggestions for matching emoji. 🎊

(use-package company-emoji
  :after company
  :ensure t
  :defer t
  :init
  (add-to-list 'company-backends #'company-emoji))

Buffer management

Buffer management is done using ibuffer, which is orders of magnitude more comfortable than other ways I’ve seen of managing buffers. Packages like helm can probably do good, but I personally like using what’s already available to me. Buffer names should also be made unique. This looks a lot fancier than the default behavior, and makes buffer names far easier to read at a glance. I can use ibuffer for both switching buffers and listing buffers, so I have no need for two separate binds.

(use-package ibuffer
  :defer t
  :init
  <<buffer-management-init>>
  <<buffer-management-custom>>
  <<buffer-management-hook>>
  :bind (("C-x b" . ibuffer)
         ("C-x C-b" . nil)
         <<buffer-management-binds>>))

Sort buffers better

The absolute beauty of ibuffer is the ability to split buffers up by unique categories. I can have my EXWM buffers separate from my Python or Emacs Lisp buffers. However, the way this is configured is a tad wonky, so it requires a number of different things to be put in place.

The first thing to do is set up the function that makes ibuffer use my buffer category list. This simply makes it use the “default” filter group.

(defun farl-ibuffer/use-default-filter-group ()
  "Switch to the intended filter group."
  (ibuffer-switch-to-saved-filter-groups "default"))

This function runs during ibuffer-mode-hook.

:hook (ibuffer-mode . farl-ibuffer/use-default-filter-group)

Finally, I can set the “default” filter group. Other variables are tangled into this block for a cleaner overall tangled output.

:custom ((ibuffer-saved-filter-groups
          (quote (("default"
                   ("exwm" (and (not (name . "Firefo[x<>1-9]+$"))
                                (or (name . "^\\*system-packages\\*$")
                                    (name . "^\\*Wi-Fi Networks\\*$")
                                    (name . "^\\*XELB-DEBUG\\*$")
                                    (mode . exwm-mode))))
                   ("firefox" (name . "Firefo[x<>1-9]+$"))
                   ("emms" (or (mode . emms-playlist-mode)
                               (mode . emms-browser-mode)
                               (mode . emms-mode)))
                   ("ebooks" (mode . nov-mode))
                   ("magit" (name . "^magit.*:"))
                   ("dired" (or (mode . dired-mode)
                                (mode . wdired-mode)))
                   ("elisp" (mode . emacs-lisp-mode))
                   ("haskell" (mode . haskell-mode))
                   ("python" (mode . python-mode))
                   ("org"   (mode . org-mode))
                   ("term" (mode . term-mode))
                   ("emacs" (or (name . "^\\*package.*results\\*$")
                                (name . "^\\*Shell.*Output\\*$")
                                (name . "^\\*Compile-Log\\*$")
                                (name . "^\\*Completions\\*$")
                                (name . "^\\*Backtrace\\*$")
                                (name . "^\\*dashboard\\*$")
                                (name . "^\\*Messages\\*$")
                                (name . "^\\*scratch\\*$")
                                (name . "^\\*info\\*$")
                                (name . "^\\*Help\\*$")))))))
         <<buffer-management-vars>>)

Cleaner unique buffer names

If any files have an identical name, make each buffer name unique using a forward slash and a non-identical directory which contains the file. If one of the buffers is killed, remove the directory name from the buffer name.

(uniquify-buffer-name-style 'forward)
(uniquify-after-kill-buffer-p t)

Immortal blank scratch buffer

I kill the scratch buffer way too often if I don’t do this.

(with-current-buffer "*scratch*"
  (emacs-lock-mode 'kill))

While I’m here, I might as well also make the scratch buffer blank.

(initial-scratch-message "")

Always kill the current buffer

I really don’t like that there’s a whole menu just to pick what buffer to kill every time I press C-x k, rather than just killing the buffer currently on the screen.

("C-x k" . kill-this-buffer)

Window management

There are built-in functions for changing focus between windows, but there are not good built-in functions for swapping two windows and moving buffers between windows. Since other-window is hot garbage, I instead use C-x o as a prefix for the commands related to moving focus or moving windows. Other window managing settings are placed in this section.

(use-package buffer-move
  :ensure t
  :defer t
  :init
  <<window-management-init>>
  <<window-management-vars>>
  :bind (("C-x o" . nil)
         ("C-x o w" . windmove-up)
         ("C-x o a" . windmove-left)
         ("C-x o s" . windmove-down)
         ("C-x o d" . windmove-right)
         ("C-x o C-w" . buf-move-up)
         ("C-x o C-a" . buf-move-left)
         ("C-x o C-s" . buf-move-down)
         ("C-x o C-d" . buf-move-right)
         <<window-management-binds>>))

Focus follows mouse

Changing focus can also be done through sloppy focus, e.g. moving the cursor between windows. I hate having to click to focus a different window, so I would rather just have windows sloppily focus. This has some flaws when using Emacs as a desktop environment, but it’s still cozier than the alternative behavior. It’s tangled into the window management form.

:custom ((focus-follows-mouse t)
         (mouse-autoselect-window t))

Follow to new windows

When forming a new window, it should be followed and ibuffer should be opened. This replaces the other not particularly friendly behavior, and is tangled into the window management form.

There are two functions which are used to create new windows. The first spawns a new window below the current window:

(defun split-and-follow-below ()
  "Open a new window vertically."
  (interactive)
  (split-window-below)
  (other-window 1)
  (ibuffer))

The other function creates a new window to the right of the current one:

(defun split-and-follow-right ()
  "Open a new window horizontally."
  (interactive)
  (split-window-right)
  (other-window 1)
  (ibuffer))

The new functions are bound to the “default” keys for splitting the window.

("C-x 2" . split-and-follow-below)
("C-x 3" . split-and-follow-right)

Balancing window sizes

("C-c b" . balance-windows)

Killing both the buffer and window

I had to adjust the function which kills both the current buffer and the current window, because it did not cooperate with EXWM buffers.

(defun kill-this-buffer-and-window ()
  "Perform `kill-buffer-and-window'.  Altered to accomodate `exwm-mode'."
  (interactive)
  (let ((window-to-delete (selected-window))
        (buffer-to-kill (current-buffer))
        (delete-window-hook (lambda ()
                              (ignore-errors
                                (delete-window)))))
    (unwind-protect
        (progn
          (add-hook 'kill-buffer-hook delete-window-hook t t)
          (if (kill-buffer (current-buffer))
              ;; If `delete-window' failed before, we repeat
              ;; it to regenerate the error in the echo area.
              (when (eq (selected-window) window-to-delete)
                (delete-window)))))))

This function is used in place of kill-buffer-and-window in the stock binds. Originally it was set to C-x C-k, but as it turns out this is a relatively important prefix key for keyboard macros.

("C-x 4 0" . kill-this-buffer-and-window)

Kill all buffers and windows at once

A useful function for cleaning up after messy buffers that get lost is killing all buffers and closing all windows.

(defun kill-all-buffers-and-windows ()
  "Kill all buffers and windows."
  (interactive)
  (when (yes-or-no-p "Really kill all buffers and windows? ")
    (save-some-buffers)
    (mapc 'kill-buffer (buffer-list))
    (delete-other-windows)))

This function is bound in a prefix that I’ve come to like: C-x 4.

("C-x 4 q" . kill-all-buffers-and-windows)

Key binds

I have no clue where else to put these, so here they are I guess.

Change directory using C-c d

(global-set-key (kbd "C-c d") #'cd)

Open Emacs configuration with C-c e

(defun config-visit ()
  "Open the configuration file."
  (interactive)
  (find-file (locate-user-emacs-file "literate-emacs.org")))

(global-set-key (kbd "C-c e") #'config-visit)

Open dotfiles configuration with C-c M-e

(defun literate-dotfiles-visit ()
  "Open the literate dotfiles."
  (interactive)
  (find-file "~/.config/dotfiles/literate-dotfiles.org"))

(when (file-exists-p "~/.config/dotfiles/literate-dotfiles.org")
  (global-set-key (kbd "C-c M-e") #'literate-dotfiles-visit))

Open system configuration with C-c C-M-e

(defun sys-config-visit ()
  "Open the literate system configuration."
  (interactive)
  (find-file "~/.config/dotfiles/literate-sysconfig.org"))

(when (file-exists-p "~/.config/dotfiles/literate-sysconfig.org")
  (global-set-key (kbd "C-c C-M-e") #'sys-config-visit))

Edit files as superuser using C-x C-M-f

This is especially useful when I need to edit system files.

(use-package sudo-edit
  :ensure t
  :defer t
  :bind ("C-x C-M-f" . sudo-edit))

No suspending Emacs on C-z or C-x C-z

Why is this even something bound to begin with? I really dislike this and when I first did it I genuinely thought I broke something.

(global-unset-key (kbd "C-x C-z"))
(global-unset-key (kbd "C-z"))

Making Emacs a good text editor

Emacs is a text editor… right? This used to be a somewhat bigger mess of different sections, but I’ve been working to categorize these settings far better, so much of what was previously elsewhere is now set up in here. Everything in here should be about making Emacs pleasant to use for editing text of various kinds. If it isn’t, I have failed.

Additional major modes

These are modes that enable Emacs to edit different kinds of files differently. Programming major modes are further down, in the programming section.

markdown-mode (bootleg org-mode for GitHub)

I really don’t like Markdown but I have to use it. I don’t configure it since I do so much in Org-mode instead.

(use-package markdown-mode
  :ensure t
  :defer t
  :mode ("\\.md\\'" . markdown-mode))

graphviz-dot-mode (diagram creation)

A nice way to make diagrams. I don’t use it too much, but having it around is reasonably comfortable.

(use-package graphviz-dot-mode
  :ensure t
  :defer t
  :mode ("\\.dot\\'" . graphviz-dot-mode))

Personal save hooks

When I save a file, sometimes I want specific things to be done. These are the functions run during after-save-hook.

Tangle literate programming files

I’ve gotten really into literate programming lately, so this makes it much easier to tangle files. Every time after-save-hook is run, if the filename contains “literate” and is an org-mode file, call org-babel-tangle.

(defun tangle-literate-program ()
  "Tangle a file if it's a literate programming file."
  (interactive)
  (when (string-match-p "literate.*.org$" buffer-file-name)
    (org-babel-tangle)))

(add-hook 'after-save-hook #'tangle-literate-program -100)

Automatically byte-compile Emacs files

This is meant to happen when I save my Emacs configuration, so that all bytecode is up to date. It adds some time to each save, but it is worth it for never having to recompile my Emacs configuration manually again.

(defun byte-compile-config-files ()
  "Byte-compile Emacs configuration files."
  (when (string-match-p "literate-emacs.org" (buffer-file-name))
    (byte-recompile-directory user-emacs-directory 0)))

(add-hook 'after-save-hook #'byte-compile-config-files 100)

General editing

These settings are kinda a “miscellaneous” collection of things I don’t think fit within other categories of this configuration. In due time, I may be able to figure out what to do with these to make it a slightly less disparate collection of things.

Word counter

For getting a general word count.

(global-set-key (kbd "C-=") #'count-words)

Spell-checking

This is just a useful little tool to check spelling while editing a buffer. It’s only configured if aspell is installed. It’s not super great, but it does the trick well enough for me.

(use-package flyspell
  :if (executable-find "aspell")
  :defer t
  :custom ((ispell-program-name "aspell")
           (ispell-dictionary "american"))
  :hook ((flyspell-mode . flyspell-buffer)
         ((prog-mode
           conf-mode) . flyspell-prog-mode)
         (text-mode . flyspell-mode)))

Better search behavior

This search behavior is SO much nicer than the default.

(use-package swiper
  :ensure t
  :defer t
  :bind ("C-s" . swiper))

No backups or auto-saving

I love living on the edge.

(setq backup-inhibited t
      make-backup-files nil
      auto-save-default nil
      auto-save-list-file-prefix nil)

Automatically revert files on change

This way if files get modified in the middle of editing them, I don’t overwrite the changes. This can also change dired and ibuffer buffers if I am not mistaken. However, I don’t need to hear every last thing about it.

(use-package autorevert
  :defer t
  :init
  (global-auto-revert-mode 1)
  :custom ((global-auto-revert-non-file-buffers t)
           (auto-revert-remote-files t)
           (auto-revert-verbose nil)))

End-of-file newlines and indent tabs

Screw indent tabs, spaces all the way. Also, if there is no end-of-file newline, add it. Things that help me keep my files nice and clean. Tabs should probably also be significantly less wide.

(setq require-final-newline t)
(setq-default indent-tabs-mode nil
              tab-width 4)

Manage the kill ring using a pop-up menu

Having the whole kill ring easy to scroll through is much less hassle than default behavior. We also set up some yanking behavior while we’re at it.

(use-package popup-kill-ring
  :ensure t
  :defer t
  :custom ((save-interprogram-paste-before-kill t)
           (mouse-drag-copy-region t)
           (mouse-yank-at-point t))
  :bind ("M-y" . popup-kill-ring))

Delete whatever is selected if typing starts

This is to reflect behavior in other programs.

(delete-selection-mode 1)

Hungrily remove all whitespace when deleting

This saves me tons of time when it comes to managing whitespace. Instead of having to repeatedly press delete or backspace, a single keystroke decimates all the whitespace between the point and whatever is in the direction the deletion happens.

(use-package hungry-delete
  :ensure t
  :defer t
  :init
  (global-hungry-delete-mode 1))

Move around visible portions of files faster

If I want to hop around in a document without calling swiper, avy is definitely the way to go. I admittedly don’t use it too much, but it definitely has its place when editing text.

(use-package avy
  :ensure t
  :defer t
  :bind ("M-s" . avy-goto-char))

Move between SubWords as well as between words

This allows for much easier navigation between words when in programming language buffers, but also has utility outside of programming so it’s enabled globally.

(global-subword-mode 1)

electric-pair-mode (OH MY GOD THIS IS SO GREAT)

I have no words for how convenient this has been and how much faster I get things done thanks to these six lines of elisp.

(use-package elec-pair
  :defer t
  :init
  (electric-pair-mode 1)
  (minibuffer-electric-default-mode 1)
  :custom (electric-pair-pairs '((?\{ . ?\})
                                 (?\( . ?\))
                                 (?\[ . ?\])
                                 (?\" . ?\"))))

Programming

It’s slowly growing, but I still truly do not need all that much when it comes to programming, mostly because I don’t actually do all that much programming outside what I do for fun… and editing this file.

Git

I used to use a terminal for version control, but this is a lot easier, a lot faster, and a whole lot nicer to use overall. It binds to C-x g by default but only if you load the package right away, so I force it to be bound here.

(use-package magit
  :ensure t
  :defer t
  :bind ("C-x g" . magit-status))

Haskell

I have started to mess around with Haskell, so I needed to grab a mode for that. This supplies basically everything I need as far as I know, e.g. company autocomplete and flycheck information.

(use-package haskell-mode
  :ensure t
  :defer t
  :custom (haskell-stylish-on-save t)
  :hook ((haskell-mode . interactive-haskell-mode)
         (haskell-mode . haskell-doc-mode)
         (haskell-mode . haskell-indentation-mode)
         (haskell-mode . haskell-auto-insert-module-template)))

Common Lisp

SBCL seems to be the typical Common Lisp implementation people use, so using it seems to be the best thing to do.

(use-package lisp-mode
  :defer t
  :custom (inferior-lisp-program "sbcl"))

Python autocomplete

(use-package company-jedi
  :after company
  :ensure t
  :defer t
  :init
  (add-to-list 'company-backends 'company-jedi))

On-the-fly syntax checking

This is nice to have so I can be told right away when something’s wrong.

(use-package flycheck
  :ensure t
  :defer t
  :hook (prog-mode . flycheck-mode))

On-the-fly Emacs package linting

Now that I’m dabbling in writing Emacs packages, I need to be able to lint packages more thoroughly. Adding a linter specifically for packages is very comfy and cool and good.

(use-package flycheck-package
  :after flycheck
  :ensure t
  :defer t
  :init
  (flycheck-package-setup))

Move flycheck issues out of the minibuffer

I want errors to be in their own area, not polluting the minibuffer.

(use-package flycheck-posframe
  :if window-system
  :after flycheck
  :ensure t
  :defer t
  :custom ((posframe-mouse-banish nil)
           (flycheck-posframe-position 'window-bottom-left-corner))
  :hook ((flycheck-mode . flycheck-posframe-mode)
         (flycheck-posframe-mode . flycheck-posframe-configure-pretty-defaults)))

avy-style navigation but between syntax errors

This one is SUPER COOL. Being able to jump straight to a problem is comfy.

(use-package avy-flycheck
  :after flycheck
  :ensure t
  :defer t
  :bind (:map prog-mode-map
         ("C-c C-'" . avy-flycheck-goto-error)))

Org-mode

As I spend more time in Org-mode, the more I need from it.

(use-package org
  :defer t
  :init
  (pdumper-require 'org)
  <<org-init>>
  <<org-custom>>
  <<org-hook>>
  <<org-binds>>)

Table of Contents

This automates creating the table of contents for an Org-mode document. It also works in markdown-mode too if I ever have to use Markdown. The table of contents for this configuration is one such table of contents from this package.

(use-package toc-org
  :ensure t
  :defer t
  :hook ((org-mode . toc-org-mode)
         (markdown-mode . toc-org-mode)))

Fancier bullet points

It’s kinda slow, but bullet points are very nice, better than asterisks. It makes the document look much cleaner overall, and gives the bullet points more space away from the start of the line as they get deeper and deeper.

(use-package org-bullets
  :if window-system
  :ensure t
  :defer t
  :hook (org-mode . org-bullets-mode))

Presentations in Emacs

It’s gonna need more polish, but it works for what I need it to do.

(use-package epresent
  :if window-system
  :ensure t
  :defer t
  :bind (:map org-mode-map
         ("C-c r" . epresent-run)))

Quality-of-life settings

These are just quick things that make org-mode much more visually pleasing and much easier to use. Other settings are pulled into here for a cleaner tangle.

:custom ((org-pretty-entities t)
         (org-src-fontify-natively t)
         (org-agenda-use-time-grid nil)
         (org-fontify-done-headline t)
         (org-src-tab-acts-natively t)
         (org-enforce-todo-dependencies t)
         (org-fontify-whole-heading-line t)
         (org-agenda-skip-deadline-if-done t)
         (org-agenda-skip-scheduled-if-done t)
         (org-fontify-quote-and-verse-blocks t)
         (org-src-window-setup 'current-window)
         (org-highlight-latex-and-related '(latex))
         (org-ellipsis (if window-system "" "..."))
         (org-hide-emphasis-markers window-system)
         <<org-vars>>)

Evaluating Graphviz blocks

Since obviously dot snippets are purely harmless as far as I know, I just don’t bother with having to confirm evaluation every time I try to update a graphic.

(org-babel-do-load-languages 'org-babel-load-languages '((dot . t)))

Execute some code without having to confirm

Since obviously dot snippets are purely harmless as far as I know, I just don’t bother with having to confirm evaluation every time I try to update a graphic. I also don’t need to confirm evaluation of snippets in use in my literate files.

(defun farl-org/confirm-babel-evaluate (lang body)
  "Don't ask to evaluate graphviz blocks or literate programming blocks."
  (not (or (string= lang "dot")
           (string-match-p "literate.*.org$" buffer-file-name))))

The variable for confirming whether to evaluate a block is set alongside the other quality-of-life settings listed above.

(org-confirm-babel-evaluate #'farl-org/confirm-babel-evaluate)

Automatically fix inline images generated by code blocks

Since some code will generate images as their result, it is important for those images to be shown after executing the code.

(org-babel-after-execute . org-redisplay-inline-images)

Shortcuts for various snippets

First, we load org-tempo, the extension that allows the old way of doing things, and add it to org-modules. Then, we add shortcuts for the individual blocks of code. Finally, we can add shortcuts for other items that aren’t blocks. I’ve grown somewhat fond of this way of organizing my shortcuts, because it separates the blocks from the one-liners.

(use-package org-tempo
  :defer t
  :init
  (add-to-list 'org-modules 'org-tempo)
  :custom ((org-structure-template-alist '(;; General blocks
                                           ("c" . "center")
                                           ("C" . "comment")
                                           ("e" . "example")
                                           ("q" . "quote")
                                           ("v" . "verse")

                                           ;; Export blocks
                                           ("a"   . "export ascii")
                                           ("h"   . "export html")
                                           ("css" . "export css")
                                           ("l"   . "export latex")

                                           ;; Code blocks
                                           ("s"   . "src")
                                           ("sh"  . "src sh")
                                           ("cf"  . "src conf")
                                           ("cu"  . "src conf-unix")
                                           ("cs"  . "src conf-space")
                                           ("cx"  . "src conf-xdefaults")
                                           ("cjp" . "src conf-javaprop")
                                           ("el"  . "src emacs-lisp")
                                           ("py"  . "src python")
                                           ("dot" . "src dot :file")
                                           ("txt" . "src text :tangle")))
           (org-tempo-keywords-alist '(;; Title/subtitle/author
                                       ("t"  . "title")
                                       ("st" . "subtitle")
                                       ("au" . "author")

                                       ;; Language
                                       ("la" . "language")

                                       ;; Name/caption
                                       ("n"  . "name")
                                       ("ca" . "caption")

                                       ;; Property/options/startup
                                       ("p"  . "property")
                                       ("o"  . "options")
                                       ("su" . "startup")

                                       ;; Other
                                       ("L" . "latex")
                                       ("H" . "html")
                                       ("A" . "ascii")
                                       ("i" . "index")))))

Don’t give angle brackets syntax

For some reason, starting with org-mode 9.3 or so, all symbols that are brackets, i.e. {}, (), <>, are given syntax as pairs. This isn’t a problem on its own (especially since it makes quotations and parentheses far easier to work with), but angle brackets specifically cause issues since they specifically are inequality operators in my books and < is the prefix for the shortcuts provided by org-tempo.

(defun farl-org/disable-angle-bracket-syntax ()
  "Disable angle bracket syntax."
  (modify-syntax-entry ?< ".")
  (modify-syntax-entry ?> "."))

This function is hooked in org-mode-hook. Other hooks are pulled into here for a significantly cleaner tangle.

:hook ((org-mode . farl-org/disable-angle-bracket-syntax)
       <<org-hooks>>)

Agenda (only enabled if an agenda is found)

I store my agendas in $HOME/agendas.

(defun open-agenda-file ()
  "Open the agenda file."
  (interactive)
  (find-file (ivy-read
              "Open agenda: "
              (all-completions "" org-agenda-files))))

The agendas are added to a list stored in the variable org-agenda-files.

(org-agenda-files (when (file-directory-p "~/agendas")
                    (directory-files-recursively
                     "~/agendas" ".org$" nil)))

I open the agenda with C-c M-a and open a specific agenda file with C-c s-a. These are the only binds done in this entire section.

:bind (("C-c s-a" . open-agenda-file)
       ("C-c M-a" . org-agenda))

Making Emacs more than an editor

Emacs is also more than just an editor, right? There is a ton more that Emacs can do than just edit my text, and I want to take advantage of that. If it isn’t about editing text but also isn’t a major thing, it will probably be found in here.

Minimally configured

These are features which require minimal configuration. Some may be more important than others, but these are mostly minimally configured features.

Calendar

I don’t use the calendar for too much, but having it bound is nice. Also important is that weeks start on Monday. I’ve thought of trying to set up more when it comes to the calendar, but I don’t know if it’s really worth it.

(setq calendar-week-start-day 1)
(global-set-key (kbd "C-c l") #'calendar)

Reading ebooks

Not the best way to do epub reading, but at least it’s in Emacs.

(use-package nov
  :ensure t
  :defer t
  :custom (nov-text-width 80)
  :mode ("\\.epub\\'" . nov-mode))

File management

The only thing I don’t like about dired is that it doesn’t list directories first by default. That is done by changing the switches given to ls when listing contents of a directory. I also prefer being able to edit file permissions through dired.

(use-package wdired
  :defer t
  :custom ((dired-listing-switches "-alh --group-directories-first")
           (wdired-allow-to-change-permissions t)))

Terminal emulator

I’ve jumped between ansi-term and vterm over and over and over but it seems like the best path is to just stick to ansi-term, thanks to some of the more useful stuff it can do. I don’t really like that it doesn’t automatically use my default shell, so I wrote my way around that with advice.

(use-package term
  :defer t
  :init
  (defun farl-term/use-shell (force-bash)
    "Force `term' to use the default shell, ignoring FORCE-BASH."
    (interactive (list (getenv "SHELL"))))
  (advice-add 'ansi-term :before #'farl-term/use-shell)
  :bind ("C-c t" . ansi-term))

Manual page reader

Wow, there’s actually an Emacs mode for this! I put these into the C-h binds, since it is a way of getting help, after all. If for some reason man isn’t working, woman can still grab a manpage without calling man.

(global-set-key (kbd "C-h 4 m") #'man)
(global-set-key (kbd "C-h 4 w") #'woman)

Weather information

Picking a service to use for this was a pain. I ended up settling for wttrin because it is the fastest and easiest to use, and plays nice with my setup.

(use-package wttrin
  :ensure t
  :defer t
  :custom (wttrin-default-cities '("Indianapolis"))
  :bind ("C-c w" . wttrin))

System package management

This one is a pleasant surprise to have honestly. Having Emacs handle system packages as well as its own makes life a million times easier. Since I use yay on Arch, I configure an entry for it and use it if it’s installed.

(use-package system-packages
  :ensure t
  :defer t
  :init
  (when (executable-find "yay")
    (pdumper-require 'system-packages)
    (add-to-list 'system-packages-supported-package-managers
                 '(yay .
                       ((default-sudo . nil)
                        (install . "yay -S")
                        (search . "yay -Ss")
                        (uninstall . "yay -Rs")
                        (update . "yay -Syu")
                        (clean-cache . "yay -Sc")
                        (log . "car /var/log/pacman.log")
                        (get-info . "yay -Qi")
                        (get-info-remote . "yay -Si")
                        (list-files-provided-by . "yay -Ql")
                        (verify-all-packages . "yay -Qkk")
                        (verify-all-dependencies . "yay -Dk")
                        (remove-orphaned . "yay -Rns $(yay -Qtdq)")
                        (list-installed-packages . "yay -Qe")
                        (list-installed-packages-all . "yay -Q")
                        (list-dependencies-of . "yay -Qi")
                        (noconfirm . "--noconfirm"))))
    (setq system-packages-use-sudo nil
          system-packages-package-manager 'yay))
  :custom (system-packages-noconfirm t)
  :bind (("C-c p i" . system-packages-install)
         ("C-c p e" . system-packages-ensure)
         ("C-c p u" . system-packages-update)
         ("C-c p r" . system-packages-uninstall)
         ("C-c p o" . system-packages-remove-orphaned)
         ("C-c p c" . system-packages-clean-cache)
         ("C-c p l" . system-packages-log)
         ("C-c p s" . system-packages-search)
         ("C-c p g" . system-packages-get-info)
         ("C-c p d" . system-packages-list-dependencies-of)
         ("C-c p f" . system-packages-list-files-provided-by)
         ("C-c p p" . system-packages-list-installed-packages)
         ("C-c p f" . system-packages-verify-all-dependencies)
         ("C-c p v" . system-packages-verify-all-packages)))

Multimedia system

I am big on doing as much in Emacs as possible. Having my music player moved to Emacs was a HUGE step. When I first started using it, it was weird, but now I have come to absolutely love it. It’s only configured if mpd is found.

(use-package emms
  :if (executable-find "mpd")
  :ensure t
  :defer t
  :init
  <<emms-init>>
  <<emms-vars>>
  <<emms-keys>>)

Loading

For the package to be properly configured, first everything about it has to be properly loaded. This loads the necessary files to ensure emms starts properly, and configures it to use any music player it finds.

(pdumper-require 'emms-setup)
(require 'emms-player-mpd)
(emms-all)

Functions

In order to directly control mpd, some functions have to be defined. A fourth function is included to fix up a behavior I don’t really like.

Starting the daemon

(defun mpd/start-music-daemon ()
  "Start MPD, connect to it and sync the metadata cache"
  (interactive)
  (shell-command "mpd")
  (mpd/update-database)
  (emms-player-mpd-connect)
  (emms-cache-set-from-mpd-all)
  (message "MPD started!"))

Stopping the daemon

(defun mpd/kill-music-daemon ()
  "Stop playback and kill the music daemon."
  (interactive)
  (emms-stop)
  (call-process "killall" nil nil nil "mpd")
  (message "MPD killed!"))

Updating the database

(defun mpd/update-database ()
  "Update the MPD database synchronously."
  (interactive)
  (call-process "mpc" nil nil nil "update")
  (message "MPD database updated!"))

Shuffling the playlist

(defun farl-emms/shuffle-with-message ()
  "Shuffle the playlist and say so in the echo area."
  (interactive)
  (emms-shuffle)
  (message "Playlist has been shuffled."))

Keybindings

Making a keymap was a mistake. This is so much comfier and looks a lot nicer when using which-key, and does not require the creation of an entire keymap.

:bind (;; Opening playlist and music browser
       ("C-c a v" . emms)
       ("C-c a b" . emms-smart-browse)

       ;; Track navigation
       ("C-c a C-n" . emms-next)
       ("C-c a C-p" . emms-previous)
       ("C-c a p" . emms-pause)
       ("C-c a C-s" . emms-stop)

       ;; Repeat/shuffle
       ("C-c a C-r" . emms-toggle-repeat-track)
       ("C-c a r" . emms-toggle-repeat-playlist)
       ("C-c a s" . farl-emms/shuffle-with-message)

       ;; Refreshing the emms cache
       ("C-c a c" . emms-player-mpd-update-all-reset-cache)

       ;; mpd related functions
       ("C-c a d" . mpd/start-music-daemon)
       ("C-c a q" . mpd/kill-music-daemon)
       ("C-c a u" . mpd/update-database))

Configuring

This is where emms is configured to use mpd.

:custom ((emms-seek-seconds 5)
         (emms-player-list '(emms-player-mpd))
         (emms-info-functions '(emms-info mpd))
         (emms-completing-read #'ivy-completing-read)
         (emms-player-mpd-server-name "localhost")
         (emms-player-mpd-server-port "6601"))

A couple environment variables are also set.

(setenv "MPD_HOST" "localhost")
(setenv "MPD_PORT" "6601")

Games

I also use Emacs to play games and do other fun things. Using swiper means I don’t need too many search functions bound, so I can bind the games prefix to C-c g without breaking too much of my workflow.

(global-unset-key (kbd "C-c g"))

Yahtzee

Fun dice game. Now I can get mad at Emacs instead of my sister.

(use-package yahtzee
  :ensure t
  :defer t
  :bind ("C-c g y" . yahtzee))

Sudoku

I love sudoku puzzles.

(use-package sudoku
  :ensure t
  :defer t
  :bind ("C-c g s" . sudoku))

Tetris

Tetris is my childhood. No way I wouldn’t set it up to be nice and comfy.

(use-package tetris
  :defer t
  :bind (("C-c g t" . 'tetris)
         :map tetris-mode-map
         ("w" . tetris-move-bottom)
         ("a" . tetris-move-left)
         ("s" . tetris-mode-down)
         ("d" . tetris-move-right)
         ([left] . tetris-rotate-next)
         ([right] . tetris-rotate-prev)
         ([?\t] . tetris-pause-game)
         ("r" . tetris-start-game)
         ("e" . tetris-end-game)))

Chess

Just for fun. I suck at chess but it’s nice to have.

(use-package chess
  :ensure t
  :defer t
  :bind ("C-c g c" . chess))

2048

A simple and fun game. Was a big deal when I was in high school. I still play it from time to time, to pass the time and remember my powers of 2.

(use-package 2048-game
  :ensure t
  :defer t
  :bind ("C-c g 2" . 2048-game))

Making Emacs a desktop environment

Yes, you read that header right. Emacs can be my entire desktop environment. It manages and launches X applications, it manages input for these windows, it controls the volume and backlight level, it manages my wallpapers, and it does a hell of a lot more as well. You should probably remove this section if you don’t plan to use Emacs as your desktop environment.

Including it doesn’t have any disadvantages though, since it only loads if an environment variable _RUN_EXWM exists, which it promptly unsets. Make a note of this when writing your .xinitrc or writing a .desktop file to load Emacs as your desktop environment. EXWM should replace the current window manager.

(use-package exwm
  :if (getenv "_RUN_EXWM")
  :ensure t
  :defer t
  :init
  (setenv "_RUN_EXWM")
  <<exwm-init>>
  :custom ((exwm-replace t)
           <<exwm-vars>>)
  <<exwm-hook>>
  <<exwm-bind>>)

X window management

These settings specifically relate to how X application buffers are handled by EXWM. It does a very good job of managing windows, so very little has to be done here to get it to my personal tastes.

(pdumper-require 'exwm)
(pdumper-require 'exwm-xim)
(pdumper-require 'exwm-randr)
(pdumper-require 'exwm-config)
(pdumper-require 'exwm-systemtray)

Name EXWM buffers after the window title

This was annoying when I first installed EXWM. Thankfully it’s easy to fix.

(defun farl-exwm/name-buffer-after-window-title ()
  "Rename the current `exwm-mode' buffer after the X window's title."
  (exwm-workspace-rename-buffer exwm-title))

We hook setting the buffer name into when EXWM picks up a change in the window title, aptly titled exwm-update-title-hook. Other hooks are pulled into this block for a cleaner tangle.

:hook ((exwm-update-title . farl-exwm/name-buffer-after-window-title)
       <<exwm-hooks>>)

Configure floating window borders

Uses the same color as my mode line, uses the same width as window dividers. For whatever reason, when changed via customize-set-variable these will break statup.

(setq exwm-floating-border-width window-divider-default-right-width
      exwm-floating-border-color (face-background 'mode-line))

Workspace configuration

There is much more needed to get workspaces properly configured than to set up window management. Each workspace tries to load on a given monitor, and will otherwise load on the primary (or first) monitor. Windows can be moved into different workspaces, and windows can also be automatically spawned on a given workspace rather than the current one.

Name workspaces intuitively

The variable farl-exwm/workspace-names is used to provide a list of workspace names to be used by the function that follows.

(defvar farl-exwm/workspace-names '("" "" "" "" ""
                                    "" "" "" "" "")
  "The names assigned to workspaces through `exwm-workspace-index-map'.")

The function farl-exwm/workspace-index-map returns the name of the currently selected workspace by the value in farl-exwm/workspace-names.

(defun farl-exwm/workspace-index-map (index)
  "Return either a workspace name for a given INDEX or INDEX itself."
  (or (elt farl-exwm/workspace-names index) index))

The variable exwm-workspace-index-map points to the function used to determine the names of workspaces.

(exwm-workspace-index-map #'farl-exwm/workspace-index-map)

Persistent list of workspaces

Because I now use so many workspaces, I need to be able to see what workspace I am currently on. This makes it easier to do that. It’s rather buggy at times, but it does what it needs to do. A function is used to grab the current state of the workspaces.

(use-package minibuffer-line
  :ensure t
  :defer t
  :init
  (defun farl-exwm/list-workspaces ()
    "List EXWM workspaces."
    (exwm-workspace--update-switch-history)
    (elt exwm-workspace--switch-history
         (exwm-workspace--position exwm-workspace--current)))
  :custom-face (minibuffer-line ((t (:inherit default))))
  :custom (minibuffer-line-format '((:eval (farl-exwm/list-workspaces))))
  :hook ((exwm-init . minibuffer-line-mode)
         (exwm-workspace-switch . minibuffer-line--update)))

Load all workspaces on startup

I do not want to have to load all of them individually on my own…

(exwm-workspace-number 10)

Assign workspaces to monitors

This section is only to ensure the proper workspaces are placed on the right monitors when my W541 is docked.

(exwm-randr-workspace-monitor-plist '(0 "DP2-1"
                                      1 "DP2-2"
                                      2 "DP2-3"
                                      3 "DP2-1"
                                      4 "DP2-2"
                                      5 "DP2-3"
                                      6 "DP2-1"
                                      7 "DP2-2"
                                      8 "DP2-3"
                                      9 "DP2-1"))

Assign programs to workspaces

…and also have some launch floating and/or without a mode line or borders.

(exwm-manage-configurations '(((string= exwm-class-name "Steam")
                               workspace 9
                               floating t)
                              ((string= exwm-class-name "hl2_linux")
                               floating-mode-line nil)
                              ((string= exwm-class-name "TelegramDesktop")
                               workspace 8)
                              ((string= exwm-class-name "discord")
                               workspace 7)
                              ((or (string-match-p "libreoffice"
                                                   exwm-class-name)
                                   (string= exwm-class-name "MuseScore3")
                                   (string= exwm-class-name "Gimp"))
                               workspace 6)
                              ((string= exwm-title "Event Tester")
                               floating-mode-line nil
                               floating t)))

Cycle forward and backward workspaces

In order to prevent the creation of additional workspaces, moving too far forward or back is prevented. This allows easier movement between workspaces by their relation to other workspaces, rather than having to pick a specific number.

(defun farl-exwm/workspace-next ()
  "Move forward one workspace."
  (interactive)
  (if (< exwm-workspace-current-index (1- exwm-workspace-number))
      (exwm-workspace-switch (1+ exwm-workspace-current-index))
    (message "No next workspace.")))

(defun farl-exwm/workspace-prev ()
  "Move to the previous workspace."
  (interactive)
  (if (> exwm-workspace-current-index 0)
      (exwm-workspace-switch (1- exwm-workspace-current-index))
    (message "No previous workspace.")))

Multi-head configuration

Thankfully, EXWM comes with hooks to handle when monitors are connected and disconnected, so I can do monitor configuration entirely in Emacs Lisp. I have two laptops: a ThinkPad X230 and a ThinkPad W541. Each has different displays and is used for different purposes.

Getting the currently connected monitors

The first thing to do is set up a function to return a list of currently connected monitors.

(defun get-connected-monitors ()
  "Return a list of the currently connected monitors."
  (split-string
   (shell-command-to-string
    "xrandr | grep ' connected ' | awk '{print $1}'")))

Configuring monitor arrangement on my X230

This one is straightforward. I never do any kind of split-monitor setup on my ThinkPad X230, so every monitor looks over the same screen.

(defun display-setup-x230 ()
  "Set up the connected monitors on a ThinkPad X230."
  (let ((monitors (get-connected-monitors))
        (possible '("LVDS1"
                    "VGA1")))
    (dolist (monitor possible)
      (if (member monitor monitors)
          (start-process "xrandr" nil "xrandr"
                         "--output" monitor
                         "--mode" "1366x768"
                         "--pos" "0x0")
        (start-process "xrandr" nil "xrandr"
                       "--output" monitor
                       "--off")))))

Configuring monitor arrangement on my W541

This is where it gets really fun. This ThinkPad does get docked, so I handle very different outputs.

(defun display-setup-w541 ()
  "Set up the connected monitors on a ThinkPad W541."
  (let* ((connected-monitors (get-connected-monitors))
         (docked-p (member "DP2-1" connected-monitors))
         (possible-monitors '("eDP1"
                              "VGA1"
                              "DP2-1"
                              "DP2-2"
                              "DP2-3")))
    (dolist (monitor possible-monitors)
      (if (and (member monitor connected-monitors)
               (not (and docked-p (string= "eDP1" monitor))))
          (progn
            (start-process "xrandr" nil "xrandr"
                           "--output" monitor
                           ;; Any enabled monitor needs a resolution.
                           "--mode" "1920x1080"
                           ;; DP2-1 and DP2-3 are rotated.
                           "--rotate" (if (string= "DP2-2" monitor)
                                          "left"
                                        (if (string= "DP2-3" monitor)
                                            "right"
                                          "normal"))
                           ;; Every enabled monitor needs a position.
                           "--pos" (if (string-match-p "DP2-3" monitor)
                                       "3000x0"
                                     (if (string-match-p "DP2-1" monitor)
                                         "1080x0"
                                       "0x0")))
            ;; Setting a monitor as primary occurs outside enabling it.
            ;; This is due to how `start-process' takes arguments.
            (when (or (string= "DP2-1" monitor)
                      (string= "eDP1" monitor))
              (start-process "xrandr" nil "xrandr"
                             "--output" monitor
                             "--primary")))
        (start-process "xrandr" nil "xrandr"
                       "--output" monitor
                       "--off")))))

Configuring peripherals while docked

Because I use a dock on my W541, there are some things I need to do alongside setting up my monitors.

(defun peripheral-setup ()
  "Configure peripherals I connect to my dock."
  (interactive)
  ;; Trackball
  (let ((trackball-id (shell-command-to-string
                       (concat "xinput | grep ELECOM | head -n 1 "
                               "| sed -r 's/.*id=([0-9]+).*/\\1/' | "
                               "tr '\\n' ' '"))))
    (start-process-shell-command
     "Trackball Setup" nil (concat "xinput set-prop " trackball-id
                                   "'libinput Button Scrolling Button' "
                                   "10"))
    (start-process-shell-command
     "Trackball Setup" nil (concat "xinput set-prop " trackball-id
                                   "'libinput Scroll Method Enabled' "
                                   "0 0 1"))
    (start-process-shell-command
     "Trackball Setup" nil (concat "xinput set-button-map " trackball-id
                                   "1 2 3 4 5 6 7 8 9 2 1 2")))
  ;; Keyboard
  (start-process "Keyboard Setup" nil "setxkbmap"
                 "-option" "ctrl:nocaps"))

Bringing it all together

Finally, I can make my generic display-and-dock setup function.

(defun display-and-dock-setup ()
  "Configure displays and dock if applicable."
  (interactive)
  (unless (get-process "Monitor Settings")
    (if (member "LVDS1" (get-connected-monitors))
        (display-setup-x230)
      (progn
        (display-setup-w541)
        (peripheral-setup)))))

Every time EXWM detects a change in the monitors connected or active, this function should be called, so it’s hooked to exwm-randr-screen-change-hook.

(exwm-randr-screen-change . display-and-dock-setup)

X applications

In order to create key bindings to launch X applications, first functions must be written which launch these X applications.

GIMP

Until GIMP’s functionality gets merged into Emacs, guess I’m stuck with it.

(defun run-gimp ()
  "Start GIMP."
  (interactive)
  (start-process "GIMP" nil "gimp"))

Steam

Gaming is possible with EXWM, if you run games windowed. I used to run it floating, but honestly just having it tile is so much easier to manage.

(defun run-steam ()
  "Start Steam."
  (interactive)
  (start-process "Steam" nil "steam"))

Firefox

Firefox has some unique abilities when it comes to how to make windows behave which work better for me. I don’t use tabs, and I don’t want anything to do with them, and Firefox lets me hide the tab bar and force all tabs to actually open as new windows. It’s like Suckless Surf, but not made by Nazis orders of magnitude faster and more comfortable and has a proper address bar.

(defun run-firefox ()
  "Start Firefox."
  (interactive)
  (start-process "Firefox" nil "firefox"))

Discord

It’s kinda trashy but my friends use it.

(defun run-discord ()
  "Start Discord."
  (interactive)
  (start-process "Discord" nil "discord"))

Telegram

Another trashy messenger my friends use.

(defun run-telegram ()
  "Start Telegram."
  (interactive)
  (start-process "Telegram" nil "telegram-desktop"))

MuseScore

I haven’t figured out how to engrave in Emacs, so for now…

(defun run-musescore ()
  "Start MuseScore."
  (interactive)
  (start-process "MuseScore" nil "musescore"))

LibreOffice

Shame me all you want.

(defun run-libreoffice ()
  "Start LibreOffice."
  (interactive)
  (start-process "LibreOffice" nil "libreoffice"))

Transmission

(defun run-transmission ()
  "Start Transmission."
  (interactive)
  (start-process "Transmission" nil "transmission-gtk"))

DE components

Of course, window management isn’t all you need for a complete desktop environment. Functions to provide more key binds for vital settings and other fun goodies are included in this section.

desktop-environment-mode

Previously I had to define a lot of functions to do these things, now I just change settings within desktop-environment-mode.

The way desktop-environment-mode passes its keys through EXWM’s mode is one of two options: either the keys are directly bound to exwm-mode-map, or the keys are added to EXWM’s prefix key set. I prefer the latter, because it means the keys associated with desktop-environment-mode will be properly unbound when the mode is toggled off.

(use-package desktop-environment
  :ensure t
  :defer t
  :init
  <<de-init>>
  :custom ((desktop-environment-update-exwm-global-keys :prefix)
           <<de-vars>>)
  :hook (exwm-init . desktop-environment-mode)
  :bind (:map desktop-environment-mode-map
         <<de-binds>>))

Brightness adjustment

This one is the simplest: all I needed to do was change the increment and decrement values.

(desktop-environment-brightness-normal-increment "5%+")
(desktop-environment-brightness-normal-decrement "5%-")

Volume adjustment

The only things I really don’t like about how desktop-environment’s volume controlling is desktop-environment-toggle-mute, which gives way too much output when you mute or unmute the speakers or microphone, so I set up basic scripts to give much more concise output.

(desktop-environment-volume-toggle-command
 (concat "[ \"$(amixer set Master toggle | grep off)\" ] "
         "&& echo Volume is now muted. | tr '\n' ' ' "
         "|| echo Volume is now unmuted. | tr '\n' ' '"))
(desktop-environment-volume-toggle-microphone-command
 (concat "[ \"$(amixer set Capture toggle | grep off)\" ] "
         "&& echo Microphone is now muted. | tr '\n' ' ' "
         "|| echo Microphone is now unmuted | tr '\n' ' '"))

Lock screen

Haha yes, this is very long and very very stupid.

(desktop-environment-screenlock-command
 (concat "i3lock -nk --color=000000 --timecolor=ffffffff "
         "--datecolor=ffffffff --wrongcolor=ffffffff "
         "--ringcolor=00000000 --insidecolor=00000000 "
         "--keyhlcolor=00000000 --bshlcolor=00000000 "
         "--separatorcolor=00000000 --ringvercolor=00000000 "
         "--insidevercolor=00000000 --linecolor=00000000 "
         "--ringwrongcolor=00000000 --insidewrongcolor=00000000 "
         "--timestr=%H:%M --datestr='%a %d %b' --time-font=Iosevka "
         "--date-font=Iosevka --wrong-font=Iosevka --timesize=128 "
         "--datesize=64 --wrongsize=32 --time-align 0 --date-align 0 "
         "--wrong-align 0 --indpos=-10:-10 --timepos=200:125 "
         "--datepos=200:215 --wrongpos=200:155 --locktext='' "
         "--lockfailedtext='' --noinputtext='' --veriftext='' "
         "--wrongtext='WRONG' --force-clock --radius 1 --ring-width 1 "
         "--pass-media-keys --pass-screen-keys --pass-power-keys "))

I also have to bind an extra key for this function.

("<XF86ScreenSaver>" . desktop-environment-lock-screen)

Screenshots

This one was the least straightforward because the way it’s implemented by desktop-environment is SUPER wonky. Here are the binds:

("<print>" . farl-de/screenshot-part-clip)
("<S-print>" . farl-de/screenshot-clip)
("<C-print>" . farl-de/screenshot-part)
("<C-S-print>" . farl-de/screenshot)

First, I set what directory to store screenshots in.

(desktop-environment-screenshot-directory "~/screenshots")

Then, I can set the commands for taking a full or partial screenshot and saving it to a file.

(desktop-environment-screenshot-command
 "FILENAME=$(date +'%Y-%m-%d-%H:%M:%S').png && maim $FILENAME")
(desktop-environment-screenshot-partial-command
 "FILENAME=$(date +'%Y-%m-%d-%H:%M:%S').png && maim -s $FILENAME")

The functions which desktop-environment comes with are kinda garbage, so I made my own to replace them.

(defun farl-de/screenshot ()
  "Take a screenshot and store it in a file."
  (interactive)
  (desktop-environment-screenshot)
  (message "Screenshot saved in ~/screenshots."))

(defun farl-de/screenshot-part ()
  "Take a capture of a portion of the screen and store it in a file."
  (interactive)
  (desktop-environment-screenshot-part)
  (message "Screenshot saved in ~/screenshots."))

(defun farl-de/screenshot-clip ()
  "Take a screenshot and put it in the clipboard."
  (interactive)
  (shell-command
   (concat desktop-environment-screenshot-command
           " && xclip $FILENAME -selection clipboard "
           "-t image/png &> /dev/null && rm $FILENAME"))
  (message "Screenshot copied to clipboard."))

(defun farl-de/screenshot-part-clip ()
  "Take a shot of a portion of the screen and put it in the clipboard."
  (interactive)
  (shell-command
   (concat desktop-environment-screenshot-partial-command
           " && xclip $FILENAME -selection clipboard "
           "-t image/png &> /dev/null && rm $FILENAME"))
  (message "Screenshot copied to clipboard."))

Setting the wallpaper

I’ve been working on an easy way to configure wallpapers which makes for way less hassle. It only relies on feh as a backend for applying wallpapers, so if you use Emacs as a daemon it can manage your wallpapers even if it isn’t the window manager. It’s on MELPA now!

(use-package wallpaper
  :ensure t
  :defer t
  :hook ((exwm-randr-screen-change . wallpaper-set-wallpaper)
         (exwm-init . wallpaper-cycle-mode)))

Monitor settings

Calling arandr to adjust monitors is useful when I am preparing to present something using my computer or need to adjust how monitors are set up in a unique way that isn’t a preset from my dotfiles.

(defun monitor-settings ()
  "Open arandr to configure monitors."
  (interactive)
  (start-process "Monitor Settings" nil "arandr"))

Network settings

This one uses two windows: one to open the NetworkManager connection editor, and another to list WiFi networks nearby.

(defun network-settings ()
  "Open a NetworkManager connection editor."
  (interactive)
  (start-process "Network Settings" nil "nm-connection-editor")
  (async-shell-command "nmcli dev wifi list" "*Wi-Fi Networks*"))

Volume mixer

For when you need to do volume mixing.

(defun volume-settings ()
  "Open pavucontrol to adjust volume."
  (interactive)
  (start-process "Volume Mixer" nil "pavucontrol"))

Audio loop-back

Used when I play Jackbox Party Pack with friends. Also set up to launch pavucontrol to set up which programs to pass through to Discord.

(defun audio-loopback ()
  "Loop desktop audio into a null sink alongside the primary input."
  (interactive)
  (dolist (command
           '(;; Create null sink `loop'
             "load-module module-null-sink sink_name=loop"
             "update-sink-proplist loop device.description=loop"
             ;; Create null sink `out'
             "load-module module-null-sink sink_name=out"
             "update-sink-proplist out device.description=out"
             ;; Loop `loop' to primary output
             "load-module module-loopback source=loop.monitor"
             ;; Pipe it into `out'
             "load-module module-loopback source=loop.monitor sink=out"
             ;; Loop primary input into `out'
             "load-module module-loopback sink=out"))
    (shell-command (concat "pacmd " command)))
  ;; Run `pavucontrol' and then unload the modules after it completes
  (start-process-shell-command
   "Audio Loop" nil (concat "pavucontrol;"
                            "pacmd unload-module module-null-sink;"
                            "pacmd unload-module module-loopback")))

Shutting down

(defun shut-down-computer ()
  "Shut down the computer."
  (interactive)
  (let ((shut-down (lambda ()
                     (shell-command "shutdown now"))))
    (add-hook 'kill-emacs-hook shut-down)
    (save-buffers-kill-emacs)
    (remove-hook 'kill-emacs-hook shut-down)))

This function is globally bound to C-x C-M-c. Other binds are pulled into here for a cleaner end result upon tangling.

:bind (("C-x C-M-c" . shut-down-computer)
       <<exwm-binds>>
       :map exwm-mode-map
       <<exwm-mode-binds>>)

Rebooting

(defun reboot-computer ()
  "Reboot the computer."
  (interactive)
  (let ((reboot (lambda ()
                  (shell-command "reboot"))))
    (add-hook 'kill-emacs-hook reboot)
    (save-buffers-kill-emacs)
    (remove-hook 'kill-emacs-hook reboot)))

This function is globally bound to C-x C-M-r.

("C-x C-M-r" . reboot-computer)

Suspending

(defun suspend-computer ()
  (interactive)
  (when (yes-or-no-p "Really suspend? ")
    (start-process "suspend" nil "systemctl"
                   "suspend" "-i")))

This function is globally bound to C-x C-M-s.

("C-x C-M-s" . suspend-computer)

Keybindings

Key bindings specifically related to window management or launching X applications are all placed in one area to make it easier to manage them.

Global binds to use across everything

Since I’m very lazy and don’t feel like writing a whole bunch of lambdas for multiple workspaces, presented here are instead some mapcar calls, like the one in this example.

(exwm-input-global-keys `(;; Switching workspace focus
                          ;; s-1 for 1, s-2 for 2, etc...
                          ,@(mapcar
                             (lambda (i)
                               `(,(kbd (format "s-%d" (% (1+ i) 10)))
                                 .
                                 (lambda ()
                                   (interactive)
                                   (exwm-workspace-switch-create ,i))))
                             (number-sequence 0 9))

                          ;; Change workspace focus by relation
                          ([?\s-q] . farl-exwm/workspace-prev)
                          ([?\s-e] . farl-exwm/workspace-next)

                          ;; Switching window to a workspace
                          ;; This was annoying to get working
                          ;; s-! for 1, s-@ for 2, etc...
                          ,@(mapcar
                             (lambda (i)
                               `(,(kbd (format "s-%s" (nth i '("!"
                                                               "@"
                                                               "#"
                                                               "$"
                                                               "%"
                                                               "^"
                                                               "&"
                                                               "*"
                                                               "("
                                                               ")"))))
                                 .
                                 (lambda ()
                                   (interactive)
                                   (exwm-workspace-move-window ,i))))
                             (number-sequence 0 9))

                          ;; Toggle how input is sent to X windows
                          ([?\s-w] . exwm-input-toggle-keyboard)

                          ;; Window size adjustment
                          (,(kbd "C-s-w") . shrink-window)
                          (,(kbd "C-s-s") . enlarge-window)
                          (,(kbd "C-s-a") . shrink-window-horizontally)
                          (,(kbd "C-s-d") . enlarge-window-horizontally)

                          ;; Opening programs
                          ([?\s-g]          . run-gimp)
                          ([?\s-s]          . run-steam)
                          ([?\s-f]          . run-firefox)
                          ([?\s-d]          . run-discord)
                          ([?\s-t]          . run-telegram)
                          ([?\s-m]          . run-musescore)
                          ([?\s-b]          . run-libreoffice)
                          ([?\s-o]          . run-transmission)
                          ([?\s-r]          . monitor-settings)
                          ([?\s-n]          . network-settings)
                          ([?\s-v]          . volume-settings)
                          ([s-return]       . ansi-term)
                          ([XF86Calculator] . calc)

                          ;; Other desktop environment things
                          ([?\s-x] . counsel-linux-app)
                          ([s-tab] . audio-loopback)
                          ([menu]  . counsel-M-x)

                          ;; Controlling EMMS
                          ([XF86AudioNext] . emms-next)
                          ([XF86AudioPrev] . emms-previous)
                          ([XF86AudioPlay] . emms-pause)
                          ([XF86AudioStop] . emms-stop)))

Emacs key bindings in X windows

This is super nice, because I love these key bindings and they are just intuitive to me, and now they can carry over safely to other programs.

(exwm-input-simulation-keys `(;; Navigation
                              ([?\M-<] . [C-home])
                              ([?\M->] . [C-end])
                              ([?\C-a] . [home])
                              ([?\C-e] . [end])
                              ([?\C-v] . [next])
                              ([?\M-v] . [prior])

                              ([?\C-b] . [left])
                              ([?\C-f] . [right])
                              ([?\C-p] . [up])
                              ([?\C-n] . [down])

                              ([?\M-b] . [C-left])
                              ([?\M-f] . [C-right])
                              ([?\M-n] . [C-down])
                              ([?\M-p] . [C-up])

                              ;; Selecting via navigation
                              (,(kbd "C-S-b") . [S-left])
                              (,(kbd "C-S-f") . [S-right])
                              (,(kbd "C-S-n") . [S-down])
                              (,(kbd "C-S-p") . [S-up])

                              ;; Copy/Paste
                              ([?\C-w] . [?\C-x])
                              ([?\M-w] . [?\C-c])
                              ([?\C-y] . [?\C-v])
                              ([?\C-s] . [?\C-f])
                              ([?\C-\/] . [?\C-z])

                              ;; Other
                              ([?\C-d] . [delete])
                              ([?\M-d] . [C-delete])
                              ([?\C-k] . [S-end delete])
                              ([?\C-g] . [escape])))

Key sequences cannot be defined in exwm-input-simulation-keys, so they are functions which are called in order to provide the desired behavior. The functions that follow ensure that keys I use in Emacs behave similarly in X windows as they do in Emacs itself.

C-s

The first is for saving. Since Emacs uses C-x C-s to save, a function has to be written to simulate that by pressing C-q then C-s, to send C-s verbatim to the X window.

(defun farl-exwm/C-s ()
  "Pass C-s to the EXWM window."
  (interactive)
  (execute-kbd-macro (kbd "C-q C-s")))

C-k

The next function is one meant to create a link in Telegram and do a number of other things in other programs. It is placed on the same key sequence that Org-mode uses to place links: C-c C-l.

(defun farl-exwm/C-k ()
  "Pass C-k to the EXWM window."
  (interactive)
  (execute-kbd-macro (kbd "C-q C-k")))

C-a

This function is meant to push C-a, which selects all text. It is bound to C-x h since that’s the equivalent key in Emacs.

(defun farl-exwm/C-a ()
  "Pass C-a to the EXWM window."
  (interactive)
  (execute-kbd-macro (kbd "C-q C-a")))

C-o

This function is meant to mimic the behavior C-o has in a standard Emacs buffer. It should push what follows onto a new line while staying on the current line.

(defun farl-exwm/C-o ()
  "Pass the equivalent of C-o to the EXWM window."
  (interactive)
  (execute-kbd-macro (kbd "<S-return> C-b")))

Binds

These functions are bound to keys in exwm-mode-map.

("C-c C-l" . farl-exwm/C-k)
("C-x C-s" . farl-exwm/C-s)
("C-x h" . farl-exwm/C-a)
("C-o" . farl-exwm/C-o)

Send a key verbatim more easily

This means there’s one less key needed to send a verbatim key to an EXWM buffer. It is obviously bound in exwm-mode-map.

("C-q" . exwm-input-send-next-key)
("C-c C-q" . nil)

Keyboard layout selection

I’m using Emacs as the input handler now, so this should make things a little easier to manage… hopefully. Because of this, C-\ needs to be added to exwm-input-prefix-keys.

(push ?\C-\\ exwm-input-prefix-keys)

Inhibit keys I don’t use

This removes the following from exwm-mode-map:

  • Toggling fullscreen
  • Toggling floating
  • Toggling hiding
  • Toggling the mode line
("C-c C-f" . nil)
("C-c C-t C-f" . nil)
("C-c C-t C-v" . nil)
("C-c C-t C-m" . nil)

On startup

This is everything that should be started or run when EXWM starts.

(defun farl-exwm/on-startup ()
  "Start EXWM and related processes."
  <<on-startup>>)

This function is run during after-init-hook.

(after-init . farl-exwm/on-startup)

Make Emacs start fullscreen

This makes Emacs startup look a lot more natural.

(set-frame-parameter nil 'fullscreen 'fullboth)

XDG compliance and WM settings

I have to set a few environment variables for the sake of compliance with various specifications, most notably the XDG Base Directory Specification. Also in this block I set an environment variable signaling to Java applications that the window manager is not a reparenting window manager.

(setenv "XDG_CURRENT_DESKTOP" "emacs")
(setenv "GTK2_RC_FILES" (expand-file-name "~/.config/gtk-2.0/gtkrc"))
(setenv "QT_QPA_PLATFORMTHEME" "gtk2")
(setenv "_JAVA_AWT_WM_NONREPARENTING" "1")

Disable screen blanking

I don’t need my laptop’s screen shutting off just because I’m sitting and watching a video with the laptop idle too long.

(start-process "Disable Blanking" nil "xset"
               "s" "off" "-dpms")

Keyboard configuration

This block sets the keyboard layout to US and give Caps Lock the functionality of Control. I was hesitant to do this at first, but it’s significantly more comfortable. I almost never used caps lock as it is, given my keyboards have no indicator for it on my laptops, but this gives me a much easier way to do commands without shifting my hand too far. Ideally, however, I configure my keyboards so that this setting is nothing more than an afterthought.

(start-process "Keyboard Layout" nil "setxkbmap"
               "us" "-option" "ctrl:nocaps")

Disable the trackpad

This thing is disgusting, and I prefer trackpoints way more.

(start-process "Trackpad Setup" nil "xinput"
               "disable" (shell-command-to-string
                          (concat "xinput | grep Synap | head -n 1 | "
                                  "sed -r 's/.*id=([0-9]+).*/\\1/' | "
                                  "tr '\n' ' ' | sed 's/ //'")))

Start the compositor

I don’t need it, but having basic compositing is very nice.

(start-process "Compositor" nil "xcompmgr")

Set fallback cursor

Some X windows will have weird cursors if this isn’t done.

(start-process "Fallback Cursor" nil "xsetroot"
               "-cursor_name" "left_ptr")

Banish the mouse

I’ve always been mixed on this behavior but it seems like a good idea.

(start-process "Mouse banisher" nil "xbanish")

Notifications

There is currently still no Emacs based notifications manager that I can personally get behind, and I by no means have enough Emacs Lisp know-how to properly write such a package. Because of this, I’m stuck using dunst.

(start-process "Notifications" nil "dunst")

Start EXWM

(exwm-enable)
(exwm-xim-enable)
(exwm-randr-enable)
(exwm-systemtray-enable)

On logout

When exiting, these are things I want done.

(defun farl-exwm/on-logout ()
  "Run this when logging out as part of `kill-emacs-hook'."
  <<on-logout>>)

This is hooked into when Emacs is killed.

(kill-emacs . farl-exwm/on-logout)

Black out the root window

This way, it looks good when exiting Emacs.

(start-process "Root window" nil "hsetroot"
               "-solid" "'#000000'")

Giving files their footers

Since we gave files their headers, I see no reason not to give them footers.

pdumper.el



;;; pdumper.el ends here

early-init.el



;;; early-init.el ends here

init.el



;;; init.el ends here

About

A far-from-sane literate GNU Emacs configuration

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published