The Emacs Operating System (EOS)
This is my attempt at a complete workflow inside of Emacs. Emacs Operating System
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.
How to use these files
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,
make install (or just
make init if you only want to run initialization
Once it’s installed, make changes to the files directly and then just run
to re-tangle the files, no re-installation necessary, since symlinks are set up
the first time you ran
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
There is some initial prep that needs to happen before someone could run this, basically creating a bunch of directories so the tangled files and symlinks can go in the right place. Initial Preparation
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
# 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).
In addition to the prep script, there needs to be a script used for installation that actually links up the appropriate parts. It ends up in Installation script
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
There is of course the prerequisite, which is this file, and then EOS contains a number of different configurations and modules. This is a basic overview of the modules, which you should visit should you desire more information about a particular module. The EOS Module Set
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
- Development (programming) System - various development settings
- 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
eos.el by default, it gets symlinked to
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"))
And a custom version 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") (require 'org))
Also, let’s make
cl things available right from the start
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-t on the
keyboard. I don’t transpose things nearly as often as I
(define-key key-translation-map "\C-t" "\C-x") (define-key key-translation-map "\C-x" "\C-t")
My strategy with regard to packaging is simple, I make heavy use of use-package
which does most of the installing with the
:ensurekeyword, but I need to set up the sources at least
(require 'package) (package-initialize) (add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t) (add-to-list 'package-archives '("gnu" . "http://elpa.gnu.org/packages/") t) (add-to-list 'package-archives '("melpa-stable" . "http://stable.melpa.org/packages/") t) (add-to-list 'package-archives '("melpa" . "http://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))
eos/did-refresh-packages, which is used as a signal in
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
(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)) ;; 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)
I install el-get, but so far I haven’t really used it for much, because
everything I want is on MELPA, and I don’t really mind bleeding edge,
regardless, it’s there if I want it.
(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.
(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)))) ;; The EOS modules (try-load 'eos-core) (try-load 'eos-helm) ;;(try-load 'eos-ido) (try-load 'eos-appearance) (try-load 'eos-navigation) (try-load 'eos-notify) (try-load 'eos-develop) (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)
Turn debugging back off, if there were no errors then things successfully got loaded.
(setq debug-on-error nil) (setq debug-on-quit nil)