Skip to content

abbreviatedman/how-to-computer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

94 Commits
 
 

Repository files navigation

How To Computer

By Colin Jaffe

This Document Is…

… well… what, exactly?

… A Software Guide

This document is a guide to creating the perfect computer, or, in a more limited sense, the perfect computer for Colin Jaffe. But some of it may well be perfect for you, and the rest of it may be close enough to perfect for you that you can use this system as a starting point.

In this document, you’ll find configuration and code for setting up the Sway Window Manager, GNU Emacs (and its many many packages), Visual Studio Code, Debian GNU+Linux, Git, and many other pieces of software that can, when put together, be a complete computer environment.

If you’re looking for how to configure any of the software I cover, then this guide will assist you.

It is a collection of How Tos for setting up pieces of software. And when you combine those How To Softwares, you get How To Computer.

… A Computer System, Executable

This document is not just a guide to setting up a computer system, it is a computer system. You can execute this document as a computer program, and running that code will create the computing environment the document describes.

It is a cake recipe—and the cake itself. A more exact metaphor would be that this document is a cake recipe that you can just hold up to your oven, and your oven will read the recipe and make the cake for you. It is a magical scroll, a document that, when read aloud, alchemically transforms the world around it.

We’ll explore “how” an executable document works as we go, but as to the “why” of it… well, it’s very useful to have a way to clone this computer system to a new or repaired machine, with all the configuration automatically added. But there are many other ways to clone computer systems—the advantage here is that the documentation for the system and the system itself can more easily stay in sync. Anyone who’s maintained technical documentation knows how often the description falls behind the current technical state. When they’re one and the same, when the medium is the message, then updating documentation becomes much less error-prone—though not fool-proof.

All of that is genuinely helpful, but the main “why” for keeping your system configuration and documentation together in an executable format is that it’s just plain neat. It’s a fascinating process, documenting a system as you build it, balancing writing for yourself against writing for public consumption, and exploring the abilities and limits of both the system you’re building and the programming magic that builds it.

There’s a group of people in this world who need extra sugar on top to make work interesting, and I appear to be among their number.

… Thoughts On Computers And Code

Sometimes, to understand why code exists in the form in which it exists, we need to take a broad overview, or a deep dive, or a long wander. This document is unafraid to cover material in the way the documenter thinks best covers it, even if it means fully erasing the already fine lines separating technical documentation from blog and blog from essay.

How To…

How To Read This Document

We’ll go in chronological order of what should be set up first as we build out our system, but that’s for the main intended audience: myself, in the future, setting up a new computer. If you’re not in that limited audience, then please feel free to instead:

  • Go straight to the bits on software you want to learn more about or configure.
  • Skim this document to see what sparks your interest.
  • See if you can find the weirdest bit of writing here.
  • Or read it in any order you want, or don’t read anything at all after this sentence, or read the whole thing backwards, or just about do anything that suits you.

How To Set Up A Debian GNU+Linux Operating System

We’ll explore how to set up a new operating system, going from installing a fresh system on your computer to configuring it.

But first, some background on why I chose this particular operating system.

Which Linux System To Use?

Anyone who’s spent time exploring the many Linux distributions (also known as “distro-hopping”) finds after a while that not only are they all great, but they’re also great in almost the exact same way. Which one you pick hardly matters. Let’s dive into why.

Let me preface this by saying that there are of course some significant differences between Linux distributions. Arch Linux has great software repositories, if you want the most up-to-date software as it’s released. Many Ubuntu-based systems put in a lot of work to give you a great experience “out of the box”, with little or no configuration needed. On the more significant side, some distributions swap out important parts of the system for alternatives, such as the initialization/daemon system or the display server. And some Linux distros have highly specialized approaches, like Qubes, which wraps every app in its own system-within-a-system to prevent any app from affecting others or the system as a whole, or TAILS, which anonymizes all your internet traffic and deletes your data when you’re done, or any of the various Linux systems designed to be run entirely from a flash drive.

But if you’re not working with niche use cases like extreme privacy or security—if you’re a user just trying to get something done with Linux—the fundamental way most of these systems work is the same. Once you get going, you’re using a web browser and a text editor and likely some communication apps, and you’re installing these apps in almost the exact same way. Even if you want to use an entirely different graphical interface to your system, one more like Windows or more like macOS or entirely unlike either of those, graphical interfaces on a spectrum of increasingly wild ways… well, with any Linux system, the graphical interface is just another piece of software you can install. In other words, any non-niche Linux system can fairly easily be made to look, feel, and act mostly like any other, so your starting point hardly matters.

Most advantages one Linux system’s approach has are mostly or entirely counterbalanced by disadvantages of that same approach, and those advantages and disadvantages are fairly slight to begin with.

I’ve spent at least a year using each of the following Linux systems as my “daily driver”:

  • Manjaro, which is built on top of Arch
  • MX Linux, which is built on top of Debian
  • various systems built on top of Ubuntu (which is itself built on top of Debian)—namely Regolith Linux, Pop!_OS, and KDE neon

The most prominent difference you notice as a user of these systems is simply what the Graphical User Interface looks like (the GUI). Once I discovered how much I like the GUI the i3wm tiling window manager gives you (more on this later!), I spent considerable time with two systems that provided it: Manjaro and Regolith. The major differences between these two lie in:

  • the repositories used for package installation (Arch repos or Ubuntu repos)
  • the way the creators and maintainers of the system pre-configured the GUI

Since most repos provide basically the same software, and any pre-configured GUI can be re-configured, I stopped sweating the small differences, and am now using a basic Debian system, and simply installed an i3wm-like system on top of it.

So Why Debian In Particular?

If all userland Linux systems are essentially the same (as opposed to serverland or securityland or justplainweirdland systems), then Debian is… as good as any of them. It’s one of the oldest still-active Linux distributions, and therefore has an established reputation of stability and usability. There’s less layered on top, and while I’m not always a fan of minimalism, this does mean that one group is in charge of it from top to bottom, reducing the chances of conflicts that arise from one group disagreeing with or simply misunderstanding the pieces they’re building on top of. So that’s good.

But since these systems are all about equal, I’ve chosen Debian simply because one of my computers already came with a Debian system, and that particular Debian system is customized by the tiny computer manufacturers to work especially with it. I don’t want to go through the effort of re-installing a new system on it and re-customizing that new system in the same way the manufacturers did with Debian. So when I was setting up my first system after getting the Debian computer, it made sense to stick with Debian, rather than fracture this document into sections on, say, Debian and Arch.

I’ve been pleasantly surprised with Debian so far. Many distributions build their software on top of Debian—MX Linux, and Ubuntu, and the many distributions which then proceed to build on top of Ubuntu—, and for good reason—it’s rock solid and time-tested, having been around since 1993. Using Debian itself instead of what builds on top of it means a slightly more minimalist setup, but I’ve found so far that Debian out of the box works seamlessly without requiring the extra setup I imagined.

Again, these Linux distributions are, for the most part, not all that different from one another!

Installing Debian

Go to http://www.debian.org, download the installation disk, copy it to a flash drive, insert it into your computer, and boot up from it. Then follow the graphical installer’s directions.

I’m (clearly) not particularly interested in walking through how to install it in great detail. There are some great how-to articles on this! This is not one of them.

Configuring Debian

So you’ve installed Debian, and you’re ready to dive in. First, let’s set up a few things.

Debian Package Sources

On any computer, you have to decide where to get your software from.

The first thing you should do on a Debian system on a computer where software availability is more important than stability is switch to the Debian Unstable package sources.

deb https://deb.debian.org/debian/ unstable main contrib non-free non-free-firmware
deb-src https://deb.debian.org/debian/ unstable main contrib non-free non-free-firmware
Set Up Password Entry Properly

I’m not a security expert. I’m not sure I’m really an expert in any computer system or sub-system, frankly–I’m more a dilettante than a delver.

All of which is to say that probably this section is wrong.

Set Sudo Privileges Up Right

If you find yourself unable to do sudo things without changing to root, this is how you can set things up to do sudo things without changing to root.

Your sudoers file (/etc/sudoers on Debian) has the following line:

root ALL=(ALL:ALL) ALL

What this does is say that the root user has those privileges.

After it, you should add an equivalent line, but with your username in place of root:

Let’s test it by running sudo visudo, which should open the sudoers file in your default editor with sudo privileges.

Switching From No Password Feedback To Masked Password Feedback

By default, sudo password entry gives you no feedback as you type the password. No characters appear at all, as if you’re not typing. This can be quite confusing the first time you encounter it, and leaves you more prone to mistyping your password. It’s annoying, and the reason we do it, so that no one looking over our shoulder knows our password’s length, is a truly useless security measure.

In the sudoers file, let’s fix that. Use sudo visudo to open it up again, and change this line:

Defaults env_reset

to:

Defaults env_reset,pwfeedback

Now when you type your passwords in the command line, you’ll get asterisks for each character you type.

Terminals

Bash Prompt

I like having a simple prompt, with only the directory name and my password.

PS1='$(basename $(dirname "$PWD"))/$(basename "$PWD") => '
PATH

Exporting the path variable sets up all the path settings I perform later in this doc.

export PATH
Installing Kitty

I don’t use terminals very often (I mostly use Emacs as my terminal, and even then I often use Emacs instead of my terminal), but having a better and more configurable terminal is an inherent good in this world.

First, enter this command:

sudo apt install \

Then enter the following:

kitty

Fonts

Here are the steps to get my current collection of fonts.

Download Input Sans from the Input Homepage.

cd into the directory they’re in (probably Downloads!) and run unzip on the zipfile.

Make a local fonts directory and move all Input Sans fonts into it with the following commands:

