In the first post we looked at some basic usage and navigation, and set up use-package
so we can easily add community packages to our Emacs installation.
Breathing Room
I think I go a little overboard with this, but every time one of my use-package
declarations goes over a single line, I like to pull it out to its own file. That way I just have one line to comment/uncomment in init.el
to activate/deactivate a package. To set this up, create a directory inside .emacs.d
- I just called mine .emacs.d/lisp
. We can ensure it gets evaluated by adding the following to init.el
:
;; Pull in ./lisp/*
(add-to-list 'load-path (expand-file-name "lisp" user-emacs-directory))
Now any whatever.el
elisp file we put in this directory will be visible to init.el
.
The Emacs ecosystem is big, and there are multiple solutions and sets of solutions for any given problem. I like to keep mine pretty minimal, this is just the set that works for me - I do urge you to explore! The packages used in this set, notably, are not the same set that Spacemacs is based around. When you do your own research, it sorta-kinda comes down to helm
& friends vs. ivy/swiper/counsel
- this is the ivy
route. I intentionally wanted to try something different from what I had gotten to know via Spacemacs, but it shouldn't be taken as a value judgement at all. I've enjoyed using both greatly.
These are completion engines. Remember the last post, when we forgot C-x C-s
but then still miraculously knew it was save-buffer
? With ivy
, you'd be able to just hit M-x
and then frantically start typing save
and ivy
will find everything it possibly could be. It will even helpfully show you the assigned key combination for a given command if there is one. Pretty damn handy with a tool as vast as Emacs! It's a personal always-on concierge.
Ivy/Counsel/Swiper
That's as good a place to start as any. Ivy is the main event here, and counsel
and swiper
are ivy
-imbued versions of common commands and file search, respectively. Create a file called init-ivy.el
:
;;; #init-ivy.el
;;; Commentary:
;;; http://oremacs.com/swiper/#installation
;;; https://sam217pa.github.io/2016/08/30/how-to-make-your-own-spacemacs/#fnref:3
;;; https://writequit.org/denver-emacs/presentations/2017-04-11-ivy.html#fnr.2
;;; Code:
(use-package ivy
:diminish (ivy-mode . "")
:init (ivy-mode 1) ; globally at startup
:config
(setq ivy-use-virtual-buffers t)
(setq ivy-height 20)
(setq ivy-count-format "%d/%d "))
(provide 'init-ivy)
;;; init-ivy.el ends here.
In this same file, I also set up counsel
. This package overrides some built-in Emacs commands with more user friendly versions. Add this above the final comment:
;; Override the basic Emacs commands
(use-package counsel
:bind* ; load when pressed
(("M-x" . counsel-M-x)
("C-s" . swiper)
("C-x C-f" . counsel-find-file)
("C-x C-r" . counsel-recentf) ; search for recently edited
("C-c g" . counsel-git) ; search for files in git repo
("C-c j" . counsel-git-grep) ; search for regexp in git repo
("C-c /" . counsel-ag) ; Use ag for regexp
("C-x l" . counsel-locate)
("C-x C-f" . counsel-find-file)
("<f1> f" . counsel-describe-function)
("<f1> v" . counsel-describe-variable)
("<f1> l" . counsel-find-library)
("<f2> i" . counsel-info-lookup-symbol)
("<f2> u" . counsel-unicode-char)
("C-c C-r" . ivy-resume))) ; Resume last Ivy-based completion
Don't worry too too much about memorizing everything here right off the bat - it will be here when you need it. For a while I had an index card with a few of the most handy ones sitting on my desk. In the last post we covered the "save" action, which was a whole keypress more than you're probably used to - this is because C-s
is reserved for searching for text in the given file. Check out the video demo.
Interlude: Wait, There Totally Are Modes
Well, yes, but they're not Vim modes! In Emacs, a mode
determines how Emacs semantically understands the text in the current buffer. These fall into two categories, major
and minor
- each buffer has one major mode, and can have multiple minor modes. A major mode might be something like clojure-mode
- this text is only Clojure code, not some other type of code as well, but could have ivy-mode
and spellcheck-mode
enabled as well, because that functionality can stack.
Alright, now that init-ivy.el
has been added to lisp/
, we can add it to init.el
:
(require 'init-ivy)
That's it! Evaluating that require
expression with C-x C-e
will read our new file and set up Ivy for us.
Flycheck
Another package I love is flycheck, which provides on the fly syntax checking. It has indicators for problematic lines, squiggly underlines, and pop-up tooltips - all the trappings of a modern syntax checker. This declaration is simpler:
;;; lisp/init-flycheck.el
(use-package flycheck
:init (global-flycheck-mode))
(provide 'init-flycheck)
And in init.el
:
(require 'init-flycheck)
Some languages will require special setup, but most things will just work out of the box.
Company
A perfect complement to flycheck-mode
is company-mode
, which provides text-completion. As you type, it will make suggestions. You can scroll through them with M-n
and M-p
, and use the enter key to select. There are more ways to interact with it as well - peep the docs for deets.
In lisp/init-company.el
:
(use-package company
:config
(add-hook 'after-init-hook 'global-company-mode))
(provide 'init-company)
And of course (require 'init-company)
in init.el
. Now we're starting to feel like a real IDE!
which-key
This is probably my favorite of the bunch. Ivy is giving us some nice completions, but you still need to know where to start - it's not great for discovering what's available. Which-key will pop up a window when you begin a command listing everything available. In our save-buffer
example, when you type the first C-x
, you'll get a big pane detailing every combination available after C-x
, with the combo and the command name. This is how I find new combos to learn, and it's great for jogging your memory.
My init-which-key.el
:
(use-package which-key
:init
(which-key-mode)
: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)
:diminish which-key-mode)
(provide 'init-which-key)
Tweak these to your liking, these settings work for me. Of course, don't forget (require 'init-which-key)
in init.el
!
Smartparens
This minor mode helps manage your parentheses. It has a number of facilities for manipulating parenthetical expressions - a huge help no matter what programming language you use.
lisp/init-smartparens.el
:
(use-package smartparens
:config
(require 'smartparens-config)
(add-hook 'lisp-mode-hook #'smartparens-strict-mode))
(provide 'init-smartparens)
I've added a hook that activates an even stricter version when I'm in a specific minor mode - this is also something you'll need to tweak for yourself! I actually also use smartparents-strict-mode
in rust-mode
- we'll get to the langauge-specific stuff later.
By now you know the drill for getting it into init.el
!
Neotree
This is my last general package. Neotree is a habit I picked up from Vim - it shows a graphical overview of the directory tree that you can use to switch between files. Another nicety that IDEs feel like they should have - though for the most part I find myself invoking C-x C-f
or C-x b
to navigate around in a project.
lisp/init-neotree.el
:
(use-package neotree
:init
(require 'neotree)
:config
(setq neo-theme (if (display-graphic-p) 'icons 'arrow))
(setq neo-smart-open t)
)
(provide 'init-neotree)
I lied - that was the second to last. I also use find-file-in-project
.
(use-package find-file-in-project)
Keybindings
The next order of business is setting up your own keybindings. We can use global-set-key
for this. The first one I set is the key to activate neotree
- add this to your init.el
:
(global-set-key [f8] 'neotree-project-dir)
To enable this behavior, I have the following snippet stolen from the emacs wiki placed in lisp/bl-fns.el
to facilitate NeoTree attempting to use the git project root when it opens:
(defun neotree-project-dir ()
"Open NeoTree using the git root."
(interactive)
(let ((project-dir (ffip-project-root))
(file-name (buffer-file-name)))
(if project-dir
(progn
(neotree-dir project-dir)
(neotree-find file-name))
(message "Could not find git project root."))))
(provide 'bl-fns)
Pretty easy, right? Now the F8 key will toggle the NeoTree window. Cool. Another keybinding I add for myself that I find useful is this:
(global-set-key (kbd "C-c q") (lambda ()
(interactive)
(other-window -1)))
The kbd
macro lets you define combos using the handy shorthand. This combo, C-c q
, will switch back to the previous active window. I generally only have two or three open and find myself using this one a lot.
I also like this shorthand for company-complete
:
(global-set-key (kbd "C-c h") 'company-complete)
Language-specific packages
Clojure
For clojure, I use CIDER:
;; init-clojure.el
(use-package clojure-mode)
(use-package cider)
(provide 'init-clojure)
CIDER is a whole can of worms in and of itself - I'll come back to that in a separate post sometime!
Rust
Rust has a little more going on to set it up with flycheck and cargo and everything:
;; init-rust.el
(use-package rust-mode)
(use-package flymake-rust)
(use-package racer)
(use-package company)
(use-package cargo
:config
(add-hook 'rust-mode-hook 'cargo-minor-mode))
(use-package flycheck-rust)
(with-eval-after-load 'rust-mode
(add-hook 'flycheck-mode-hook #'flycheck-rust-setup))
(provide 'init-rust)
To be completely honest, Rust was my biggest driver in migrating toward VSCode - Rust in Emacs is fantastic, Rust in VSCode is unparalleled. The above works great, but I just can't in good faith recommend this setup over using the RLS from VSCode.
Some More
Forth, JavaScript/HTML/CSS, and Reason/OCaml I use with zero config:
(use-package forth-mode)
(use-package js2-mode)
(use-package reason-mode)
(use-package web-mode)
(use-package ocp-indent)
And....that's all I got for ya! This set of packages provides a complete multi-language IDE without much bloat.
To update your installed packages, run M-x list-packages
- this will refresh the latest package list. Then just type U
(shift-u) to upgrade any that are outdated.
Top comments (7)
Hey Ben! I've just found this!!! I'm seriuosly considering making my own version
This is really cool, I had no idea! Thanks for the link, there goes my afternoon.
Great article! This helps a lot.
One thing though...
Apparently you need to add ":ensure t" to make use-package install the library.
As in:
(use-package counsel
:ensure t
Good catch! You're absolutely correct. I omitted it here because folks that followed through the first post too have added the line
(setq use-package-always-ensure t)
toinit.el
, which sets:ensure t
by default on every declaration - I prefer that behavior to setting it one at a time.In retrospect, it probably should have just been one big post.
Oops. I missed that line. Sorry. Thanks again.
(provide 'init-ivy) at the end of the ivy example code?
Oops, yep, thank you! Fixed.