How I build my own spacemacs

Spacemacs is a great starter kit. It is the only kit that got my attention. I have been using it for one year or so. I know most of its most useful commands. I like the plug-and-play feeling of its layer system. I like the laziness of its packaging system. I like how the devs build a great terminal experience inside emacs. I like how it abstracts Evil from its users.

Spacemacs and its great UI.

But I recently grew tired of its clumsiness. It took like seven or eight second to load. I tried to remove some configuration layer. I removed chunks of config I did not need. At its best, Spacemacs took — on my machine — seven long seconds to load. I gave me the feeling that I did not need most of the full blown spacemacs distribution. Plus I wanted to understand how to setup my own emacs. I wanted to tweak it. I wanted to get my hands dirty.

Diving into the elisp-world…

During my year in the spacemacs world, I discovered four awesome packages : use-package, general.el, which-key and ivy. They are the backbone of my new setup.

I only need those four packages to build a good spacemacs experience.
An emacs-falcon at use-package speed.

use-package made my emacs fast

use-package is a package by the current maintainer of emacs. I use it to load packages lazily. They will not be loaded unless I call them, or one of my packages call them. It make the startup super fast.

The following code loads the package which-key, and make sure it is loadable. If not, it downloads it, thanks to key :ensure keyword.

(use-package which-key :ensure t

I use the :init keyword to execute bits of code before the package is loaded. The following will enable which-key in each buffer.

:init (which-key-mode)

I use the :config keyword to customize the module to my convenience. Those bits of code are executed after the package is loaded.

:config 
(which-key-setup-side-window-right-bottom)
(setq which-key-sort-order 'which-key-key-order-alpha
which-key-side-window-max-width 0.33
which-key-idle-delay 0.05)
)

To avoid a cluttered mode-line, I use the :diminish keyword of use-package.

:diminish which-key-mode

I could also use the following syntax, to replace the WK in the mode-line with Ꙍ.

:diminish (which-key-mode . "Ꙍ")

Here I showed you how I use use-package to load a package that is enabled globally at startup. But use-package has many option to make sure the package is not loaded if nobody needs it. I use the :commands keyword to load the package when a command in the list of command is called.

For example, if I want to use the ranger package, I do not need it until I call the (ranger) function. So I put (ranger) in the list of commands that will trigger the loading of ranger.

(use-package ranger :ensure t 
:commands (ranger)
:bind (("C-x d" . deer))
:config (setq ranger-cleanup-eagerly t)
)

use-package also provide the :bind keyword. I use it to describe the list of commands that will trigger the loading of my package, and the keybindings that I want to associate with the command. In the previous example, I mapped (deer) to C-x d. So, unless I call (ranger) before typing C-x d, the ranger package will not load.

This is the magic of use-package.
General.el makes a great leader.

general.el made my evil shine

General.el is the new evil-leader black. It makes it easy to implement leader keys, of any length you want. It also has nice integration with use-package and which-key. Its primary use is in combination with evil, but you can also use it with bare emacs.

(use-package general :ensure t 
:config
(general-evil-setup t)
(general-define-key
:states '(normal insert emacs)
:prefix "C-SPC"
:non-normal-prefix "C-SPC"
"l" '(avy-goto-line)
"a" 'align-regexp
)
  (general-define-key 
:states '(normal motion insert emacs)
:prefix "SPC"
"ar" '(ranger :which-key "call ranger")
"g" '(:ignore t :which-key "Git")
"gs" '(magit-status :which-key "git status")
)
)

The previous chunks load the general package at startup. This one is not lazily loaded. It uses (general-define-key) to define keys that are under the prefix C-SPC. When the evil-state is not normal, ie when I am in insert or visual mode, the prefix is also C-SPC, but I can set it to something different. So when I press C-SPC then l, it calls avy-goto-line.

The next chunks is all it takes to setup a Spacemacs-like interface to my favorite commands using the space bar as a prefix. Like in spacemacs, SPC ar calls ranger. Notice the :which-key keyword. Use it to describe your keybindings. The string I use will be displayed by the which-key package. Use the :ignore keyword when the key-press is only a prefix, and you want to describe the prefix via which-key.

Reading the emacs documentation…

Which-key will make your emacs friendly

The obvious benefit of spacemacs to me is its discoverability. Press a key, read the description of the prefix, press another key, etc…

which-key is a package that prints out a buffer of all the keybindings currently assigned to the prefix you type. In the previous example, if I type C-x, then a buffer prints out:

It is a listing of all the keyboard shortcut starting with C-x. But what if I want to know what is behind the RET keypress ? which-key provide a way to describe a prefix.

(which-key-add-key-based-replacements 
"C-x RET" "coding system - input"
)

So now when I press C-x, I see:

Those three packages all belongs in the spacemacs universe, general.el being only an upgrade over evil-leader. The spacemacs dev chose to use helm as a default “incremental completion and selection narrowing frameworks”. But recently, a proficient and prolific emacs package developper build an ecosystem of tools based on his variation of ido. Those packages are ivy, counsel and swiper. They represent a great alternative to the somewhat clumsy helm ecosystem.

Just like helm or ido, ivy is a “generic completion framework”. It shines at being unobtrusive and really fast. I was really surprised by the fact that I could reimplement most of my most used spacemacs commands on top of ivy or its associated package counsel. Here is what the ivy-dev has to tell about it :

ivy-mode ensures that any Emacs command using completing-read-function uses ivy for completion. Counsel takes this further, providing versions of common Emacs commands that are customised to make the best use of ivy.

Here is my (use-package) declaration for ivy and counsel:

(use-package ivy :ensure t
:diminish (ivy-mode . "") ; does not display ivy in the modeline
:init (ivy-mode 1) ; enable ivy globally at startup
:bind (:map ivy-mode-map ; bind in the ivy buffer
("C-'" . ivy-avy)) ; C-' to ivy-avy
:config
(setq ivy-use-virtual-buffers t) ; extend searching to bookmarks and …
(setq ivy-height 20) ; set height of the ivy window
(setq ivy-count-format "(%d/%d) ") ; count format, from the ivy help page
)
(use-package counsel :ensure t
:bind* ; load counsel when pressed
(("M-x" . counsel-M-x) ; M-x use counsel
("C-x C-f" . counsel-find-file) ; C-x C-f use counsel-find-file
("C-x C-r" . counsel-recentf) ; search recently edited files
("C-c f" . counsel-git) ; search for files in git repo
("C-c s" . counsel-git-grep) ; search for regexp in git repo
("C-c /" . counsel-ag) ; search for regexp in git repo using ag
("C-c l" . counsel-locate)) ; search for files or else using locate
)

swiper is an isearch replacement based on ivy. It is really really fast. I have tried it on huge files. It still is really really fast. I have the feeling it is much faster than helm-swoop, though I did not measured it. I bind it to C-s, the default keyboard press to isearch.

Conclusion

Here I showed you how I used those four packages and their ecosystem to build myself a great spacemacs-like experience. In the next post, I want to spend some time on helping you build your own experience of emacs. I will use those packages to describe you how you could build yourself a great emacs environment, using modern tools.


Originally published at sam217pa.github.io on August 30, 2016.