Author | {{{author}}} ({{{email}}}) |
Date | {{{time(%Y-%m-%d %H:%M:%S)}}} |
There are many like it, but this one is mine. So let’s start with a story.
I used to be a hardcore Vim guy (hence the website named after a Vim command), in 2010 I joined a small company called Sonian doing Clojure as a full-time gig. As part of the job, we all pair-programmed together on Clojure code in Emacs using TMUX. This meant that I needed to learn Emacs having never used it before. I dived in head first and found Emacs to be a more customizable and elegant editor.
Around the same time, I switched to typing in Dvorak, so if you are reading this configuration and wondering why some of the bindings are the way they are, remember that my keys might be different than someone on Qwerty :)
People often say about Emacs “sure it’s a great operating system, but it lacks a good editor” so I decided to call my configuration “EOS” for the Emacs Operating System. This is my tribute to the Complete Computing Environment, which heavily inspires this and from which I continue to find interesting tidbits to copy.
If you want to look at specific parts, jump down to the module list to see links to different parts of the configuration.
So usually you would check out this repository and then run make
in the
directory. That will tangle a bunch of .el
files. If you want to install this,
run make install
(or just make init
if you only want to run initialization
things).
Once it’s installed, make changes to the files directly and then just run make
to re-tangle the files, no re-installation necessary, since symlinks are set up
the first time you ran make install
.
If you get lost at any point, checking the org source is probably the best way to go, or send me an email (lee at writequit dot org) or a message on twitter.
Because the installation symlinks things into whatever directory EOS is checked
out it, it allows me to type make
and generate new .el
files without any
file copying.
This will tangle an initialization script and invoke it. This is handled by the
Makefile
which special-cases this one file. Basically the following goes into
initialize.sh
:
# Directory for user-installed scripts
mkdir -p ~/bin
# GnuPG
mkdir -p ~/.gnupg
chmod 700 ~/.gnupg
# SSH
mkdir -p ~/.ssh
chmod 700 ~/.ssh
# Emacs configuration folders
mkdir -p ~/.emacs.d
mkdir -p ~/.emacs.d/snippets
mkdir -p ~/.emacs.d/eshell
This initialization file is then run first thing when running make install
(or
you can run make initialize
manually to run this part).
install.sh
Warning! This will overwrite your current configuration if you run =install.sh=!
ln -sfv $PWD/eos.el ~/.emacs.d/init.el
ln -sfv $PWD ~/.emacs.d/eos
ln -sfv $PWD/site-lisp ~/.emacs.d/site-lisp
cp -vf bin/* ~/bin
Ideally, each module except for the “Core” module is optional and can be skipped if not desired. In practice though, I load all of them, because this is my config. I haven’t really tried loading them all individually to make sure I don’t have links between them.
- Core EOS - base Emacs settings and configurations
- Appearance - change the look-and-feel
- Navigation - helpers when navigating around Emacs
- Notification System - unifying notifications in Emacs with sauron
- Helm - incremental completion and selection framework
- Vertico - an alternative completion and selection framework
- Development (programming) System - various development settings
- Java - for the day job
- Clojure - for the old day job and the open source work
- Elasticsearch - so useful for things
- Git - usually the only VCS I use, so it has quite a few customizations
- Completion - auto-completing things while programming (and not programming)
- Org-mode and agenda - org is a fantastic organization tool
- Writing - various settings for writing human language
- Dired - directory browsing and file management
- Working with Remote Servers - transparently edit remote files with TRAMP
- Web browsing - internal and external browsing with eww
- Shell - shells inside and outside of Emacs, mostly inside with eshell
- Mail (Email) - mu4e configuration to keep mail inside
- IRC - ERC configuration for IRC inside of Emacs
- Distributed services - distributed services for things like ipfs and matrix
- RSS - keep up to date with websites I enjoy
- Twitter - social networking at its angriest
- Fun and Leisure - a catch-all for other things
- Music - listening to tunes with Emacs
init.el
is the file that Emacs reads when it starts up, so here we do most of
the bootstrapping before the EOS modules are loaded, then load the modules, then
some cleanup at the end. It’s worth noticing that even though this would tangle
to eos.el
by default, it gets symlinked to ~/.emacs.d/init.el
.
Since an error may occur in loading any EOS files, I set some debugging things so a debugger is entered if there’s a problem. These get unset after everything loads successfully.
(setq debug-on-error t)
(setq debug-on-quit t)
I load a couple of custom versions of libraries that are included in Emacs. This is so I can run a newer version than what’s bundled, in particular this checks for the existence and loads them if there are there, otherwise it uses the bundled version.
A custom version of CEDET:
;; Load a custom version of cedet, if available
(when (file-exists-p "~/src/elisp/cedet/cedet-devel-load.el")
(load "~/src/elisp/cedet/cedet-devel-load.el"))
I hardcode a version of of Org-mode:
;; Load a custom version of org-mode, if available
(when (file-exists-p "~/src/elisp/org-mode/lisp")
(add-to-list 'load-path "~/src/elisp/org-mode/lisp")
(add-to-list 'load-path "~/src/elisp/org-mode/contrib/lisp")
(require 'org))
Also, let’s make cl
things available right from the start
(require 'cl)
I can’t live without this, “x” on Dvorak is where “b” is on Qwerty, and it’s just too hard for all the C-x things I have to hit. Maybe one day I’ll just switch to evil (or god-mode) and be done with it.
For now, ‘t’ is much more convenient so I switch C-x
and C-t
on the
keyboard. I don’t transpose things nearly as often as I C-x
things
(define-key key-translation-map "\C-t" "\C-x")
(define-key key-translation-map "\C-x" "\C-t")
:ensure
keyword, but I need to set
up the sources at least
(require 'package)
(package-initialize)
(add-to-list 'package-archives
'("gnu" . "https://elpa.gnu.org/packages/") t)
(add-to-list 'package-archives
'("melpa-stable" . "https://stable.melpa.org/packages/") t)
(add-to-list 'package-archives
'("melpa" . "https://melpa.org/packages/") t)
Let’s also set up a custom file and load it before we do anything too fancy, we want to make sure to keep customize settings in their own file instead of init.el.
(setq custom-file "~/.emacs.d/custom.el")
(when (file-exists-p custom-file)
(load custom-file))
I define eos/did-refresh-packages
, which is used as a signal in install-pkgs
that we need to refresh the package archives.
(defvar eos/did-refresh-packages nil
"Flag for whether packages have been refreshed yet")
install-pkgs
is a simple elisp function that will iterate over a list, and
install each package in it, if it is not installed. If
eos/did-refresh-packages
is set to nil
, it’ll also refresh the package
manager.
(defun install-pkgs (list)
(dolist (pkg list)
(progn
(if (not (package-installed-p pkg))
(progn
(if (not eos/did-refresh-packages)
(progn (package-refresh-contents)
(setq eos/did-refresh-packages t)))
(package-install pkg))))))
Pin some of the packages that go wonky if I use the bleeding edge.
(when (boundp 'package-pinned-packages)
(setq package-pinned-packages
'((org-plus-contrib . "org")
(cider . "melpa-stable")
(ac-cider . "melpa-stable")
(clojure-mode . "melpa-stable")
(clojure-mode-extra-font-locking . "melpa-stable")
(company-cider . "melpa-stable"))))
Now, install the things we need in the future for all other package installation/configuration, in particular, use-package needs to be installed because we require it everywhere else.
(install-pkgs '(use-package diminish))
;; Load use-package, used for loading packages everywhere else
(require 'use-package nil t)
;; Set to t to debug package loading or nil to disable
(setq use-package-verbose nil)
;; Set to t to always defer package loading
(setq use-package-always-defer t)
(add-to-list 'load-path "~/.emacs.d/el-get/el-get")
(unless (require 'el-get nil 'noerror)
(with-current-buffer
(url-retrieve-synchronously
"https://raw.githubusercontent.com/dimitri/el-get/master/el-get-install.el")
(goto-char (point-max))
(eval-print-last-sexp)))
(add-to-list 'el-get-recipe-path "~/.emacs.d/el-get-user/recipes")
;;(el-get 'sync)
And now, let’s start things up by loading all of the modules. I’d eventually like to keep the module list in an org table and reference it here, but I’m not quite sure how that would work for tangling, so for now it’s hard-coded.
;; Mitigate Bug#28350 (security) in Emacs 25.2 and earlier.
(eval-after-load "enriched"
'(defun enriched-decode-display-prop (start end &optional param)
(list start end)))
(defvar after-eos-hook nil
"Hooks to run after all of the EOS has been loaded")
(defvar emacs-start-time (current-time)
"Time Emacs was started.")
;; Installed by `make install`
(add-to-list 'load-path "~/.emacs.d/eos/")
(defmacro try-load (module)
"Try to load the given module, logging an error if unable to load"
`(condition-case ex
(require ,module)
('error
(message "EOS: Unable to load [%s] module: %s" ,module ex))))
;; Override projectile's default map back to C-c p before it gets loaded by
;; anything
(setq projectile-keymap-prefix (kbd "C-c p"))
;; The EOS modules
(try-load 'eos-core)
;; Only load one completion framework (ido/helm/vertico/ivy)
;; (try-load 'eos-ido)
(try-load 'eos-helm)
;; (try-load 'eos-vertico)
;; (try-load 'eos-ivy)
(try-load 'eos-appearance)
(try-load 'eos-navigation)
(try-load 'eos-notify)
(try-load 'eos-completion)
(try-load 'eos-develop)
(try-load 'eos-git)
(try-load 'eos-es)
(try-load 'eos-org)
(try-load 'eos-writing)
(try-load 'eos-dired)
(try-load 'eos-remote)
(try-load 'eos-java)
(try-load 'eos-clojure)
(try-load 'eos-web)
(try-load 'eos-shell)
(try-load 'eos-mail)
(try-load 'eos-irc)
(try-load 'eos-distributed)
(try-load 'eos-rss)
(try-load 'eos-twitter)
(try-load 'eos-leisure)
(try-load 'eos-music)
;; Hooks
(add-hook 'after-eos-hook
(lambda ()
(message "The Emacs Operating System has been loaded")))
(defun eos/time-since-start ()
(float-time (time-subtract (current-time)
emacs-start-time)))
(add-hook 'after-eos-hook
`(lambda ()
(let ((elapsed (eos/time-since-start)))
(message "Loading %s...done (%.3fs)"
,load-file-name elapsed))) t)
(add-hook 'after-init-hook
`(lambda ()
(let ((elapsed (eos/time-since-start)))
(message "Loading %s...done (%.3fs) [after-init]"
,load-file-name elapsed))) t)
(run-hooks 'after-eos-hook)
(setq initial-scratch-message ";; ╔═╗┌─┐┬─┐┌─┐┌┬┐┌─┐┬ ┬\n;; ╚═╗│ ├┬┘├─┤ │ │ ├─┤\n;; ╚═╝└─┘┴└─┴ ┴ ┴ └─┘┴ ┴\n")
Turn debugging back off, if there were no errors then things successfully got loaded.
(setq debug-on-error nil)
(setq debug-on-quit nil)
If you’ve checked this out so far, head back up and check out the Module Set!
Copyright (C) 2015-2017 Lee Hinman <lee@writequit.org>
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
Code in this document is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
This document https://writequit.org/eos/ (either in its HTML format or in its Org format is licensed under the GNU Free Documentation License version 1.3 or later (http://www.gnu.org/copyleft/fdl.html).
The code examples and CSS stylesheets are licensed under the GNU General Public License v3 or later (http://www.gnu.org/licenses/gpl.html).