mkdir -p ~/.local/share/fonts
mv [path to InputSans directory]/*.ttf ~/.local/share/fonts

Install some more fonts by first entering this command:

sudo apt install -y \

Then enter the following:

fonts-noto \
fonts-font-awesome \
fonts-cardo

Then place this as your fontconfig config file in ~/.config/fontconfig/fonts.conf:

<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
  <!-- Font settings for Cardo -->
  <match>
    <test name="family"><string>Cardo</string></test>
    <test name="lang"><string>en</string></test>
    <edit name="family" mode="assign">
      <string>Cardo</string>
    </edit>
  </match>

  <!-- Alias Font Awesome for symbols -->
  <match target="pattern">
    <test name="family"><string>FontAwesome</string></test>
    <edit mode="assign" name="family">
      <string>FontAwesome</string>
    </edit>
  </match>

  <!-- Alias Noto Color Emoji for emojis -->
  <match target="pattern">
    <test name="family"><string>Noto Color Emoji</string></test>
    <edit mode="assign" name="family">
      <string>Noto Color Emoji</string>
    </edit>
  </match>
</fontconfig>

Let’s define our default fonts for Emacs.

(use-package emacs
  :init
  (defvar crj--fixed-pitch-font "Hack")
  (defvar crj--variable-pitch-coding-font "Input Sans")
  (defvar crj--variable-pitch-font "Cardo")
  (defvar crj--coding-font crj--variable-pitch-coding-font)
  (defvar crj--emoji-font "Noto Color Emoji")
  (defvar crj--default-font-size 180)

  :config
  (when (member "Noto Color Emoji" (font-family-list))
    (set-fontset-font
     "fontset-default" 'unicode
     (font-spec :family "Noto Color Emoji")
     nil
     'prepend))

  (set-face-attribute 'default nil
			:font crj--coding-font
			:height crj--default-font-size)

  (set-face-attribute 'fixed-pitch nil
			:font crj--coding-font
			:height 0.8)

  (set-face-attribute 'variable-pitch nil
			:font crj--variable-pitch-font
			:height 1.0
			:weight 'regular))

Let’s set our prose files to use variable pitch as their main fonts.

(add-hook 'org-mode-hook #'variable-pitch-mode)
(add-hook 'markdown-mode-hook #'variable-pitch-mode)

Now, set some modes to use a fixed pitch font. This is for places where indenting to match characters in above lines is meaningful, which is suprisingly rare.

 (use-package emacs
   :preface
   (defun crj--use-fixed-pitch ()
     "Make the current buffer use a fixed pitch.

 Sometimes I really do want fixed-pitch for alignment, such as with terminals."
     (interactive)
     (set (make-local-variable 'buffer-face-mode-face) 'crj--monospace)
     (buffer-face-mode t))

   (defconst crj--fixed-pitch-mode-hooks
     '(emacs-lisp-mode-hook
	calendar-mode-hook
	proced-mode-hook
	cfw:calendar-mode-hook
	minibuffer-setup-hook
	mu4e-headers-mode-hook
	magit-log-mode-hook))
   (defface crj--monospace
     '((t
	 :family "Hack"
	 :foundry unspecified
	 :width normal
	 :height 1.0
	 :weight normal
	 :slant normal
	 :foreground "#505050"
	 :distantForeground unspecified
	 :background "#f8f8f8"
	 :underline nil
	 :overline nil
	 :strike-through nil
	 :box nil
	 :inverse nil
	 :stipple nil
	 :font "Hack"
	 :fontset unspecified
	 :extend nil))
     "Face for monospace fonts.")
   :init
   (dolist (hook crj--fixed-pitch-mode-hooks)
     (add-hook hook #'crj--use-fixed-pitch)))

And make line numbers monospaced, so they don’t jump around when they go from single-digit to double and so on.

 (use-package emacs
   :preface
   (defconst crj--line-number-faces '(line-number
				       line-number-current-line
				       line-number-major-tick
				       line-number-minor-tick))

   (defun crj-make-line-number-face-monospace (&rest args)
     "Makes line numbers monospace and fixes them in size."
     (interactive)
     (dolist (face crj--line-number-faces)
	(set-face-attribute face nil
			    :family crj--fixed-pitch-font
			    :height 1.0))
     args)

   :init
   (add-hook 'emacs-startup-hook #'crj-make-line-number-face-monospace))

There! Now you have good-looking fonts, each with their own special purpose. And you also feel special now! Special and weird and unique and fully fully fontified.

Git

  • Install keychain to store your SSH passwords with sudo apt install -y keychain
  • Generate the key with ssh-keygen -t ed25519 -C "[your email address]"
  • Add the key to the ssh agent with ssh-add ~/.ssh/id_ed25519
  • Run keychain on terminal launch by adding the following to the .profile file:
eval `keychain --agents ssh --eval ~/.ssh/id_ed25519`
  • Add the contents of ~/.ssh/id_ed25519.pub as a new SSH key in GitHub and GitLab’s settings.
  • Create a Git Config file with the following contents:
[user]
	name = Colin Jaffe
	email = colin.jaffe@gmail.com
[core]
	excludesFile = ~/.gitignore-global
	ignorecase = false
	editor = emacsclient -c
[interactive]
	singleKey = true
[init]
	defaultBranch = main
[pull]
	rebase = false
[push]
	followTags = true
[gitlab]
	user = abbreviatedman
[github]
	user = abbreviatedman

And now a global gitignore file, to tell Git what files not to add to repositories.

.env
node_modules
.dir-locals.el
.DS_Store
.vscode

Window Manager

Sway is a tiling window manager that’s a drop-in replacement for i3. It uses Wayland instead of X11, which is a good thing, but it also means that some things that work in X11 don’t work in Wayland. I’m still figuring out what those things are.

Tiling managers are great because:

  • You use the keyboard for everything.
  • Windows take up the whole screen, so you don’t have to think about window placement.
  • As you add more windows, they evenly split the space.
  • You can switch between windows arranged
    • vertically,
    • horizontally,
    • in a grid,
    • tabbed (as I use pretty often on my moderately-sized laptop screen),
    • or any comgination thereof!
  • You can effortless move your focus from window to window.
  • You can arrange your windows to suit your current needs.
  • As those needs change from moment to moment, you can change your window arrangement to suit them.
  • Desktop management is also effortless.

All of this adds up to a workflow where you don’t think about your workflow at all. As soon as you think about switching tasks, you’ve done it.

Installation

To install Sway on Debian, you’ll want to run:

sudo apt install \
     sway \
     swaybg \
     swaylock \
     waybar \
     swaybackgrounds \
     swayidle \
     swaylock \
     xdg-desktop-portal-wlr \
     xwayland \
     wl-clipboard \
     brightnessctl \
     grim

This installs Sway and some good utilities for it, most especially Waybar. (Configured below.)

Configuration

My Sway configuration differs from the base in only a few ways:

  • I use Emacs as my default terminal, opening new terminal windows with emacsclient.
  • I enable swayidle
  • I set up my trackpad.
  • That… actually might be it for now. The default Sway config is pretty good!

One thing I’m learning more and more as I go is that very often, default configurations have been well thought through. It’s really good to sit with the defaults for a while and see if the designers have a better understanding of how to use their app than you do. So often your paradigms for how to use software are brought over from your use of other software, and so often the designers of software have an entirely new paradigm. It’s good to learn that new paradigm, even if you end up deciding it’s not for you.

So far, Sway’s defaults have instead, been for me!

### Variables
#
# Logo key. Use Mod1 for Alt.
set $mod Mod4

# Home row direction keys, like vim
set $left h
set $down j
set $up k
set $right l

# Your preferred terminal emulator
set $term kitty

# Your preferred application launcher
set $menu wmenu-run

### Output configuration
#
# Default wallpaper (more resolutions are available in /usr/share/backgrounds/sway/)
output * bg /usr/share/backgrounds/sway/Sway_Wallpaper_Blue_1920x1080.png fill

#
# Example configuration:
#
#   output HDMI-A-1 resolution 1920x1080 position 1920,0
#
# You can get the names of your outputs by running: swaymsg -t get_outputs
#
# Switching outputs:
bindsym $mod+Ctrl+Tab move workspace to output left

### Idle configuration
#
# Example configuration:
#
exec swayidle -w \
     timeout 300 'swaylock -f -c 000000' \
     timeout 600 'swaymsg "output * power off"' resume 'swaymsg "output * power on"' \
     before-sleep 'swaylock -f -c 000000'
#
# This will lock your screen after 300 seconds of inactivity, then turn off
# your displays after another 300 seconds, and turn your screens back on when
# resumed. It will also lock your screen before your computer goes to sleep.

### Input configuration
#
# Example configuration:
#
#   input "2:14:SynPS/2_Synaptics_TouchPad" {
#       dwt enabled
#       tap enabled
#       natural_scroll enabled
#       middle_emulation enabled
#   }
#
# You can get the names of your inputs by running: swaymsg -t get_inputs
# Read `man 5 sway-input` for more information about this section.

input type:touchpad {
    drag enabled
    drag_lock enabled
    dwt enabled
    tap enabled
    natural_scroll disabled
    middle_emulation enabled
}

# Start a terminal to begin a session. This starts logging me into
# SSH, which I mostly use for Git.

exec kitty

### Key bindings
#
# Basics:
#
# Start an Emacs terminal
bindsym $mod+Backspace exec emacsclient -c -e "(multi-vterm)"

# Start a TERMINAL terminal
bindsym $mod+Shift+Backspace exec $term

# Start an Emacs window
bindsym $mod+Return exec emacsclient -c -a ""

# Start a browser.
bindsym $mod+Shift+Return exec firefox

# Start a quick note-taking Emacs window
bindsym $mod+n exec emacsclient -cn -a "" ~/Documents/org-stuff/quick-note.md

# Kill focused window
bindsym $mod+Shift+q kill

# Start your launcher
bindsym $mod+space exec $menu

# Drag windows by holding down $mod and left mouse button.
# Resize them with right mouse button + $mod.
# Despite the name, also works for non-floating windows.
floating_modifier $mod normal

# Reload the configuration file
bindsym $mod+Shift+r reload

# Exit sway (logs you out of your Wayland session)
bindsym $mod+Shift+e exec swaynag -t warning -m 'You pressed the exit shortcut. Do you really want to exit sway? This will end your Wayland session.' -B 'Yes, exit sway' 'swaymsg exit'

#
# Moving around:
#
# Move your focus around
bindsym $mod+$left focus left
bindsym $mod+$down focus down
bindsym $mod+$up focus up
bindsym $mod+$right focus right

# Or use $mod+[up|down|left|right]
bindsym $mod+Left focus left
bindsym $mod+Down focus down
bindsym $mod+Up focus up
bindsym $mod+Right focus right


# Move the focused window with the same, but add Shift
bindsym $mod+Shift+$left move left
bindsym $mod+Shift+$down move down
bindsym $mod+Shift+$up move up
bindsym $mod+Shift+$right move right

# Ditto, with arrow keys
bindsym $mod+Shift+Left move left
bindsym $mod+Shift+Down move down
bindsym $mod+Shift+Up move up
bindsym $mod+Shift+Right move right
#
# Workspaces:
#
# Switch to workspace
bindsym $mod+1 workspace number 1
bindsym $mod+2 workspace number 2
bindsym $mod+3 workspace number 3
bindsym $mod+4 workspace number 4
bindsym $mod+5 workspace number 5
bindsym $mod+6 workspace number 6
bindsym $mod+7 workspace number 7
bindsym $mod+8 workspace number 8
bindsym $mod+9 workspace number 9
bindsym $mod+0 workspace number 10

# Move focused container to workspace
bindsym $mod+Shift+1 move container to workspace number 1
bindsym $mod+Shift+2 move container to workspace number 2
bindsym $mod+Shift+3 move container to workspace number 3
bindsym $mod+Shift+4 move container to workspace number 4
bindsym $mod+Shift+5 move container to workspace number 5
bindsym $mod+Shift+6 move container to workspace number 6
bindsym $mod+Shift+7 move container to workspace number 7
bindsym $mod+Shift+8 move container to workspace number 8
bindsym $mod+Shift+9 move container to workspace number 9
bindsym $mod+Shift+0 move container to workspace number 10

# Note: workspaces can have any name you want, not just numbers.
# We just use 1-10 as the default.
#
# Layout stuff:
#
# You can "split" the current object of your focus with
# $mod+b or $mod+v, for horizontal and vertical splits
# respectively.
bindsym $mod+b splith
bindsym $mod+v splitv

# Switch the current container between different layout styles
bindsym $mod+s layout stacking
bindsym $mod+t layout tabbed
bindsym $mod+e layout toggle split

# Make the current focus fullscreen
bindsym $mod+f fullscreen

# Toggle the current focus between tiling and floating mode
bindsym $mod+Shift+g floating toggle

# Swap focus between the tiling area and the floating area
bindsym $mod+g focus mode_toggle

# Move focus to the parent container
bindsym $mod+a focus parent

#
# Scratchpad:
#
# Sway has a "scratchpad", which is a bag of holding for windows.
# You can send windows there and get them back later.

# Move the currently focused window to the scratchpad
bindsym $mod+Shift+minus move scratchpad

# Show the next scratchpad window or hide the focused scratchpad window.
# If there are multiple scratchpad windows, this command cycles through them.
bindsym $mod+minus scratchpad show

#
# Resizing containers:
#
mode "resize" {
    # left will shrink the containers width
    # right will grow the containers width
    # up will shrink the containers height
    # down will grow the containers height
    bindsym $left resize shrink width 30px
    bindsym $down resize grow height 30px
    bindsym $up resize shrink height 30px
    bindsym $right resize grow width 30px

    # Ditto, with arrow keys
    bindsym Left resize shrink width 30px
    bindsym Down resize grow height 30px
    bindsym Up resize shrink height 30px
    bindsym Right resize grow width 30px

    # Return to default mode
    bindsym Return mode "default"
    bindsym Escape mode "default"
    bindsym $mod+r mode "default"
}

bindsym $mod+r mode "resize"

#
# Utilities:
#
# Special keys to adjust volume via PulseAudio
bindsym --locked XF86AudioMute exec pactl set-sink-mute \@DEFAULT_SINK@ toggle
bindsym --locked XF86AudioLowerVolume exec pactl set-sink-volume \@DEFAULT_SINK@ -5%
bindsym --locked XF86AudioRaiseVolume exec pactl set-sink-volume \@DEFAULT_SINK@ +5%
bindsym --locked XF86AudioMicMute exec pactl set-source-mute \@DEFAULT_SOURCE@ toggle

# Special keys to adjust brightness via brightnessctl
bindsym --locked XF86MonBrightnessDown exec brightnessctl set 5%-
bindsym --locked XF86MonBrightnessUp exec brightnessctl set 5%+

# Special key to take a screenshot with grim
bindsym Print exec grim

# And run Flameshot on startup.
exec_always flameshot

#
# Status Bar:
#
# Read `man 5 sway-bar` for more information about this section.
# bar {
# position top

# When the status_command prints a new line to stdout, swaybar updates.
# The default just shows the current date and time.
# status_command while date +'%Y-%m-%d %X'; do sleep 1; done

# colors {
# statusline #ffffff
# background #323232
# inactive_workspace #32323200 #32323200 #5c5c5c
# }
# }

bar {
    swaybar_command waybar
}

bindsym $mod+c exec pkill -SIGUSR1 '^waybar$'

include /etc/sway/config.d/*

Waybar

This is the configuration for Waybar, Sway’s status bar. It tells me the state of things–the time, laptop battery, network connection, remaining system memory, etc.

I haven’t added anything to it, only disabled modules I don’t use, switched it to the left side, and broke text lines to make the width narrower. Programmers prefer things to be vertical, for reasons we’ll get into at some point.

{
     "position": "left", // Waybar position (top|bottom|left|right)
    "width": 10, // Waybar width
    "spacing": 4, // Gaps between modules (4px)
    "modules-left": [
        "sway/workspaces",
        "sway/mode",
        "sway/scratchpad",
        "custom/media"
    ],

    "modules-right": [
        "mpd",
        "idle_inhibitor",
        "pulseaudio",
        "network",
        "power-profiles-daemon",
        "cpu",
        "memory",
        "temperature",
        "backlight",
        "keyboard-state",
        "sway/language",
        "battery",
        "battery#bat2",
        "clock",
        "tray",
        "custom/power"
    ],

    "keyboard-state": {
        "numlock": true,
        "capslock": true,
        "format": "{name} {icon}",
        "format-icons": {
            "locked": "",
            "unlocked": ""
        }
    },

    "sway/mode": {
        "format": "<span style=\"italic\">{}</span>"
    },

    "sway/scratchpad": {
        "format": "{icon} {count}",
        "show-empty": false,
        "format-icons": ["", ""],
        "tooltip": true,
        "tooltip-format": "{app}: {title}"
    },

    "mpd": {
        "format": "{stateIcon} {consumeIcon}{randomIcon}{repeatIcon}{singleIcon}{artist} - {album} - {title} ({elapsedTime:%M:%S}/{totalTime:%M:%S}) ⸨{songPosition}|{queueLength}⸩ {volume}% ",
        "format-disconnected": "Disconnected ",
        "format-stopped": "{consumeIcon}{randomIcon}{repeatIcon}{singleIcon}Stopped ",
        "unknown-tag": "N/A",
        "interval": 5,
        "consume-icons": {
            "on": ""
        },

        "random-icons": {
            "off": "<span color=\"#f53c3c\"></span> ",
            "on": ""
        },

        "repeat-icons": {
            "on": ""
        },

        "single-icons": {
            "on": "1 "
        },

        "state-icons": {
            "paused": "",
            "playing": ""
        },

        "tooltip-format": "MPD (connected)",
        "tooltip-format-disconnected": "MPD (disconnected)"
    },

    "idle_inhibitor": {
        "format": "{icon}",
        "format-icons": {
            "activated": "",
            "deactivated": ""
        }
    },

    "tray": {
        "spacing": 10
    },

    "clock": {
        "tooltip-format": "<big>{:%Y %B}</big>\n<tt><small>{calendar}</small></tt>",
        "format-alt": "{:%Y-%m-%d}"
    },

    "cpu": {
        "format": "{usage}% ",
        "tooltip": false
    },

    "memory": {
        "format": "{}% "
    },

    "temperature": {
        "critical-threshold": 80,
        "format": "{temperatureC}°C {icon}",
        "format-icons": ["", "", ""]
    },

    "backlight": {
        "format": "{percent}% {icon}",
        "format-icons": ["", "", "", "", "", "", "", "", ""]
    },

    "battery": {
        "states": {
            "warning": 30,
            "critical": 15
        },

        "format": "{capacity}% {icon}",
        "format-full": "{capacity}% {icon}",
        "format-charging": "{capacity}% ",
        "format-plugged": "{capacity}% ",
        "format-alt": "{time} {icon}",
        "format-icons": ["", "", "", "", ""]
    },

    "battery#bat2": {
        "bat": "BAT2"
    },

    "power-profiles-daemon": {
      "format": "{icon}",
      "tooltip-format": "Power profile: {profile}\nDriver: {driver}",
      "tooltip": true,
      "format-icons": {
        "default": "",
        "performance": "",
        "balanced": "",
        "power-saver": ""
      }
    },

    "network": {
        "format-wifi": "{essid}\n({signalStrength}%) ",
        "format-ethernet": "{ipaddr}/{cidr} ",
        "tooltip-format": "{ifname}\nvia {gwaddr} ",
        "format-linked": "{ifname}\n(No IP) ",
        "format-disconnected": "Disconnected ⚠",
        "format-alt": "{ifname}:\n{ipaddr}/{cidr}"
    },

    "pulseaudio": {
        "on-click": "pavucontrol",
        "format": "{volume}% {icon}\n{format_source}",
        "format-bluetooth": "{volume}% {icon}\n{format_source}",
        "format-bluetooth-muted": " {icon}\n{format_source}",
        "format-muted": " {format_source}",
        "format-source": "{volume}% ",
        "format-source-muted": "",
        "format-icons": {
            "headphone": "",
            "hands-free": "",
            "headset": "",
            "phone": "",
            "portable": "",
            "car": "",
            "default": ["", "", ""]
        },
    },

    "custom/media": {
        "escape": true,
        "exec": "$HOME/.config/waybar/mediaplayer.py 2> /dev/null", // Script in resources folder
        "format": "{icon} {text}",
        "return-type": "json",
        "max-length": 40,
        "format-icons": {
            "spotify": "",
            "default": "🎜"
        },
    },

    "custom/power": {
        "format" : "",
		"tooltip": false,
		"menu": "on-click",
		"menu-file": "$HOME/.config/waybar/power_menu.xml", // Menu file in resources folder
		"menu-actions": {
			"shutdown": "shutdown",
			"reboot": "reboot",
			"suspend": "systemctl suspend",
			"hibernate": "systemctl hibernate"
		}
    }
}

Utilities

Some random little apps.

Screenshots

I’ve found Flameshot to be just fine.

You can install it with:

sudo apt install -y \
  flameshot

And then we run it on startup, and it sits in Sway’s toolbar. (See Sway config.)

VS Code

I may try to do this more automatically through the terminal, but, for now, go to VS Code’s site and download their .deb file. Then run sudo apt install [path to .deb file], and you’re good.

Node

To install Node on Debian, you’ll want to run:

sudo apt install -y nodejs npm

Emacs

The greatest operating system ever created.

Run Emacs as a Server

This allows there to be one running instance of Emacs, which means that you only need to execute the config once per session, and new frames (otherwise known as windows at the OS level) pop up instantly.

(server-start)

Look and Feel

Theme
(use-package modus-themes
   :vc (modus-themes :url "https://gitlab.com/protesilaos/modus-themes" :branch main)
  ;; :pin gnu
  :config
  (set-var modus-themes-italic-constructs t
	     modus-themes-bold-constructs t
	     modus-themes-mixed-fonts t
	     modus-themes-variable-pitch-ui t
	     modus-themes-completions '(((matches . (intense background underline bold))
					 (selection . (accented intense bold))))
	     modus-themes-headings '((0 . (variable-pitch  1.8))
				     (1 . (variable-pitch  1.8))
				     (2 . (variable-pitch  1.6))
				     (3 . (variable-pitch  1.4))
				     (4 . (variable-pitch  1.2)))
	     modus-themes-common-palette-overrides modus-themes-preset-overrides-intense)
  ;; (dolist (face '(modus-themes-prose-code modus-themes-fixed-pitch))
  ;; (set-face-attribute face nil :family crj--fixed-pitch-font :height 1.0))
  (mapc #'disable-theme custom-enabled-themes)
  (load-theme 'modus-operandi-deuteranopia :no-confirm))
Org Mode

#+name org-look-and-feel

(add-hook 'org-mode-hook #'org-toggle-link-display)
Line Numbers

It’s been a long time since I felt this way, but I bet I would have been shocked at some point in my life to learn how much I would someday care about the numbers that go next to the lines in a document.

Basic Line Number Settings

Sets up line numbers to be (almost) everywhere, with the major exception being terminal modes, where they’re largely unnecessary.

Also sets up line numbers to be relative to the current line number. I like to use relative line numbers because they make line-wise work easier if you’re using Evil Mode. Which I do.

(use-package emacs
  :preface
  (defconst crj--hooks-for-modes-without-line-numbers '(term-mode-hook
							  vterm-mode-hook
							  shell-mode-hook
							  treemacs-mode-hook
							  eshell-mode-hook))

  (defun crj--turn-off-line-numbers ()
    "Turns off line numbers. Meant to be added as a hook."
    (display-line-numbers-mode 0))

  :init
  (set-var display-line-numbers-type 'relative
	     magit-disable-line-numbers nil
	     magit-section-disable-line-numbers nil)
  (setq-default display-line-numbers-width 4)
  (global-display-line-numbers-mode t)
  (dolist (hook crj--hooks-for-modes-without-line-numbers)
    (add-hook hook #'crj--turn-off-line-numbers)))
Wrapping Lines

Turn on what in other editors would just be called “word wrap”, but of course it isn’t in Emacs.

(global-visual-line-mode 1)

Evil Keybindings Setup

Leader Key

First, let’s use General, which commands my army of Evil keybindings. As the name suggests, it also works with leaders.

(use-package general
  :init
  (general-create-definer leader
    :states '(normal motion visual)
    :keymaps 'override
    :prefix "SPC"))
Menus

Organizing your keyboard shortcuts into menus helps tremendously with discoverability and documentation, particularly with which-key involved. The leader key feature from General has a great way to do so.

(use-package emacs
  :general
  (leader
    "a" '(:ignore t :which-key "Applications menu")
    "am" '(:ignore t :which-key "Music menu")
    "v" '(:ignore t :which-key "Vterm menu")
    "m" '(:ignore t :which-key "Markdown menu")
    "q" '(:ignore t :which-key "Quit menu")
    "g" '(:ignore t :which-key "Source control menu")))
Handling Escape Better

Makes escape generally get you out of more prompts and other interface elements, although you should really know to reach for Emacs’ C-g if that fails.

Also gets rid of highlighting Evil’s search results with a hit of the escape key.

(use-package emacs
  :init
  (advice-add 'evil-force-normal-state :after #'evil-ex-nohighlight)
  (global-set-key (kbd "<escape>") 'keyboard-escape-quit))

Better Variable Setter

This variable setter from General allows you to treat custom and non-custom variables the same in Emacs.

(defalias 'set-var 'general-setq)

Terminals

Environment Variable Integration

Let’s set Emacs up to get our environment variables correctly:

(when (memq window-system '(mac ns x pgtk))
  (exec-path-from-shell-initialize))
Vterm

For the few things where Emacs can’t easily replace a terminal, vterm is a good choice. It’s a terminal emulator that runs in Emacs, and it’s pretty good. Its focus is being true to a standard terminal experience, and it’s very, very good at that focus.

Because it runs based on an external program, there’s actually a fair amount of setup involved.

Installation
The Vterm Library

First, we’ll install libvterm. It may already be installed, but, if not:

sudo apt install \
     libvterm-bin \
     libtool \
     libtool-bin
Vterm Emacs Package

Now the actual Emacs package.

 (use-package vterm
   :preface
   (defun crj-kill-unnamed-vterm-terminals ()
     "Kill all vterm buffers that still have their default buffer names.

 It temporarily removes confirmation of killing modified buffers.

 So please be sure you want to kill all modified vterm buffers before
 you run this command."
     (interactive)
     (let ((kill-buffer-query-functions
	     (remq 'process-kill-buffer-query-function kill-buffer-query-functions)))
	(mapc (lambda (buffer)
		(when (string-match-p (regexp-quote "vterm") (buffer-name buffer))
		  (kill-buffer buffer)))
	      (buffer-list))))

   :init
   (set-var vterm-always-compile-module t
	     vterm-max-scrollback 100000)
   :general
   (leader "qv" '(crj-kill-unnamed-vterm-terminals :which-key "Kill all unnamed vterm buffers."))
   (:keymaps 'vterm-mode-map
	      "C-c <escape>" '(vterm-send-escape :which-key "Send escape key to underlying shell.")
	      "C-c :" '(vterm--self-insert :which-key "Send colon key to underlying shell."))
   (:keymaps '(vterm-mode-map vterm-copy-mode-map)
	      "C-c n" '(vterm-copy-mode :which-key "Toggle Vterm Copy Mode.")
	      "C-c C-t" '(vterm-copy-mode :which-key "Switch to Vterm Copy Mode.")))
Additional Configuration
Multiple Terminal Buffers

Vterm is fantastic, but its default is to have one terminal window. I want to have multiple terminals, and I want to be able to quickly:

  • open a new terminal
  • open a new terminal in the current project
  • switch to my most recent terminal
  • switch to the next terminal if that wasn’t the right one

multi-vterm solves these problems, and it has commands with well thought-out UI: the “next terminal” command will open a new terminal if none exist, and the “project terminal” command will open in current directory if you’re not in a project buffer, switch to the dedicated project terminal if it’s open, and close the dedicated project terminal if it’s the active buffer.

(use-package multi-vterm
  :general
  (leader
    "vn" '(multi-vterm :which-key "Open a Vterm terminal.")
    "vo" '(multi-vterm-project :which-key "Open a Vterm terminal in the current project root.")
    "v TAB" '(multi-vterm-next :which-key "Switch to next terminal.")))
Bash Integration

Bash needs some functions to make the integration complete.

vterm_printf() {
  if [ -n "$TMUX" ] \
      && { [ "${TERM%%-*}" = "tmux" ] \
          || [ "${TERM%%-*}" = "screen" ]; }; then
      # Tell tmux to pass the escape sequences through
      printf "\ePtmux;\e\e]%s\007\e\\" "$1"
  elif [ "${TERM%%-*}" = "screen" ]; then
      # GNU screen (screen, screen-256color, screen-256color-bce)
      printf "\eP\e]%s\007\e\\" "$1"
  else
      printf "\e]%s\e\\" "$1"
  fi
}

if [ "$INSIDE_EMACS" = 'vterm' ]; then
    clear() {
        vterm_printf "51;Evterm-clear-scrollback";
        tput clear;
    }
fi

Project

Project is Emacs’ built-in library for managing “projects”–collections of related files, usually all under the same directory.

Prefer ELPA Version

There are some good fixes in the version of project.el that resides on ELPA, particularly regarding following submodule .gitignores.

(use-package project
  :pin gnu)
Open A File On Project Open

When I open a project, I usually want to open a file immediately. Let’s set that.

(use-package emacs
  :init
  (set-var project-switch-commands 'project-find-file)
  :general
  (leader "SPC" '(project-find-file :which-key "Find file in current project")))
Add Projects Based On Files
(use-package emacs
  :init
  (set-var project-vc-extra-root-markers '(".dir-locals.el"
					     "package.json"
					     "requirements.txt")
	     project-vc-ignores '(".DS_Store"
				  ".env"
				  "node_modules"
				  ".vscode")))
Project Commands to Bind Later
  • project-remember-under
  • project-remove

Package Installation System

Package Installation Basics

Set up package installation, as well as draw from the repository for non-GNU packages.

(require 'package)
(add-to-list 'package-archives
	       '("melpa" . "https://melpa.org/packages/"))
(package-initialize)
(package-refresh-contents)
A More Mature System

What we want is, ideally, for Emacs to:

  • Configure its packages using a clean, extensible, and already well extended system. (use-package, now included in Emacs)
  • Make sure packages are always installed, particularly for when we set up a new system. (use-package-ensure)
  • Get packages from version control systems when needed. (vc-use-package, soon to be included in Emacs)
  • Auto-update packages on startup, and also on command.
(require 'use-package)
(require 'use-package-ensure)
(setq use-package-always-ensure t)
(unless (package-installed-p 'vc-use-package)
  (package-vc-install "https://github.com/slotThe/vc-use-package"))
(require 'vc-use-package)
(use-package auto-package-update
  :init
  (defun crj--update-all-packages ()
    (interactive)
    (package-refresh-contents)
    (auto-package-update-now-async))
  :config
  (setq auto-package-update-delete-old-versions t
	  auto-package-update-hide-results t)
  (auto-package-update-maybe))

Working With Non-Code Text

Spell Check
General Settings

Before evaluating the below, it’s important to install hunspell with:

sudo apt install -y hunspell
 (use-package flyspell
   :diminish 'flyspell-mode
   :after evil
   :demand t
   :preface
   (defun crj--turn-on-flyspell ()
     (flyspell-mode 1))

   (defun crj-spellcheck-visible-window ()
     "Runs the spellchecker on the visible part of the window."
     (interactive)
     (flyspell-region (window-start) (window-end)))

   (defun crj-flyspell-add-previous-word-to-dictionary ()
     "Add the previous \"misspelled\" word to the dictionary."
     (interactive)
     (save-excursion
	(evil-prev-flyspell-error)
	(let ((current-location (point))
	      (word (flyspell-get-word)))
	  (when word
	    (flyspell-do-correct
	     'save
	     nil
	     (car word)
	     current-location
	     (cadr word)
	     (caddr word)
	     current-location)
	    (message (format "%s saved to dictionary." (car word)))))))
   :init
   (general-add-hook
    '(text-mode-hook org-mode-hook git-commit-mode-hook)
    #'crj--turn-on-flyspell)
   (add-hook 'prog-mode-hook #'flyspell-prog-mode)
   (set-var ispell-program-name "hunspell")

   :config
   (ispell-set-spellchecker-params)
   (ispell-hunspell-add-multi-dic "en_US")
   (ispell-change-dictionary "en_US" t)

   :general
   (leader
     "bs" #'(crj-spellcheck-visible-window :which-key "Spellcheck visible window.")
     "bS" #'(flyspell-buffer :which-key "Spellcheck whole buffer."))

   ('(insert normal emacs)
    "C-;" #'flyspell-auto-correct-previous-word
    "C-M-;" #'crj-flyspell-add-previous-word-to-dictionary))

 (use-package flyspell-correct
   :init
   (defun crj-flyspell-correct-with-rapid-mode ()
     "Correct multiple words in a row."
     (interactive)
     (let ((current-prefix-arg '(4)))
	(call-interactively 'flyspell-correct-wrapper)))

   (defun crj-flyspell-correct-dwim ()
     "Correct previous word using candidate selection, first undoing any previous
 auto-correct.

 Inspired by alphapapa's function here:

 https://github.com/d12frosted/flyspell-correct/issues/30"
     (interactive)
     ;; If we've already auto-corrected, undo it.
     (when (equal flyspell-previous-command #'flyspell-auto-correct-previous-word)
	(progn
	  (save-excursion
	    (undo))
	  (crj-spellcheck-visible-window)))

     ;; Either way, select a correction candidate.
     (call-interactively 'flyspell-correct-wrapper))

   :general
   ('(insert normal emacs)
    "C-:" #'crj-flyspell-correct-dwim
    "C-M-:" #'crj-flyspell-correct-with-rapid-mode))

 (use-package flyspell-correct-popup
   :after flyspell-correct)
Spell Check Dictionary

This is my list of words, but having this be the source of truth isn’t quite right, since I’m always adding directly to the file. It’s possible to do a reverse tangle, where changes to a file are reflected back here, and I may explore that at some point. But if I simply update this every once in a while from that file, that will do for now.

If you’re setting up a machine for the first time, this goes in ~/.hunspell_en_US

Na'taja
neen
API's
APIs
AddBookmark
Algo
Alsa
Ang
Animorphs
Arkham
Asus
Austell
Autostart
Avec
BTUs
Bam
Banff
Bauch
Bazi
Bechdel
Beetlejuice
Bigby
Bluth
Bonjour
BookmarkDetails
Borbon
Bossypants
Brakebills
Brinn
Bueller
Bulma
Burgos
Buzzfeed
Capilano
Ceratosaurus
Charmander
CircleCI
Clipgrab
Cliqhop
Cmd
Codecademy
Codepen
Codewars
Coldwater
Collab
Colorizer
ContactList
Cortelyou
Cristobal
Ctrl
DMs
Dagm
Daly
Deinonychus
Denniston
Desmonda
Dinos
Diplodocus
DisplayPort
DonationForm
Dotfiles
Dratch
Dratch's
Dunham
Durandisse
Dygma
Elea
Emagi
Emagi's
Emmet
Erian
Esc
Eshell
Everdell
Ewuoso
Expensify
Fatema
Fatema's
Fey's
Fillory
Flamme
Flexbox
Fn
Fogg
Fozzie
FundMe
GParted
Galculator
Gcal
Gigi
Gloomhaven's
Gmail
GraphQL
Guarriello
HOFs
HackerRank
Haddish
Haha
Handoush
Heeeeey
HelperQueue
Hola
Homebrew
Hulkaroy
Hulkaroy's
IAs
Ibuffer
Ikechi
Imgur
Imma
Immersives
IndexError
Issa
Itzkovitz
JSDoc
Jaffe
Jaffe's
Jalamang
Jamelia
Jargondome
Joshuas
Josié
José
Jukay
JustWorks
Kahn
Kaling
Kanban
Karabiner
Karolin
Kemper
Kensington
Keyboardio
Ki
Kimmy
Kisha
Kiyomi
Kurakani
Larissa
Learnings
Lebsack
LeetCode
Lightdm
LinkedIn
Looper
Loopers
Lowlights
Luiza
Lxappearance
MDN
MacOS
Maddy
Manjaro
Mariia
Mascarade
Mashu
Mayakovsky
Mayakovsky's
Mendings
MewTwo
Middleware
Midori
Miggy
Millenial
Modeline
Mongo
Mongo's
MoodFlics
Moonlander
Motherload
MuseScore
Nakisha
Nazareno
Neovim
Netlify
NoSQL
Nothin
Nvim
OAuth
Oblogout
Oo
OpenWeather
Oume
Oumu
PRs
Pak
Pamac
Parkville
Peart
Perkaj
Pikachu
PlugInstall
Poehler
Ponkapoag
Postable
Postgres
ProductItem
ProductPage
ProductThumbnail
Prot
Pterosaur
PyMongo
Qtconfig
Quisa
Quokka
RJSX's
React's
ReactDOM
Redux
Repos
Responder
Resubmission
Rewatch
Reynard
Rhinemann
Roadmaps
Rohan
Rohan's
Ronin
Roomba
Rowlf
SQLAlchemy
Sandpoint
Sareen
Schenck
Schulist
Schumer
Scrabblemania
Sequelize
Sev
Sev's
Shohaib
Shoutout
Sihame
SimpleNote
Smallworld
Soma
SomaFM
Spellcasting
Spellwork
Spotify
Steph
Streeeeeetch
Sunil
Swayze
Syyu
TAs
Tashawnee
Teamity
Teladoc
Teyanna
Theo
ThinkPad
Thunderdome
Timebox
Timeset
Todon't
Todont
Todos
Toggleable
Trainings
Trello
Triane
Triane's
TypeError
URWGothic
Uber
Unbookables
Uncomment
UserProfile
VSCode
VSCodeVim
Vinton
Vous
Walesca
Walkthough
Weingarten
Wes
What're
Wheee
Whichever's
Whoo
WideKey
WidgetShow
Wireframe
Woohoo
Wookie
Workspaces
Xfburn
Xiaoming
Xiaoming's
Xresources
Younes
Younes's
Zach
Zelle
Zheng
abbreviatedman
abcdefghijklmnopqrstuvwxyz
acers
advices
alsamixer
angiemunoz
arf
argv
asdf
aspell
async
athon
autosave
axios
backchannel
backend
backgr
balloonasaurus
bennyzheng
bindsym
birdseeds
blurlock
bmenu
boeuf
bootcamp
bootcamps
bourguignon
bugapalooza
calamares
camelCase
cc'd
centric
charAt
charset
cheapos
checkbox's
checkboxes
checkmarks
cj
cking
className
cli
cloneDeep
cmake
cmds
codealong
codebase
codewars
codings
communitarian
concat
config
configs
const
convo
cortelyouparents
ctrl
curlies
d'oeuvres
defcon
destructured
destructuring
dev
devs
dicking
dired
divs
dmenu
dotenv
dropdown
duckduckgo
dunst
eboot
ejs
el
eldoc
elif
elisp
else's
emacsclient
emojify
empathetically
env
eshell
eshell's
eval
explainer
falsy
fav
favoriting
fetchWeather
ffffff
filepath
findIndex
fira
forEach
formatName
fpakman
frontend
fs
fullscreen
gamers
generalassemb
geocoder
geolocating
geonames
getArea
getListsMenu
getenv
gfm
giphy
gitignore
gitlab
gmail
goshdarned
goto
gpg
gradebook
granularly
groovesalad
gtk
gvim
handleInputChange
hardcode
hardcoded
hardcoding
hashmap
headshot
hjkl
hlissner
homeworks
hors
hotspot
href
https
hutdown
iTerm
ibernate
ibuffer
iframe
ijkl
improv
indexOf
indic
init
inlineimages
inplace
inputValue
isSubArray
ish
jaffe
jdrichardstech
jist
josemejia
journaling
js
json
jsx
kata
keycard
keychain
keypresses
keyring
killall
kyu
lakishajohnson
leaguers
lifecycle
listItems
localhost
lockdown
lodash
lol
loooong
lotr
lowercased
lowlight
ly
macOS
magit
mainStreet
mbsync
meds
meeples
memoirist
miasmic
minibuffer
mins
mkdir
moar
mockup
mockups
mocp
modeline
moduleoneisfinallyover
moly
monospace
morc
mousedown
moveTabToNewWindow
moveWindowToNewTab
mult
myIncludesFunction
myIndexOfFunction
myJoinFunction
myPopFunction
myPushFunction
myReverseFunction
myShiftFunction
mySliceFunction
myUnShiftFunction
nagbar
nav
navbar
ncurses
nemo
neurodiversity
nextcloud
ng
nista'd
nodejs
nodemon
nop
nowabouts
npm
npx
num
numberizable
numpad
nums
ock
octopi
ohmyz
ol
omg
onChange
onClick
onSubmit
onboarding
ordinated
oss
pacman
pactl
palemoon
pandoc
params
parens
partytime
pavucontrol
pcmanfm
picom
pkexec
pkill
pls
png
polkit
pomodoro
ponymix
porking
pos
postInfo
postamble
posteo
ppt
pre
prepended
preventDefault
programmatically
prog
prototypal
pseudocode
psql
pulseaudio
pushback
pwfeedback
py
pythonic
qq
querySelector
ramen
readme
readmes
readthedocs
recency
reframe
reimplementation
repo
repo's
repos
req
reshim
revealOptions
rgb
ribbit
ripgrep
roleplaying
rq
sammich
sbin
scaffolded
schooler
scoobies
sequelize
ser
serviceWorker
setState
setenv
setq
shareouts
shat
signup
signups
skillset
slidedeck
sms
spacestation
speedbumps
src
standups
statusline
storyId
strategize
struggler
stylesheet
subfields
subwords
sudo
svg
swappable
symlinks
syncthing
targetAmount
tashawneeguarriello
tasing
textarea
timeframe
timeslot
titlecase
toc
todo
todo's
toggleable
trackpad
trackpoint
trello
truthy
tsx
ttf
unbookable
undergird
undiscoverable
uneditable
unhide
unmuting
unrelatedly
unring
unsee
unshift
uppercased
useEffect
userId
userapp
userguide
uspend
usr
utf
util
uuid
validator
viewList
viewport
vimium
virtualbox
visudo
vlc
volumeicon
vterm
wakka
walkthrough
wasd
weatherObject
webstore
whiteboarding
whitespace
wireframes
workspaces
wrt
xautolock
xcape
xclip
xcursor
xdg
xft
xit
xkill
xmodmap
xrandr
yadm
yas
yay
zsa
zsh
holla
hunspell
emacs
i3wm
LLC
SPC
txt
vertico
NERDTree
neotree
unicode
VCS
crj
Changelog
PROJ
changelog
gcal
Simplenote
emacsclient
dwim
Tecosaur's
html
alist
FiraCode
link
uncommenting
docstrings
beespell
ascii
linux
macos
mmm
beespell
colin
subword
mu4e
Flyspell
flyspell
Flyspell's
Stef
keymap
defvar
tex
ispell
tex
NixOS
superwords
Modus
noctuid
Solarized
myhunspell
dic
wucuo
lsasldjfd
hacky
Case
Case
Words
Words
zcz
uncomment
Case
Case
Words
Case
Cloud
Next
camelcase
WIR
Zoomer
joinpursuit
angiepmunoz
dangerroom
superpowered
dangerroom
Charlie
EOM
Charlie
Charlie
Charlie
Charlie
labbed
RJSX
pokemon
LSP
Vivendi
Zenburn
modus
vivendi
Sareen's
shoutouts
shoutout
DBs
hulkaroy
zenburn
modus
autocomplete
LPD
JJ's
k
NextCloud
EOY
Borbon's
middleware
RESTful
url
restclient
workin
PBA
Algos
CORS
José's
tickets
operandi
io
UX
cpu
acpi
i3blocks
i3block
pango
wonkiness
saviourize
am
Allosaurus
deceptive
Compsognathus
Dracorex
pachycephalosaur
Elasmosaurus
Giraffatitan
Indosuchus
Jingshanosaurus
Jingshan
prosauropods
Khaan
oviraptor
Minmi
anklyosaurian
Ouranosaurus
iguanodont
Parasaurolophus
Spinosaurus
Utahraptor
velociraptor
Spielberg's
velociraptors
Vulcanodon
Zephyrosaurus
postcranial
Xenoceratops
cors
Bitwarden
Udemy
TypeScript
am
sexp
pm
Passcode
Neato
amirite
sooooo
Yeeeeaaaaah
DSA
postgres
Kabir
Joshua
jQuery
uninstallation
Q1
instructors'
lsp
capf
Mct
CAND
Consult's
yasnippet
defun
sql
CRA
recommender
javascript
Github
10am
1pm
Github
ERDs
LucidChart
ERD
bookselling
Xfinity
Minorly
unmute
EOD
Snackalog
supergroups
KDE
actualista
M1
M2
M3
M4
Git's
VSCode's
SSL
el
backtick
assumptively
pm
pm
throughs
gif
ORM
configurability
dinostorus
DROP
Heroku
PERN
Netlify
monorepo
subtree
Wordle
OOO
lengthed
pm
wireframing
pm
Tashawnee
queryable
Gooood
GH
unfinishable
impactful
Guix
tomorrow
Loggins
heroku
crj
Lua
featureful
pm
Niiiiice
Netlify's
juuuuust
am
LPD's
gradeable
UI
inStock
Karolin's
Laiba
PBD
walkthroughs
tradeoff
spammy
Fullstack
FSA
recursers
CTAs
HackMD
TLDR
CC'd
Gonsalves
OOP
Davonte
replayable
FizzBuzz
Polya's
airpods
booleans
algos
REPL
repl
NaN
Barksdale
PEMDAS
Which'll
Codewarsing
labbing
Gah
keycaps
Ok
ordinating
BPSS
CodeTrack
SLA
Reactstrap
REPLs
upvoted
Reddit
Outro
Importanter
DuckDuckGo
HackerNews
pm
am
TMNT
partnerless
Adderall
Wes'
Regifter
Ol'
MVC
2a
maskless
BigInt
strugglers
docstring
phonebook
JM
breakpoint
recursed
recurse
Customizable
walka
tamagotchi
rehomed
pseudocoding
substring
PM
AM
Named
PM
Relatedly
Maveriks
customizable
jsdoc
typedef
param
Leetcode
Leetcode
util
USV
BCC
kefir
Kickstarter
HOF
Maspeth
tm
pm
NPS
md
Spaceteam
FAST's
Reveal's
crypto
Crypto
D3
Zan
d3
lightbulb
FaceBook
ok
Combinator
'Case
Study'
Etsy
Shopify
s
Pharma
1's
janky
subarrays
1s
Lorem
Ipsum
Speedgrader
bolded
Krichevsky
battlestations
TBD
Chevoi
github
jpg
PWD
dirname
css
dirs
ISC
console
printf
console
Codeacademy's
WWCD
bootcamp's
relatedly
Jayabose
wireframe
untracked
some
urls
oneline
this
message'
git's
passcode
capstoning
performant
YAGNI
YAGNI
Keiko
tactiles
clickies
Zilents
Zealio
linears
Kailh
scrollback
monitorable
Budafly
HobbyBoard
Tobi
FK
Trello's
Sharity
NFTy
Miru
Vue
CanvasJS
LRU
Sprawlopolis
Morte
distro
CNAME
signin
keeb
webhook
Coolors
Gravehold
Terraforming
pw
MUI
NiFTy
Nunn
ortholinear
MX
Shinobi
Pok
DJCJ
ftw
Raz
Sharity's
speedtest
Comcast
Jaffes
cloudified
ERF
app
EOW
EnCanto
amped
megaclass
pm
Rohanning
Mazzilli
Solaque
Zaylors
Rodica
IANAD
partyrex
partypoop
nighter
KayKay
Andragogy
MacKrell
Dingman
commentariat
shitless
miscommunicate
miscommunicated
GitLab
GitLab's
GitLab's
GitLab's
Marp
api
learnings
checkmark
Calendly
LST
Gaal
Kertis
Lili
Mukayila
Nima
XKCD
Codeberg
Gitbook
tecosaur
xdotool
getactivewindow
gui
'primary
'utf8
args
em
arg
noweb
résumé
Mya
Poaty
VSC
vscodevim
Bibi's
stringp
MacBook
TSA
Pluralsight
Nima's
org's
padlines
Fuschia
Pinktastic
As
playdate
persp
Sangun
Maher
Jadoa
Yi
NFTs
tbh
Amex
Kodkollektivet
SSID
NetworkManager
nmcli
vpn
Aakre
EOB
vterm
cmd
bindkey
crj
turn
kbd
vterm
Sedgwick
backtab
Keymaps
Keypress
Keymapping
Mussie
Tussie
masukomi
ClojureScript
telehealth
Grammarly
duplicative
smtp
smtpmail
isync
PascalCase
preprocessors
dired's
iso
dirvish
hardlinks
alexluigit
flycheck
regexes
Rando
Flatbush
Salesforce
GCal
IM
hijinks
convos
codeshare
proced
Tikka
tikka
presenteeism
Na'Taja
Szekeres
Frink
Wattanachaiyot
Rakon
Faraó
Szekeres
funder
Taija
Ari
gitconfig
gitconfig
Replit
Ariunna
Ariunaa
DMing
Kerridene
Marangely
Vandhana
Riya
Markeya
Yasser
Zana
missable
missable
Shaniqua
Kanique
ShawnDe
unpublishing
The
Rosh
Hashanah
incentivizes
Neovim
Plex
homoiconic
Timmer
variable
org
default
mode
default
fixed
FiraGO
Leuven
English
Kippur
Yom
Dotemacs
Termux
recentf
Burbs
Burbs
Dotemacs
Termux
termux
T
T
Prettifies
IDM
strikethrough
Otiti
Myagmar
Diandre
Benjemou
Cortez
Osei
Fachin
Desrosiers
Jenel
Almodovar
Jyoti
Karimah
Reavis
Keeanu
Kwabe
Sarpong
Mckoy
Carree
Fodera
Lundy
Coston
Tafari
Excell
Touhami
Benmessaoud
Trystan
Muniz
Tsegereda
Yonas
Mohan
Lesane
headspace
codebases
Ariela
Benmessoud
unstage
unstaged
McKoy
Shani
Shani
symex
Michaelangelo
dojo
PATs
vc
Ahh
What've
rhaps0dy
telotortium
Chillstep
DropSignal
SONGCAST
MDN's
cheatsheets
Tafari's
Sharpening's
Michelle
ands
PluralSight
SICP
ReferenceErrors
backticks
Geiser
mathy
keymapping
typeof
data's
Trystan's
Kwabe's
joinpursuit
joinpursuit's
KRs
andragogy
Washdry
Nitter
reforking
excellers
Karimah's
Comms
ers
Megamouth
Shaquala
IDEs
roadmap
HONAMs
Evangelion
acronymizing
btw
SUGU
Sugu
Sugus
sugu's
sugu
sugus
nintos
Jihan
Sahleem
zippas
Jons
Battlecrest
DevOps
StumpWM
HyperText
FAE
REPL's
gifs
MBP
Eline
playmat
Gaston
Jafar
EIN
courseware
XHRs
Lakisha
mouseover
Channukah
GameBoy
Airbnb
refactorings
NodeList
minorly
mentee
Wacom
itty
Trumpist
WFH
PowerShell
authinfo
Superagenda
sugas
kenjos
Chatwin
Seb
Watcherwoman
Adiyodi
Lovelady
Orloff
Diaz
Kady
Weghe
Vix
dieselpunk
Andrieski
mothman
Greenstreet
mancer
mancer
Phosphoromancy
JWTs
HealthTech
Lipson
Genji
Meers
Squarrel
Oathers
Maxtrix
NELLI
TREEHOUSE
Treehouse
Nelli
GoldenEye
Doone
Scarlett
Doone
Proliant
Sauron
Westeros
Dunford
Rolfe
Rettendon
Goosen
Mitchell
McCabe
Scarlett's
Kingsglaive
Marlott
BAFTA
Trudie
Geoff
Dinsdale
Reece
Battersby
Spansky's
Wakelam
Menaul
Lamarr
Hedy
BAFTA
Lucis
Lucis
Snowpiercer
Wolfwalkers
Tomm
Tomm
Kneafsey
Whittaker
Metacritic
Fi
Cronenberg
Cronenberg
Riseborough
Krasinski
Emmys
Sewell
Thewlis
Keepin
Pertwee
crash
Donohoe
another's
Pascale
Ferran
Bohbot
Pascale
Ferran
Coulloc'h
Hippolyte
Girardot
Metacritic
Winsor
Winsor
Byfield
Whately
BAFTA
Hackett
Kavanagh
Liebmann
BAFTA
Chater
Brierley
BAFTA
Windprints
Wicht
Dahms
Potgieter
Wicht
Egelhof
Fong
Niehaus
Bettinson
Cookson
Teale
Anny
Tobin
MuppeTelevision
Charleson
Emer
Figgis
McDermott
Emmys
Poley
Diarmuid
Fricker
Condou
s
rainforests
Hitman
boho
Sally4Ever
uncheck
PID
PyCharm
DevelopIntelligence
tripical
Ket
Malcony
Talcony
Hamp
airtank
Niko
foci
Fooda
Zillow
TicketMaster
Twilio
CapitalOne
MSNBC
eTrade
Expedia
Coindesk's
BitCoin
Example
Fundamentals
Unauthorized
RPC
REST's
Notepad's
RESTfulness
HATEOAS
ADHD
SIBO
Uptime
Freemium
microservices
freemium
Resy
OpenTable
Venmo
FB
IG
ifttt
YAML
SwaggerHub
JWT
barcode
MITM
OpenID
OAuth's
Sanitization
tradeoffs
hotfix
detangling
Brinni
Platey
Maiasaura
Saura
Brachi
Circly
Minny
KUI
inboxed
useFsEvents
useFsEvents
dynamicPriority
fileWhichChangesOften
Bellfaire
VMs
VM
CodePipeline
Tacko
olds
TurboTax
Nico
VM
Busytown
BPL
useState
untrusted
rebasing
reflog
XSS
untrusted
tsc
eslint
Zustand
Schemas
Detangler
BlueSleep
StormWind
B2B
Zora
Seussical
MNT
protobuf
Natadaly
Whooo
Whooooaaaaa
Spinosaur
spinosaur
Maiasaurs
Brachiosaurs
Spinosaurs
Mmhmm
TP
GPT
ChatGPT
OpenAI's
Golrex
capturer
Newkirk
pm
Montclair
unstyled
NodeJS
NodeJS
unstyled
OpenAI
frameworkless
Kiara
runtimes
insertOne
bodyParser
schemas
mongoDB
uri
schema
from
to
User
Post
User
friend
Friend
author
s
sender
receiver
f'A
friendrequests
mongodb
myDatabase
Catie
Vyond
Sqlite
DOOMDIR
Ouroboros
AnnieCannons
ChatGPT's
blazingly
Vuex
Ghibli
Ghibli
impactfully
internet's
whatever's
xkcd
keymaps
EFS
Vimmers
Vimmish
EFS
spacebar
keybound
discoverability
sharts
nnnnn
Unmark
Unmarks
Neidhardt
Magit's
treemacs
ui
introspectability
Crafters
Flymake
Eglot
Vertico's
fi
wi
quickrun
Smartparens
uncoolest
dotfiles
unintuitive
deprioritize
mmmmm
Eradio
keypress
soooo
autocorrection
autocorrecting
Oooh
minibuffers
uptime
Kia
Jiggi
Scarllette
touchpad
Catie's
Da'Kirah
SDLC
Thinkpads
Jakki
symlinked
Stavrou
Protesilaos
onesies
onesie
mysterioso
cuuuute
Sudafed
Elfeed
RSS
TCF
th
Fenris
BGG
Evernote
OneNote
weirdnesses
TCF
th
Figma
Workflowy
Goshdarnit
MacBooks
Hailey
Dorcus
Nic
Jazmine
Staysha
Macbook
Yasnippet's
CodeCademy
Juneteenth
ItP
Lai
Sedlec
Royale
Naturopolis
Agropolis
Gryphon
Unsurmountable
Ragemore
Numbsters
Noir
SpaceShipped
paver
Wildtails
EOL
Gaw
techcident
gptel
chatgpt
ItP
robyn
julie
Lai
Avy
Juneteenth
pstore
unconfident
Zoomcident
there'd
Atlassian
Guidewire
HVAC
deprioritizing
Bing
Sourcetree
Udemy's
SAML
CSV
Homebrew's
OMZ's
Classpocalypse
Horsepeople
tiktok
youtube
unpublish
rewordings
itp
Siri
Cheatsheet
cheatsheet
colinrjaffe
OMZ
Jiggii
Scarllett
userinyerface
GHP
H1s
Craigslist
Anchal
Ritwick
parentheticals
Alfie
favicon
forkable
HRM
CodePen
BCONTR
bconatr
Baconator
px
sublists
CDN
Clockify
math's
Baconator
BCONTR
BCONATR
bconatr
GPL
W3C
unergonomic
Vimmy
undos
eglot
Emojinator
LOs
wttr
Weathery
Garay
Fintech
PLP
Rosemond
Rambly
Staysha's
Ghayad
bookended
Wherever's
Templeton
spinosaurs
Dayquil
DevTools
serverless
ORMs
serverful
unmatching
prepending
CBE
Symexes
symexes
Treesitter
Symex's
Lispyville
eww
cleverparens
paredit
natively
fastboot
textobj
Emac's
matchit
andragogical
Multiedit
Lissner's
readline
Reindents
unshortcutted
Emacsy
Floorp
This'll
Imenu
Bookbag
func
lispy
gsetq
LionyxML
MELPA
reorderings
bork
siloed
Symexy
Detangles
initiater
bookbag
fullscreened
sqlite
capfs
IME
WORDs
Glassdoor
editability
alphapapa's
dito
NeoVim
Flyspell
Vundo
Avy
λ
WYSIWIG
brainer
wcheck
TikTok
looooong
Ediff
lawyerin
Groupon
GoDaddy
wgrep
openai
ignoreCapsLock
passKeys
closql
codelaong
timeframes
tempel
goshdarnit
Reformats
W3D
codealongs
Rideshare
tanzanite
CODE'S
Railwaycat
Toeey
Ankly
Canva
readFile
Recipea
stringifying
SystemCrafters
serieses
Wyckoff
Earnshaw
Shoshanna
TextMate
woofy
Henergy
memer
memeing
she'll
NPM's
GETs
RESTfully
Disambiguates
unrefactored
CRUD's
Flyspell
upcase
flymake
Manik
libs
Hannukah
upcase
upcasing
downcasing
downcase
http
Shashi
Kratts
to
audiobook
jammies
A
Draftosaurus
Barenpark
Celestia
Battlelore
Obscurio
Kemet
thinky
timesink
Dahan
VAMPIRIC
Millenials
spellbook
compilable
there'll
configging
LLMs
hyperfocusing
Oreos
Reimagining
Thunderspeaker
SAV
Snagglefang
Dirathian
Gorefly
Bylax
Whitefur
E1c
Bo
Starcutter
Betafly
Circuipede
Vibrascuttle
Wattroach
Dataworm
Electrosting
Ampeater
Electick
Dyna
Shockwasp
Blastbot
Synchron
Chillbeak
Micropulse
Riddlemaster
Doubtweaver
Furion
Sabaki
Continnua
Lunaris
Randomizer
Reshi
Gavril
Nasma
Toli
Alexios
Deleth
Hadronic
Threadblade
Spinebiter
Armblades
Scattershooter
Swashbot
Nassai
Rustbeard
Dirath
Felis
Shuriken
Solarus
Lithocule
Katana
Naoko
Tagteam
Phantoscope
Rentek
neurodivergent
Sahil
Innovare's
underserved
EdTech
Scarry
overfitted
Zofia
fixups
Gościcki
Dangerroom
VCSs
dangerroom's
Kiki's
eyeline
Yessir
parseable
Yoto
verbosities
McCarthys
Neotree's
Emacsing
SoHo
REI
Mieville's
Tribeca
Covid
Fillorian
Hyman
Rattening
Gamergate
AuDHD
Nala
Sarabi
Rafiki
Mufasa
Sarabi's
Zazu
Nala's
Pumbaa
Bibi
Dustess
Zarjeena
Blumpy
Elsewise
Pridelands
UPPAbaby
Ingoyama
Dunkin
Warcraft
wha
RoboWar
iMac
IMing
overmedicating
Ditmas
fansite
husbandhood
Yasmin's
SSD
WoW
metacognition
Staycation
whoever's
Elaina
Nala's
Deno's
WCS
steampunky
Railsea
SFX
bangy
Scrying
Massi
literacies
Uncommon's
Lexive
Sangha
Millhouse
HTMX
schoolers
DEI
Bonso
Sixian
REpresentational
OSM
underserving
schoolers
Natur
Combopolis
Ultimopolis
Buttonshy
Gamery
Peduzzi
classesportal
MassPIRG
CPPAX
Stormwind
ENIAC
unchastened
so
effortfully
MERN
Buttonshy
JSPB
norovirus
Yaakov
paystub
nvm
naptime
mailto
serieseses
afterburn
enum
PDL
Paxlovid
juuust
Swiffer
each's
Rosalina
Firestar
Doop
Fantomex
Pryde
Shatterstar
Nightcrawler
Longshot
Psylocke
Namor
Northstar
Peni
Symbiote
Morlun
Sunfire
Yondu
Magik
Yondu
Magik
Sunfire
Yondu
Punisher
Okoye
Shuri
Korg
Deadpool
Drax
Gwenpool
Elektra
Groot
Hawkeye
Havok
Gamora
Pyro
Deathbird
Stryfe
ToolKit
Morgan
CRUD
Bakhodir
Asha
Ibrahim
Morty
Sarjeena
todos
ELPA
hawck
Setup
innerHTML
Vite
Props
Ripgrep
Waybar
PokeAPI
Eshell
Destructure
onclick
ul
DOMContentLoaded
todoId
DOCTYPE
Checkwriters
ve
didn
Pajuste
Viktoria
Markovic
Lunn
isn
Dangerroom
parsability
REACTO
algo
colinjaffe
dyll
vscode
Org Mode
Basic Settings

I’ll have a lot more here soon, but for now, here’s a keyboard shortcut for searching Org headings.

(use-package org
  :general
  (leader "m/" '(consult-org-heading :which-key "Search current headings.")))
Org/Evil Integration

Let’s integrate Evil Mode with Org mode with this nice package.

Mostly this is self-explanatory code if you dive into the variables. I do some extra keybinding work, including disabling their keybindings for some commands I like defined globally.

(use-package evil-org
  :diminish
  :after (evil org)
  :init
  (add-hook 'org-mode-hook #'evil-org-mode)
  (set-var org-return-follows-link t
	     evil-org-use-additional-insert t)
  :config
  (evil-org-set-key-theme '(textobjects todo additional))
  (evil-define-key '(normal visual insert) 'evil-org-mode
    (kbd "C-S-k") nil
    (kbd "C-S-h") nil)
  :general
  (:keymaps 'org-mode-map
	      :states 'insert
	      "RET" #'evil-org-return)
  (:keymaps 'org-mode-map :states '(motion normal visual)
	      "gl" #'org-down-element
	      "gh" #'org-up-element
	      "gk" #'org-backward-element
	      "gj" #'org-forward-element))
Tangle Settings

For now, all we’re specifying is that when a directory doesn’t exist in a file path we’re tangling, we should make that directory.

(use-package org
  :config
  (add-to-list 'org-babel-default-header-args '(:mkdirp . "yes")))
Emacs Lisp Code Block Settings

All Emacs Lisp should have lexical scope, by default.

(use-package org
  :init
  (setq org-babel-default-header-args:emacs-lisp '((:lexical . "yes"))))
Markdown

The not-quite-as-good-as-Org-but-more-universally-spoken markup language.

Let’s see if we can hit these customizations quickly. We:

  • set gfm-mode to be the main mode we use for Markdown files. GFM is GitHub-Flavored Markdown, which is a reasonably popular extension of the Markdwon format.
  • set a bunch of markdown-mode’s configuration variables you can check out yourself
  • make sure our fonts scale appropriately
  • add a command to add a new heading, similar to org mode’s approach (should definitely be refactored at some point)
  • add a command to kill the quick note buffer and frame, useful for when I’m taking quick notes in a separate frame. I have to make wl-copy my default clipboard-copying to make interprogram clipboards save after the frame is deleted.
  • set my favorite JS mode to run for JS code blocks: RJSX Mode
  • allow promotion, demotion, and movement in insert state
  • Add edit-indirect, a package that allows you to edit an embedded code block in a dedicated code-oriented buffer in Markdown buffers, which is another, “Oh, cuuuute, it thinks it’s Org Mode!” kind of feature.
  • Add some Evil keybindings with Evil Markdown Mode.
 (use-package markdown-mode
   :preface
   (defun crj--copy-to-clipboard (text)
     (let ((process-connection-type nil))
	(start-process "wl-copy" nil "wl-copy" text)))

   (setq interprogram-cut-function 'crj--copy-to-clipboard)

   (defun crj-kill-quick-note ()
     "Kills the quick-note text and frame, and saves it with its contents gone."
     (interactive)
     (clipboard-kill-region (point-min) (point-max))
     (save-buffer)
     (delete-frame))

   (defun crj-add-markdown-header ()
     "Add a markdown header after the current one, at the same level."
     (interactive)
     (let ((level (crj--get-markdown-level)))
	(when (thing-at-point-looking-at markdown-regex-header)
	  (forward-char))
	(if (re-search-forward markdown-regex-header nil t)
	    (forward-line -1)
	  (goto-char (point-max)))
	(markdown-insert-header level nil nil))
     (when (featurep 'evil)
	(evil-insert-state)))

   (defun crj--get-markdown-level ()
     "Helper function to get the current markdown heading level.

 Used by `crj-add-markdown-header'"
     (save-excursion
	(unless (thing-at-point-looking-at markdown-regex-header)
	  (re-search-backward markdown-regex-header nil t))
	(markdown-outline-level)))
   :mode
   ("\\.\\(?:md\\|markdown\\|mkd\\|mdown\\|mkdn\\|mdwn\\)\\'" . gfm-mode)
   :gfhook
   #'variable-pitch-mode
   :init
   (set-var markdown-indent-on-enter 'indent-and-new-item
	     markdown-list-indent-width 2
	     markdown-fontify-code-blocks-natively t
	     markdown-asymmetric-header t)
   :config
   (add-to-list 'markdown-code-lang-modes '("javascript" . rjsx-mode))
   :general
   (general-def 'insert markdown-mode-map
     "M-l" #'markdown-demote
     "M-h" #'markdown-promote
     "M-k" #'markdown-move-up
     "M-j" #'markdown-move-down)
   (general-def '(insert normal) markdown-mode-map
     "C-<return>" #'crj-add-markdown-header
     "C-c k" #'crj-kill-quick-note
     "C-c C-k" #'crj-kill-quick-note))

 (use-package edit-indirect)

 (use-package evil-markdown
   :diminish
   :vc (evil-markdown :url "https://www.github.com/Somelauw/evil-markdown")
   :after markdown-mode
   :ghook ('(markdown-mode-hook gfm-mode-hook))
   :general
   (:keymaps 'evil-markdown-mode-map
	      :states '(insert emacs)
	      "C-d" nil))

Avoiding Mixing Init File and Custom File

(use-package emacs
  :init
  (setq custom-file "~/.emacs.d/emacs-custom-file.el")
  (unless (file-exists-p custom-file)
    (write-region "" nil custom-file)))

Org Babel Settings

(require 'ob-shell)
(org-babel-do-load-languages
 'org-babel-load-languages
 '((shell . t)
   (emacs-lisp . t)))

Evil Mode

Basic Setup
(use-package evil
  :ensure t
  :preface (setq evil-want-keybinding nil)
  :init
  (setq evil-want-keybinding nil
	    evil-want-fine-undo t)
  :custom (evil-undo-system 'undo-redo)
  :config
  (evil-mode 1)
  (setq evil-want-keybinding t))
Evil Bindings For Other Packages

This famous package contains Evil bindings for many many other packages.

(use-package evil-collection
  :diminish 'unimpaired
  :diminish 'evil-collection-unimpaired-mode
  :after evil
  :init
  (evil-collection-init)
  (evil-collection-quickrun-setup)
  :general
  (general-unbind '(normal visual motion) evil-collection-unimpaired-mode-map
    "]l"
    "[l"
    "[m"
    "]m"
    "[e"
    "]e")

  (general-def '(normal visual motion) :prefix "["
    "B" '(org-previous-block :which-key "Go to previous org block.")
    "m" '(evil-collection-unimpaired-move-text-up :which-key "Move text up.")
    "e" '(evil-collection-unimpaired-previous-error :which-key "Go to previous error."))
  (general-def '(normal visual motion) :prefix "]"
    "B" '(org-next-block :which-key "Go to next org block.")
    "m" '(evil-collection-unimpaired-move-text-down :which-key "Move text down.")
    "e" '(evil-collection-unimpaired-next-error :which-key "Go to next error.")))
Evil-Style Commenting

Evil Nerd Commenter is a really great way to handle comments!

(use-package evil-nerd-commenter
  :after evil
  :general
  ([remap comment-line] #'evilnc-comment-or-uncomment-lines)
  (:keymaps 'normal :prefix "g"
	      "c" '(evilnc-comment-operator :which-key "Toggle comment.")
	      "C" '(evilnc-copy-and-comment-operator :which-key "Copy and comment.")
	      "K" '(evilnc-comment-box :which-key "Create comment box."))
  (general-def 'normal evil-inner-text-objects-map
    "c" #'evilnc-inner-comment)
  (general-def 'normal evil-outer-text-objects-map
    "c" #'evilnc-outer-comment))
Evil Number Manipulation
(use-package evil-numbers
  :after evil
  :general
  (leader "+" #'evil-numbers/inc-at-pt
	    "-" #'evil-numbers/dec-at-pt))

Some Sane Defaults

(defalias 'yes-or-no-p 'y-or-n-p)
(repeat-mode 1)
(pixel-scroll-precision-mode 1)
(global-auto-revert-mode 1)
(save-place-mode 1)
(auto-save-mode 1)
(menu-bar-mode -1)
(scroll-bar-mode -1)
(tool-bar-mode -1)
(global-display-line-numbers-mode 1)
(general-setq vc-follow-symlinks t
		save-interprogram-paste-before-kill t
		recentf-max-saved-items 50
		global-auto-revert-non-file-buffers t
		auto-save-timeout 10
		auto-save-no-message t
		backup-by-copying t
		ediff-window-setup-function 'ediff-setup-windows-plain
		frame-inhibit-implied-resize t
		backup-directory-alist `(("." . ,(concat user-emacs-directory "/backups")))
		gc-cons-threshold (* 100 1000 1000)
		uniquify-buffer-name-style 'forward
		load-prefer-newer t)

Color Theme

(set-var modus-themes-italic-constructs t
	   modus-themes-bold-constructs t
	   modus-themes-org-blocks 'gray-background
	   modus-themes-completions (quote
				     ((matches . (intense background underline bold))
				      (selection . (accented intense bold))))

	   modus-themes-headings '((0 . (variable-pitch  1.8))
				   (1 . (variable-pitch  1.8))
				   (2 . (variable-pitch  1.6))
				   (3 . (variable-pitch  1.4))
				   (4 . (variable-pitch  1.2))))

(mapc #'disable-theme custom-enabled-themes)
(load-theme 'modus-operandi t)

Remember Things

(recentf-mode 1)
(set-var history-length 100)
(savehist-mode 1)

Completion

Minibuffer Completion
Minibuffer Completion Interface

Vertico is a popular Emacs minibuffer search interface. It gives you a simple but powerful UI for accessing whatever you’re looking to access, and it does it with a minimum of code, mostly focusing on extending the built-in Emacs interface. This makes it fast to use and, importantly, easy for the devs to debug.

What we’ll do to configure it is:

  • Set “reverse” as the default interface setup.
  • Tun on the ability to toggle interface setup between reverse and regular view.
  • Add the Vertico extension vertico-repeat and a keybinding to repeat previous searches.
  • Set Emacs to remember that repeat history.
  • Set the results of searches to wrap from bottom to top.
  • Fix some weirdness caused by the interplay of directories and completion.
  • Add keybindings for
    • more Vim-like j~/~k scrolling
    • scrolling by pages
    • going up a directory with a single delete

Here that all is:

(use-package vertico
  :init
  (vertico-mode)
  (vertico-multiform-mode)
  (setq vertico-cycle t
	  vertico-multiform-categories '((t reverse)))

  :config
  (add-to-list 'load-path (expand-file-name "vertico/extensions/" user-emacs-directory))
  (require 'vertico-directory)
  (require 'vertico-repeat)
  (add-hook 'minibuffer-setup-hook #'vertico-repeat-save)
  (with-eval-after-load 'savehist
    (add-to-list 'savehist-additional-variables 'vertico-repeat-history))
  (add-hook 'rfn-eshadow-update-overlay-hook #'vertico-directory-tidy)
  :general
  (leader "'" '(vertico-repeat-select :which-key "Repeat previous vertico searches."))
  (:keymaps 'vertico-map
	      "C-k" #'vertico-next
	      "C-j" #'vertico-previous
	      "C-S-p" #'vertico-scroll-up
	      "C-S-n" #'vertico-scroll-down
	      "C-S-k" #'vertico-scroll-up
	      "C-S-j" #'vertico-scroll-down
	      "RET" #'vertico-directory-enter
	      "DEL" #'vertico-directory-delete-char
	      "M-DEL" #'vertico-directory-delete-word))
Filtering Minibuffer Searches

The Orderless package is powerful and fascinating. It decides how the data you’re searching is filtered as you type, and you can even search in different ways in the same search.

Here’s an example using the setup below.

  • I start a fuzzy search for files in a project.
  • I type in part of the name of the file I want to open.
  • Then I notice that some similarly named results are in a directory I want to exclude.
  • So I type in a ! (a pretty universal developer symbol for “not”) and the name of that directory, and those results are filtered out.
  • Then I realize I want only files that are in a directory called controller. So I type ^ (developer for “starts with”) and /controller. Finally, I want only JavaScript files, so I type $ (developer for “ends with”) and then type js, and boom!

Once you’re used to this speedy filtering process, it greatly cuts down on the time spent searching for whatever it is you want to do.

 (use-package orderless
   :preface
   (defun crj--vertico-orderless-dispatch (pattern _index _total)
     "The set of dispatch commands I use for filtering searches.

 Taken from the Doom Emacs project, which has added so much useful configuration code to the Emacs world. Thanks, Doom contributors!"
     (cond
      ;; Ensure $ works with Consult commands, which add disambiguation suffixes
      ((string-suffix-p "$" pattern)
	`(orderless-regexp . ,(concat (substring pattern 0 -1) "[\x200000-\x300000]*$")))
      ;; Ignore single !
      ((string= "!" pattern) `(orderless-literal . ""))
      ;; Without literal
      ((string-prefix-p "!" pattern) `(orderless-without-literal . ,(substring pattern 1)))
      ;; Character folding
      ((string-prefix-p "%" pattern) `(char-fold-to-regexp . ,(substring pattern 1)))
      ((string-suffix-p "%" pattern) `(char-fold-to-regexp . ,(substring pattern 0 -1)))
      ;; Initialism matching
      ((string-prefix-p "`" pattern) `(orderless-initialism . ,(substring pattern 1)))
      ((string-suffix-p "`" pattern) `(orderless-initialism . ,(substring pattern 0 -1)))
      ;; Literal matching
      ((string-prefix-p "=" pattern) `(orderless-literal . ,(substring pattern 1)))
      ((string-suffix-p "=" pattern) `(orderless-literal . ,(substring pattern 0 -1)))
      ;; Flex matching
      ((string-prefix-p "~" pattern) `(orderless-flex . ,(substring pattern 1)))
      ((string-suffix-p "~" pattern) `(orderless-flex . ,(substring pattern 0 -1)))))
   :init
   (set-var completion-ignore-case t
	     completion-styles '(orderless basic)
	     orderless-component-separator "#"
	     orderless-style-dispatchers '(crj--vertico-orderless-dispatch)
	     orderless-matching-styles '(orderless-flex orderless-literal orderless-regexp)
	     completion-category-overrides '((file (styles partial-completion)))))
Completion Annotation

Marginalia adds annotations to the completion candidates in the minibuffer. These annotations are particular to the kind of list we’re looking at–first lines of docstrings for functions, values for variables, etc.

(use-package marginalia
  :init
  (marginalia-mode 1)
  :bind (:map minibuffer-local-map ("M-A" . marginalia-cycle)))
In-Buffer Completion
Corfu

Corfu is the close-to-bare-metal-but-still-powerful in-buffer completion package. It’s increasingly popular in the Emacs community for good reason.

(use-package corfu
  :init
  (set-var corfu-cycle t
	     corfu-auto t
	     corfu-preselect 'prompt
	     corfu-separator ?#
	     corfu-on-exact-match nil
	     corfu-popupinfo-delay nil)
  (corfu-popupinfo-mode 1)
  (corfu-history-mode 1)
  (add-to-list 'savehist-additional-variables 'corfu-history)
  (global-corfu-mode)
  (general-unbind corfu-map "RET")
  :general
  (:states 'insert
	     "C-n" nil
	     "C-p" nil)
  (corfu-map "TAB" #'corfu-next
	       [tab] #'corfu-next
	       "S-TAB" #'corfu-previous
	       [backtab] #'corfu-previous
	       "<escape>" nil
	       "C-n" #'corfu-next
	       "C-p" #'corfu-previous
	       "C-S-n" #'corfu-scroll-up
	       "C-S-p" #'corfu-scroll-down
	       "C-S-j" #'corfu-scroll-up
	       "C-S-k" #'corfu-scroll-down
	       "C-g" #'corfu-quit
	       "C-<tab>" #'corfu-popupinfo-toggle))

(use-package kind-icon
  :after corfu
  :init
  (set-var kind-icon-use-icons t
	     kind-icon-default-face 'corfu-default
	     kind-icon-blend-background nil
	     kind-icon-blend-frac 0.08)
  :config
  (add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter))

(use-package emacs
  :init
  (setq tab-always-indent 'complete))
LSP

We’ll configure Language Server Protocol integration using the now-built-into-Emacs package Eglot.

Some noteworthy things here:

  • I’m trying to get orderless working with Eglot, and this isn’t quite doing it for some reason. Will have to dive further into it when I have some time.
  • Eglot booster does appear to be speeding up a famously slow Emacs LSP experience. It runs a separate process to work with LSP’s completion server, which makes a big difference to poor old single-threaded Emacs.
  • Eldoc, the wonderful Emacs documentation library, works well with Eglot, but Eglot is a little aggressive with printing the full documentation–something that bugs me even more in VS Code, but is still annoying here. Fortunately, setting the multi-line feature to nil allows us to have a non-distracting one-line doc at the bottom of the screen for the value at point, while keeping the full documentation a keypress away.
(use-package eglot
  :init
  (general-add-hook '(rjsx-mode-hook typescript-mode-hook clojure-mode-hook) #'eglot-ensure)
  (set-var eldoc-echo-area-use-multiline-p nil
	     completion-category-overrides '((eglot (styles orderless))
					     (eglot-capf (styles orderless))))
  :general
  (leader
    "cc" '(eglot :which-key "Run eglot in project root.")
    "cr" '(eglot-rename :which-key "Rename symbol in project.")))

(use-package eglot-booster
  :vc (eglot-booster :url "https://github.com/jdtsmith/eglot-booster")
  :after eglot
  :config (eglot-booster-mode))

(use-package cape
  :after eglot
  :config
  (advice-add 'eglot-completion-at-point :around #'cape-wrap-buster))
Eglot Booster

Emacs is single-threaded, which is a problem for LSP access. This Rust crate/Emacs package combo creates a separate process for handling LSP, then passes it to Emacs.

First, we need to install it.

Install Rust if you don’t already have it by running this in your terminal:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Then we’ll add to our path in .bashrc so that Rust’s cargo commands are executable:

PATH="$HOME/.cargo/bin:$PATH"

Now install the Rust crate using cargo in your terminal:

cargo install emacs-lsp-booster
Copilot

To get in-buffer suggestions as you type from GitHub Copilot, you must first have a GitHub account. You must also have npm.

After you’ve installed the below package, you need to run:

  • M-x copilot-install-server, which uses NPM to install it
  • followed by M-x copilot-login, which will display and copy to the clipboard a code to use, as well as launch your default browser and try to log into GitHub to authenticate Copilot. You’ll use the code to do so.
(use-package copilot
  :vc (copilot :url "https://github.com/copilot-emacs/copilot.el")
  :init
  (set-var copilot-max-char -1)
  :config
  (add-to-list 'copilot-major-mode-alist '("rjsx" . "javascriptreact"))
  :general
  (leader
    "tC" '(global-copilot-mode :which-key "Toggle copilot globally.")
    "tc" '(copilot-mode :which-key "Toggle copilot for current buffer."))
  (copilot-mode-map
   "M-RET" #'copilot-accept-completion
   "M-<tab>" #'copilot-next-completion
   "M-<iso-lefttab>" #'copilot-previous-completion
   "M-o" #'copilot-panel-complete
   "M-w" #'copilot-accept-completion-by-word
   "M-l" #'copilot-accept-completion-by-line
   "M-p" #'copilot-accept-completion-by-paragraph))

Contextual Menus

Sometimes you choose the action, then the item. For example, if you want to delete a file,

 (use-package embark
   :preface
   (defun crj-embark-act-noquit ()
     "Run action but don't quit the minibuffer afterwards."
     (interactive)
     (let ((embark-quit-after-action nil))
	(embark-act)))
   :init
   (set-var prefix-help-command #'embark-prefix-help-command)
   :config
   (add-to-list 'display-buffer-alist
		 '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*"
		   nil
		   (window-parameters (mode-line-format . none))))
   (defvar-keymap embark-help-actions
     :doc "Keymap for actions for help searches."
     :parent embark-general-map)
   (add-to-list 'embark-keymap-alist '(help . embark-help-actions))
   :general
   (general-def (embark-become-help-map embark-symbol-map)
     "x" #'execute-extended-command)
   (general-def (embark-command-map embark-function-map)
     "h" #'helpful-command)
   (general-def (embark-become-file+buffer-map)
     "d" #'delete-file)
   (leader "d" '(embark-act :which-key "Launch contextual menu for thing at point."))
   :bind
   (("C-." . embark-act)
    ("C->" . crj-embark-act-noquit)))

 (use-package embark-consult
   :hook
   (embark-collect-mode . consult-preview-at-point-mode))

Search

Emacs is really, really great at search.

Since a programmer’s life is spent in text, a great text search tool is a massive gain. I’ve seen mouse-driven workflows really struggle at finding the code they’re looking for. I’ve also seen people who are really good at using the mouse to find things, but they’re still slower than a good keyboard-driven search. And a great keyboard-driven search? It allows you to instantly find the exact part of the code that you’re looking for in the exact file.

Let’s set up one of my favorite searching packages: consult.

(use-package consult
  :vc (consult :url "https://github.com/minad/consult")
  :commands consult-line
  :general
  (leader "/" '(consult-line :which-key "Search in current buffer.")))

Some general search shortcuts, many of them consult-powered:

(use-package emacs
  :init
  (leader
    "," '(consult-buffer :which-key "Switch buffer or open recent file.")
    "<" '(consult-project-buffer :which-key "Switch to project-specific buffer.")
    "TAB" '(evil-switch-to-windows-last-buffer :which-key "Previous buffer.")
    ";" '(execute-extended-command :which-key "Run interactive command.")
    ":" '(eval-expression :which-key "Evaluate expression.")
    "." '(find-file :which-key "Open or create file.")
    "y" '(consult-yank-from-kill-ring :which-key "Select from clipboard history.")))
Ripgrep

Ripgrep is a great tool for running searches through text files. Let’s make sure we download it.

sudo apt install -y ripgrep
Editable Search Results
 (use-package wgrep
   :commands wgrep-change-to-wgrep-mode
   :config (setq wgrep-auto-save-buffer t)
   :general
   (general-def grep-mode-map
     "C-c C-e" '(wgrep-change-to-wgrep-mode :which-key "Switch to writable search results.")))

 (use-package emacs
   :preface
   (defun crj-vertico-embark-export-write ()
     "Export the current vertico results to a writable buffer if possible.

 Supports exporting consult-grep to wgrep, file to wdired, and consult-location to occur-edit.

 Credit to Doom Emacs."
     (interactive)
     (require 'embark)
     (require 'wgrep)
     (let* ((edit-command
	      (pcase-let ((`(,type . ,candidates)
			   (run-hook-with-args-until-success 'embark-candidate-collectors)))
		(pcase type
		  ('consult-grep #'wgrep-change-to-wgrep-mode)
		  ('file #'wdired-change-to-wdired-mode)
		  ('consult-location #'occur-edit-mode)
		  (x (user-error "embark category %S doesn't support writable export" x)))))
	     (embark-after-export-hook `(,@embark-after-export-hook ,edit-command)))
	(embark-export)))
   :general
   (general-def minibuffer-local-map
     "C-c C-e" '(crj-vertico-embark-export-write :which-key "Switch to writable search results."))
   (general-def dired-mode-map
     "C-c C-e" '(wdired-change-to-wdired-mode :which-key "Switch to writable search results.")))
add grep as backup to ripgrep

File Browser

Dired (the “directory editor”) is an Emacs file manager. And it’s really really good! It’s everything you used to use a file browser or a terminal for, but with all the benefits of both.

I’ll configure it to stick with one buffer, so as not to clutter up the buffer list.

(put 'dired-find-alternate-file 'disabled nil)
(set-var dired-kill-when-opening-new-dired-buffer t)
(general-define-key :states 'normal
		      :keymaps 'dired-mode-map
		      "RET" #'dired-find-alternate-file
		      "a" #'dired-find-file)

Shell

Running shells within Emacs is helpful sometimes, even if using pure Emacs is generally preferable.

We’ll do some minor setup for the shell. For now, simply skipping the confirmation before starting a new buffer.

(set-var async-shell-command-buffer #'new-buffer)

Emacs Git Integration

Like many (most?) emacsers, I use Magit to manage Git. It’s the best interface to Git–as powerful as the CLI, with a better keyboard-driven interface.

Only some minor settings changes from the basic setup:

  • Turn on insert state and off Copilot mode when writing Git commits. Though I know there are some AI features focused on commit messages and maybe I should check them out, right now plain Copilot is really bad at commit messages.
  • Make Magit a little more Evil by reclaiming the leader key, h and l, and the low-digit count keys.
  • Turn on confirmation.
  • Make the window setup a little tidier.
  • And add some shortcuts to both the general Magit menus and things I do more often, like clone and switch/create branches.
  • Use a complimentary package to open the browser for the current repo–or even the current file and line.
(use-package magit
  :preface
  (defun crj--set-up-git-commit-buffer ()
    (evil-insert-state)
    (copilot-mode -1))

  :init
  (add-hook 'git-commit-mode-hook 'crj--set-up-git-commit-buffer)
  (set-var evil-collection-magit-want-horizontal-movement t
	     magit-bury-buffer-function #'magit-restore-window-configuration
	     magit-no-confirm nil
	     magit-display-buffer-function #'magit-display-buffer-fullframe-status-v1
	     forge-add-default-bindings nil)
  :commands magit-status
  :general
  (:keymaps '(magit-mode-map
		magit-blame-read-only-mode-map
		magit-section-mode-map
		magit-diff-mode-map)
	      :states '(insert normal)
	      "1" #'digit-argument
	      "2" #'digit-argument
	      "3" #'digit-argument
	      "4" #'digit-argument)
  (with-editor-mode-map
   "C-c C-c" '(with-editor-finish :which-key "Finish editing.")
   "C-c c" '(with-editor-finish :which-key "Finish editing.")
   "C-c '" '(with-editor-finish :which-key "Finish editing.")
   "C-c k" '(with-editor-cancel :which-key "Cancel editing.")
   "C-c C-k" '(with-editor-cancel :which-key "Cancel editing."))
  (leader
    "gg" '(magit-status :which-key "Launch Git interface.")
    "gm" '(magit-dispatch :which-key "Show Git HUD.")
    "gb" '(magit-branch-or-checkout :which-key "Switch or create Git branch.")
    "gi" '(magit-init :which-key "Initialize a Git repo.")
    "gc" '(magit-clone :which-key "Git clone.")))

(use-package browse-at-remote
  :general
  (leader "gr" '(browse-at-remote :which-key "Browse repo's remote.")))

Programming Languages

JavaScript

For JS, I use RJSX Mode for its support of JSX. There are some good alternatives, I hear, so I may check them out at some point, but this works for me.

 (use-package emacs
   :init
   (setq js-indent-level 2
	  js2-strict-missing-semi-warning nil))

 (use-package rjsx-mode
   :preface
   (defun crj--auto-close-jsx-fragments (n)
     "Inserts matching JSX fragment.

 Credit: Yuri Pieters in this GitHub issue:

 https://github.com/felipeochoa/rjsx-mode/issues/112#issuecomment-773660200"
     (if (or (/= n 1) (not (and (eq (char-before) ?<) (eq (char-after) ?/)))) 't
	(insert ?> ?<)
	(backward-char)))
   :gfhook
   #'turn-off-flyspell
   :init
   (advice-add #'rjsx-electric-gt :before-while #'crj--auto-close-jsx-fragments)
   (add-to-list 'auto-mode-alist '("\\.jsx\\'" . rjsx-mode))
   (add-to-list 'auto-mode-alist '("\\.js\\'" . rjsx-mode))
   (add-to-list 'auto-mode-alist '("\\.tsx\\'" . rjsx-mode))
   (add-to-list 'auto-mode-alist '("\\.ts\\'" . rjsx-mode)))
Python

Install Python using Anaconda’s installer. After downloading it, you’ll need to execute it using:

chmod +x [path to Anaconda installer]

[path to Anaconda installer]

Then this goes at the bottom of your bashrc file:

__conda_setup="$('/home/colin/anaconda3/bin/conda' 'shell.bash' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/home/colin/anaconda3/etc/profile.d/conda.sh" ]; then
	  . "/home/colin/anaconda3/etc/profile.d/conda.sh"
    else
	  export PATH="/home/colin/anaconda3/bin:$PATH"
    fi
fi

unset __conda_setup

Applications

Emacs is an Operating System! Here aer some applications it runs.

Now, it’s a little hazy as to what feels like an application—even when you’re using Emacs as a text editor, you’re using an application built on top of the Lisp interpreter.

But the below feel more like applications than an in-built part of Emacs. Possibly just because they’re not part of most text editors?

Radio

Internet radio is still alive and well in Emacs.

(use-package eradio
  :preface
  (defun crj-play-station-dwim ()
    "Plays either the default station or the last station selected"
    (interactive)
    (eradio-play (or eradio-current-channel (cdar eradio-channels))))
  :init
  (general-setq eradio-channels '(("LO FLY Radio" . "http://64.20.39.8:8421/listen.pls?sid=1&t=.pls")
				    ("SomaFM - Fluid" . "https://somafm.com/fluid.pls")
				    ("SomaFM - Groove Salad." . "https://somafm.com/groovesalad.pls")
				    ("SomaFM - Secret Agent" . "https://somafm.com/secretagent.pls")))
  :general
  (leader
    "amr" '(crj-play-station-dwim :which-key "Play default internet radio.")
    "amR" '(eradio-stop :which-key "Stop playing internet radio.")
    "am C-r" '(eradio-play :which-key "Select radio station and play it.")))

Things To Document

  • Stuff from notes.md
  • wormhole - apt install

Things To Configure Next

  • fix copilot indentation warnings
  • increase Waybar font size
  • Org
  • Sync
  • AI
  • syntax highlight symbols and numbers
  • screenshot taking
  • Fix elisp indentation.
  • some basic menus and bindings
  • helpful
  • which-key
  • Evil elisp bindings
  • Evil bindings
    • evil collection
    • 3rd party evil stuff
    • other evil settings
    • evil next sentence motion
    • my evil stuff
  • emacs-in-vim-in-emacs
  • minor Emacs fixes
    • no recursive deletion confirmation needed
  • Keyboard daemon to normalize Emacs keybindings everywhere (hawck)
  • Completion
    • tree-sitter
    • Marginalia
  • Tangle block for minimal config.

Index

Here is where I tangle all the source blocks in this file, in the order I’d like and to the files I want.

Bash Config

First, our .bash_profile file should have all our paths, as well as our keychain setup.

<<cargo-path>>
<<export-path>>
<<keychain-settings>>

We should also make sure our .bashrc file loads that .bash_profile code.

And a couple other changes at the end!

# ~/.bashrc: executed by bash(1) for non-login shells.
# see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
# for examples

# If not running interactively, don't do anything
case $- in
    *i*) ;;
    *) return;;
esac

# don't put duplicate lines or lines starting with space in the history.
# See bash(1) for more options
HISTCONTROL=ignoreboth

# append to the history file, don't overwrite it
shopt -s histappend

# for setting history length see HISTSIZE and HISTFILESIZE in bash(1)
HISTSIZE=1000
HISTFILESIZE=2000

# check the window size after each command and, if necessary,
# update the values of LINES and COLUMNS.
shopt -s checkwinsize

# If set, the pattern "**" used in a pathname expansion context will
# match all files and zero or more directories and subdirectories.
#shopt -s globstar

# make less more friendly for non-text input files, see lesspipe(1)
#[ -x /usr/bin/lesspipe ] && eval "$(SHELL=/bin/sh lesspipe)"

# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
    debian_chroot=$(cat /etc/debian_chroot)
fi

# set a fancy prompt (non-color, unless we know we "want" color)
case "$TERM" in
    xterm-color|*-256color) color_prompt=yes;;
esac

# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
force_color_prompt=yes

if [ -n "$force_color_prompt" ]; then
    if [ -x /usr/bin/tput ] && tput setaf 1 >&/dev/null; then
	  # We have color support; assume it's compliant with Ecma-48
	  # (ISO/IEC-6429). (Lack of such support is extremely rare, and such
	  # a case would tend to support setf rather than setaf.)
	  color_prompt=yes
    else
	  color_prompt=
    fi
fi

if [ "$color_prompt" = yes ]; then
    PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
unset color_prompt force_color_prompt

# If this is an xterm set the title to user@host:dir
case "$TERM" in
    xterm*|rxvt*)
	  PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
	  ;;
    *)
	  ;;
esac

# enable color support of ls and also add handy aliases
if [ -x /usr/bin/dircolors ]; then
    test -r ~/.dircolors && eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"
    alias ls='ls --color=auto'
    #alias dir='dir --color=auto'
    #alias vdir='vdir --color=auto'

    #alias grep='grep --color=auto'
    #alias fgrep='fgrep --color=auto'
    #alias egrep='egrep --color=auto'
fi

# colored GCC warnings and errors
#export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'

# some more ls aliases
#alias ll='ls -l'
#alias la='ls -A'
#alias l='ls -CF'

# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.

if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi

# enable programmable completion features (you don't need to enable
# this, if it's already enabled in /etc/bash.bashrc and /etc/profile
# sources /etc/bash.bashrc).
if ! shopt -oq posix; then
    if [ -f /usr/share/bash-completion/bash_completion ]; then
	  . /usr/share/bash-completion/bash_completion
    elif [ -f /etc/bash_completion ]; then
	  . /etc/bash_completion
    fi
fi

if [ -f ~/.bash_profile ]; then
    . ~/.bash_profile
fi

<<bash-prompt>>
<<bash-vterm-integration>>
<<anaconda-setup>>
Emacs Config
<<run-emacs-as-server>>
<<package-setup>>
<<emacs-env-integration>>
<<vc-package-setup>>
<<custom-file-setup>>
<<evil-mode>>
<<define-leader-key>>
<<define-menus>>
<<set-var>>
<<lexical-scope>>
<<file-browser>>
<<sane-defaults>>
<<shell-setup>>
<<set-up-emacs-memory-systems>>
<<prefer-project-el-on-elpa>>
<<customize-project>>
<<project-root-extra-identifiers>>
<<minibuffer-selection-interface>>
<<search-filtering>>
<<completion-annotation>>
<<in-buffer-completion>>
<<improve-escape>>
<<line-number-settings>>
<<markdown-setup>>
<<org-look-and-feel>>
<<org-settings>>
<<font-definitions>>
<<theme>>
<<set-fixed-pitch-modes>>
<<make-line-numbers-monospaced>>
<<tangle-settings>>
<<variable-pitch-setup>>
<<word-wrap>>
<<magit>>
<<javascript-setup>>
<<evil-number-manipulation>>
<<evil-org-mode-configuration>>
<<evil-integration-with-third-party-packages>>
<<evil-style-commenting>>
<<lsp-setup>>
<<copilot-setup>>
<<spell-check-settings>>
<<search-line>>
<<search-shortcuts>>
<<contextual-menus>>
<<editable-search-results>>
<<emacs-vterm-terminal>>
<<multiple-vterm-buffers>>
<<streaming-radio>>

About

A guide to creating one particular kind of productive computer work environment (mine).

Resources

Stars

Watchers

Forks

Contributors 2

  •  
  •