Emacs From Scratch, Part 2: Package management
This is a series of tutorials geared around building up your own customized environment, using emacs, from scratch. You can find part 1 here You can find part 3 here
Installing and Managing Packages #
Requirements #
To follow along with this tutorial, all you need is an existing installation of Emacs 24, or package.el. I note 24 specifically because if you have Linux, your distribution might not have an Emacs package that is version 24 or higher.
You can find out your emacs version with the ‘M-x emacs-version’ in your Emacs. If you don’t have 24, Bozhidar Batsov wrote a great guide on installing emacs 24.
Conversely, you can install package.el. Simple add it somewhere to your .emacs.d and load it as shown in part 1.
Background #
Text editors tend to be limited in the initial functionality they provide. Even Emacs, which provides a larger set of base functionality and features than most, will probably not have everything you want. Luckily, like most other editors these days, Emacs provides methodology to extend your text editor by taking code others have written. Vim and Sublime call them plugins, Emacs calls them packages.
As of Emacs 24, packages management is now included by default. This means you have a way to:
- install packages: M-x package-install
- list all existing packages: M-x list-packages
But we have a couple steps to go until we reach package management nirvana.
The Code #
For this tutorial, let’s add two separate files into our ~/.emacs.d/ directory:
- my-packages.el
- my-loadpackages.el
And load .emacs.loadpackages files in your ~/.emacs:
(load "~/.emacs.d/my-loadpackages.el")
We’re going to split our code up into two parts: one file to define what packages we want to install, and another to load and set up those packages.
Adding packages archives #
Emacs 24’s packages manager allows the adding of additional package archives, the places where package.el looks for packages to install. In your my-packages.el, let’s tell Emacs to add some more package archives:
; my-packages.el
(require 'package)
(add-to-list 'package-archives '("melpa" . "http://melpa.milkbox.net/packages/") t)
(add-to-list 'package-archives '("marmalade" . "http://marmalade-repo.org/packages/") t)
(package-initialize)
So what are these package archives? Here’s some info about them:
- melpa is a package archive managed by Milkypostman. It’s the easiest package archive to add packages too, and is automatically updated when the package is. The go-to source for up to date, and the vast majority of, packages. However it’s worth noting that with cutting-edge comes instability, so that is a risk of stability one should be aware of. It’s worth noting I’ve never been broken for any package I’ve installed via melpa, however.
- marmalade is another third-party package manager. Marmalade tends to be more stable, due to the requirement that developers explicitely upload new versions of their packages.
I personally use both (and the built-in Emacs 24 package-archive), but if you don’t want to use one or the other, remove the offending statements from above.
The code above does the following:
- loads the package ‘package’ via the require keyword
- installs relevant package managers
- initializes the package system so definitions are loaded
Installing and Loading Packages on Startup #
Now that we have the package repositories we like, it’s time to install some packages! First, choose a package you’d like to install. I’m going to install magit, a very nice version control major mode for git, and yasnippet, a package to easily parameterize and inject templates as needed (e.g. a java class template). Remember, you can always find more package by using ‘M-x list-packages’
If you wanted to install these manually, all you would have to do is ‘M-x
package-install
(To give proper attribution, I adapted this method from snippets in this file in emacs-prelude.)
The first step is to define a list of packages you want installed on startup. In your my-packages.el, after the package archives have been initialized, let’s create a list and store our desired packages in them:
; my-packages.el
; defvar is the correct way to declare global variables
; you might see setq as well, but setq is supposed to be use just to set variables,
; not create them.
(defvar required-packages '( magit yasnippet ) "a list of packages to ensure are installed at launch.")
Now that required-packages is defined, we can use it to install some packages! Let’s add a few more lines to install these packages for us:
Add the following to my-packages.el:
; my-packages.el
(require 'cl)
; method to check if all packages are installed
(defun packages-installed-p ()
(loop for p in required-packages
when (not (package-installed-p p))
do (return nil)
finally (return t)
)
)
; if not all packages are installed, check one by one and install the missing ones.
(unless (packages-installed-p)
; check for new packages
(package versions)
(message "%s" "Emacs is now refreshing its package database...") (package-refresh-contents)
(message "%s" " done.")
; install the missing packages
(dolist (p required-packages)
(when (not (package-installed-p p)) (package-install p))
)
)
So what does this code do? Well:
- package-installed-p is from package.el and checks if a package is installed
- packages-installed-p checks if all desired packages are installed
- the unless clause:
- first checks if all packages are installed. If they are, no need to do extra work.
if not all packages are installed:
- refresh the package indices
- install each non-installed package.
So whenever I want to install a package, I just add it to the list. If you share your .emacs configuration across machines, or have to start from scratch, this makes it very easy to build an environment. Even if you completely blow away your existing packages.
Give it a try! shut down your emacs now and start it back up, and you should install the magit and yasnippet packages.
Loading and Configuring Packages #
So now we have packages installing automatically. How do we use them?
Each package has it’s own configuration, so it’s best to read the README or documentation. However, almost all packages require you to require it first. Let’s add a few lines to our .emacs.d/my-loadpackages.el:
; my-loadpackages.el
; loading package
(load "~/.emacs.d/my-packages.el")
(require 'magit)
(define-key global-map (kbd "C-c m") 'magit-status)
(require 'yasnippet)
(yas-global-mode 1)
(yas-load-directory "~/.emacs.d/snippets")
(add-hook 'term-mode-hook (lambda() (setq yas-dont-activate t)))
So each package section starts with a “require”, which loads a particular package into the existing emacs environment. This is required before configuring anything related no that package. Notice that I also use the require as a section header, defining what package is related to what configuration.
One thing to note here is that once a package is loaded via require, it’s methods are globally available EVERYWHERE. There’s no concept of importing just for the file in emacs lisp, you just add everything to this global context. However, most packages use a prefix, (such as ‘yas’ for yasnippet commands) so it doesn’t seem too cluttered.
Here we also see another use of add-hook, but it’s different this time: this time we hook it to a particular major mode. This means that this particular hook will activate when that major-mode is activated. This is useful when you want to activate specific behaviour for when you’re editing a particular kind of text (e.g. binding a shortcut to open up a python interpreter if you’re in a python major mode)
As an aside, here’s the configuration I’m setting here:
- binding C-c m to magit-status: this is an example of a custom shortcut for my environment. Wherever I am, I can hit C-c m and see the status of the git repository I’m in (if I’m in one).
- yas-global-mode: this ensures that yasnippet is activated globally. Since yasnippet doesn’t typically interfere with anything, and I’ve found that any sort of text I’m modifying benefits from snippets, It’s a good default to have.
- yas-load-directory: this allows me to load snippets from a specific location. I have custom snippets I store in there.
- (add-hook ‘term-mode-hook…): this is a little hack that needs to exist. Otherwise, tab-complete doesn’t work in Emacs’ terminal emulators such as ansi-mode.
Summary #
Here’s what we learned:
- emacs has a built-in (as of Emacs 24) package management system.
- can install third-party repositories by adding entries to package-archives
- can install packages manually with M-x install-package
- packages can be loaded via
(require '<package-name>)
Final Code #
Note: this includes code from part one
.emacs:
(load "~/.emacs.d/my-loadpackages.el")
(add-hook 'after-init-hook
'(lambda () (load "~/.emacs.d/my-noexternals.el") )
)
.emacs.d/my-noexternals.el:
; ~/.emacs.d/my-noexternals.el
;; Remove scrollbars, menu bars, and toolbars
(when (fboundp 'menu-bar-mode) (menu-bar-mode -1))
(when (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(when (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
;; Wind-move
(global-set-key (kbd "C-c C-j") 'windmove-left)
(global-set-key (kbd "C-c C-k") 'windmove-down)
(global-set-key (kbd "C-c C-l") 'windmove-up)
(global-set-key (kbd "C-c C-;") 'windmove-right)
.emacs.d/my-packages.el:
(require 'package)
(add-to-list 'package-archives
'("melpa" . "http://melpa.milkbox.net/packages/") t)
(add-to-list 'package-archives
'("marmalade" . "http://marmalade-repo.org/packages/") t)
(package-initialize)
(defvar required-packages
'( magit yasnippet )
"a list of packages to ensure are installed at launch."
)
; method to check if all packages are installed
(defun packages-installed-p ()
(loop for p in required-packages
when (not (package-installed-p p))
do (return nil) finally (return t))
)
; if not all packages are installed, check one by one and install the missing ones.
(unless (packages-installed-p)
; check for new packages
(package versions)
(message "%s" "Emacs is now refreshing its package database...")
(package-refresh-contents)
(message "%s" " done.")
; install the missing packages
(dolist (p required-packages)
(when (not (package-installed-p p))
(package-install p)))
)
.emacs.d/my-loadpackages.el:
; ~/.emacs.d/my-loadpackages.el
; loading package
(load "~/.emacs.d/my-packages.el")
(require 'magit)
(define-key global-map (kbd "C-c m") 'magit-status)
(require 'yasnippet)
(yas-global-mode 1)
(yas-load-directory "~/.emacs.d/snippets")
(add-hook 'term-mode-hook (lambda() (setq yas-dont-activate t)))
What’s Next #
Next tutorial, we’ll talk about writing our own methods and modifying behaviour ourselves.