The Emacs Operating System (EOS)
Table of Contents
Author | Lee Hinman (lee@writequit.org) |
Date | 2017-08-21 14:30:13 |
Emacs Operating System
This is my attempt at a complete workflow inside of Emacs.
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,
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.
Initial Preparation
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.
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).
Installation script
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 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
The EOS Module Set
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.
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
- 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
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")
package.el
Setup
My strategy with regard to packaging is simple, I make heavy use of use-package
which does most of the installing with the :ensure
keyword, but I need to set
up the sources at least
(require 'package) (package-initialize) ;; orgmode.org unfortunately does not support https (add-to-list 'package-archives '("org" . "http://orgmode.org/elpa/") t) (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)) ;; 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)
el-get
setup
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)
Module setup
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-ido) (try-load 'eos-helm) ;; (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!
License
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).