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
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.
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.
- Clone the repo into where you store your Emacs configuration.
- Make sure you have all the right dependencies. See below for more details.
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.
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.
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.
aspell
: For spell-checking.mpd
: For playing music withemms
.ebook-tools
: For reading ebooks withnov
.pylint
: For syntax checking within Python.python-jedi
: For Python auto-complete.curl
: For getting weather withwttrin
.graphviz
: For creating diagrams.stack
: Haskell tools.sudo
: Duh.
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/>.
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 --- 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 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 --- 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:
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.
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
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)
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)
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))
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)
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"))
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.
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"))
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 ifpdumper-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-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))
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)
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)
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 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)
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.
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))
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)
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/")))
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.
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 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)))
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))
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)
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.
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.
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)
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))
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))
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)
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>>)
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)
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))
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)
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))
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)
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>>)
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)
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))
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))
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*")))
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))
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)
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)
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)
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))
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))
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)))
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))
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))
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.
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.
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))
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)
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)
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)
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)
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)
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.")
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.
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))
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)))
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))
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 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>>))
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>>)
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)
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 "")
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)
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>>))
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))
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)
("C-c b" . balance-windows)
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)
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)
I have no clue where else to put these, so here they are I guess.
(global-set-key (kbd "C-c d") #'cd)
(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)
(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))
(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))
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))
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"))
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.
These are modes that enable Emacs to edit different kinds of files differently. Programming major modes are further down, in the programming section.
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))
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))
When I save a file, sometimes I want specific things to be done. These are
the functions run during after-save-hook
.
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)
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)
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.
For getting a general word count.
(global-set-key (kbd "C-=") #'count-words)
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)))
This search behavior is SO much nicer than the default.
(use-package swiper
:ensure t
:defer t
:bind ("C-s" . swiper))
I love living on the edge.
(setq backup-inhibited t
make-backup-files nil
auto-save-default nil
auto-save-list-file-prefix nil)
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)))
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)
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))
This is to reflect behavior in other programs.
(delete-selection-mode 1)
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))
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))
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)
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 '((?\{ . ?\})
(?\( . ?\))
(?\[ . ?\])
(?\" . ?\"))))
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.
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))
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)))
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"))
(use-package company-jedi
:after company
:ensure t
:defer t
:init
(add-to-list 'company-backends 'company-jedi))
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))
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))
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)))
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)))
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>>)
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)))
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))
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)))
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>>)
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)))
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)
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)
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")))))
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>>)
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))
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.
These are features which require minimal configuration. Some may be more important than others, but these are mostly minimally configured features.
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)
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))
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)))
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))
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)
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))
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)))
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>>)
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)
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.
(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!"))
(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!"))
(defun mpd/update-database ()
"Update the MPD database synchronously."
(interactive)
(call-process "mpc" nil nil nil "update")
(message "MPD database updated!"))
(defun farl-emms/shuffle-with-message ()
"Shuffle the playlist and say so in the echo area."
(interactive)
(emms-shuffle)
(message "Playlist has been shuffled."))
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))
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")
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"))
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))
I love sudoku puzzles.
(use-package sudoku
:ensure t
:defer t
:bind ("C-c g s" . sudoku))
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)))
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))
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))
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>>)
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)
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>>)
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))
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.
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)
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)))
I do not want to have to load all of them individually on my own…
(exwm-workspace-number 10)
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"))
…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)))
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.")))
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.
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}'")))
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")))))
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")))))
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"))
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)
In order to create key bindings to launch X applications, first functions must be written which launch these X applications.
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"))
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 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"))
It’s kinda trashy but my friends use it.
(defun run-discord ()
"Start Discord."
(interactive)
(start-process "Discord" nil "discord"))
Another trashy messenger my friends use.
(defun run-telegram ()
"Start Telegram."
(interactive)
(start-process "Telegram" nil "telegram-desktop"))
I haven’t figured out how to engrave in Emacs, so for now…
(defun run-musescore ()
"Start MuseScore."
(interactive)
(start-process "MuseScore" nil "musescore"))
Shame me all you want.
(defun run-libreoffice ()
"Start LibreOffice."
(interactive)
(start-process "LibreOffice" nil "libreoffice"))
(defun run-transmission ()
"Start Transmission."
(interactive)
(start-process "Transmission" nil "transmission-gtk"))
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.
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>>))
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%-")
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' ' '"))
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)
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."))
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)))
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"))
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*"))
For when you need to do volume mixing.
(defun volume-settings ()
"Open pavucontrol to adjust volume."
(interactive)
(start-process "Volume Mixer" nil "pavucontrol"))
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")))
(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>>)
(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)
(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)
Key bindings specifically related to window management or launching X applications are all placed in one area to make it easier to manage them.
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)))
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.
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")))
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")))
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")))
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")))
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)
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)
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)
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)
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)
This makes Emacs startup look a lot more natural.
(set-frame-parameter nil 'fullscreen 'fullboth)
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")
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")
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")
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/ //'")))
I don’t need it, but having basic compositing is very nice.
(start-process "Compositor" nil "xcompmgr")
Some X windows will have weird cursors if this isn’t done.
(start-process "Fallback Cursor" nil "xsetroot"
"-cursor_name" "left_ptr")
I’ve always been mixed on this behavior but it seems like a good idea.
(start-process "Mouse banisher" nil "xbanish")
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")
(exwm-enable)
(exwm-xim-enable)
(exwm-randr-enable)
(exwm-systemtray-enable)
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)
This way, it looks good when exiting Emacs.
(start-process "Root window" nil "hsetroot"
"-solid" "'#000000'")
Since we gave files their headers, I see no reason not to give them footers.
;;; pdumper.el ends here
;;; early-init.el ends here
;;; init.el ends here