2016-01-26 Emacs lisp, projectile, and eshell
Table of Contents
Elisp
Emacs lisp is the language that the Emacs VM is written in. You can think of Emacs as simply a lisp interpreter that happens to have an editor implemented on top of it. Emacs lisp has great support for dealing with things like strings, buffers, and files (as you would expect from an editor). Emacs lisp is also a LISP-21.
To follow along, open an elisp repl with M-x ielm
!
Helpful packages and modes
eldoc-mode
- edebug
describe-function
(bound toC-h f
)describe-variable
(bound toC-h v
)M-x eval-expression
(bound toM-:
usually)- elisp-slime-nav (enables
M-.
in elisp buffers)
Basic Introduction to Lisp and Emacs Lisp
If you haven't worked with lisp before, it uses a lot of parentheses ()
. Lisp
is known for a number of traits:
- Lists
- S-expressions
- Lambda / functional underpinnings
When I say "lists", what I mean is that expressions look like:
'(1 2 3) '("a" (1 2) 'thing)
In the land of lisp, the operator for a method is the first element of the list
(+ 1 2) ;; => 3 ;; ' and (list ...) are the same things '("a" "b" "c") ;; => ("a" "b" "c") (list "a" "b" "c") ;; => ("a" "b" "c") (quote ("a" "b" "c")) (append '(1 2) '(4 5)) ;; => (1 2 4 5)
Variables can be defined using defvar
and set by using setq
(set quoted)
(defvar my-fancy-var '(1 2) "This is my fancy variable") (setq my-fancy-var (cons 0 my-fancy-var)) my-fancy-var ;; => (0 1 2)
Functions can be defined using defun
(defun add-one (x) "Adds 1 to a number provided as `x'." (projectile-find-file ) (+ 1 x)) (defun jump-to-thingy (x)) (add-one 3) ;; => 4
Or with multiple / optional arguments
(defun print-things (&rest things) (message "args: %s" things) (c-mode) (mapcar (lambda (thing) (message "thing: %s" thing)) things)) (print-things "a" 1 '('b "hi"))
Notice the lambda
there, Emacs supports anonymous functions this way.
Local variables
You can define local variables using the let
form, though I vastly prefer the
let*
form because it behaves closer to Clojure's 'let' form, allowing you to
reference variables defined in the same form.
(defun make-ngrams (string ngram-size) (let* ((string-length (string-bytes string)) ;; length of the string (ngram-size (min ngram-size string-length)) (i 0) ;; a counter (new-chunks '())) ;; empty list we'll use as state ;; while we haven't reached the end of the string yet (while (<= (+ i ngram-size) string-length) (let ((s (substring string i (+ i ngram-size)))) (push s new-chunks) ;; add the string to the list (setq i (+ i 1)))) ;; increment i new-chunks)) (make-ngrams "supercalafragilisticexpeallodocious" 1) (make-ngrams "supercalafragilisticexpeallodocious" 2) (make-ngrams "supercalafragilisticexpeallodocious" 3) (make-ngrams "supercalafragilisticexpeallodocious" 120)
What do you think would have happened if I used let
instead of let*
?
(interactive)
, prompting magic
Our add-one
function is great and all, but what about making a function that
shows up in M-x
? To do that, a function needs to be marked as interactive
.
;; Doesn't show up with M-x! (defun say-hello () (message "Hello %s" (user-full-name))) (defun say-hello2 () (interactive) (message "Hello %s" (user-full-name)))
Interactive is for more than this though, it can also be used for prompting input as placeholders for function arguments
(defun say-hello-to-me (username favorite-number) (interactive "sWhat's your name? nWhat's your favorite number? ") (message "Why, hello there %s! Your favorite number times 7 is %d" username (* 7 favorite-number))) ;; You can still invoke it as a regular function (say-hello-to-me "Sally" 42)
There are a bunch of other interactive
parameters too, check out the help for
it (C-h f interactive
) or the Emacs manual entry for it.
Interacting with buffers
Emacs lisp has many, many, function to deal with text, let's make a function that will number lines!
(defun my-number-lines (&optional offset) (interactive) (goto-char (point-min)) (let ((i (or offset 1))) (beginning-of-line) (insert-string "0. ") (while (not (= (forward-line 1) 1)) (beginning-of-line) (insert-string (format "%d. " i)) (setq i (+ i 1)))))
But wait! you notice at the end of running that, the cursor has jumped around, what if we don't want that?
(defun my-number-lines (&optional offset) (interactive) (save-excursion (goto-char (point-min)) (let ((i (+ 1 (or offset 0)))) (beginning-of-line) (insert-string (format "%d. " (- i 1))) (while (not (= (forward-line 1) 1)) (beginning-of-line) (insert-string (format "%d. " i)) (setq i (+ i 1))))))
Then it can be bound to a key:
(global-set-key (kbd "C-x N") #'my-number-lines)
Helpful links for Elisp programming
- An Introduction to Programming in Emacs Lisp
- A comparison between CL, Scheme, Clojure and Elisp
- Good libraries for programming with
- Using the seq.el and map.el libraries
- A small-ish introduction to Elisp
- [video] Emacs development tips with John Wiegley
- [video] Creating a spotify client in 15 minutes
Projectile
Projectile is a project interactive library for Emacs. This means you can do all sorts of neat things like going to a file in a project, searching the whole project, jump between a test and implementation, switch between project buffers, etc.
To install it, make sure you have MELPA set up and then do:
(package-install 'projectile)
Then you can enable it in your config globally with:
(projectile-global-mode)
All of projectile's bindings start with the projectile prefix, which defaults to
C-c p
.
Here are some of the most important keybindings:
Keybinding | Action |
---|---|
C-c p f |
Find a file in the project |
C-c p p |
Switch project |
C-c p g |
Run grep on all files in the project |
C-c p b |
Switch between buffers for the project |
C-c p r |
Run query-replace for all files in the project (for refactoring) |
C-c p k |
Kill all open buffers for the project |
If there are files you don't want to show up in projectile, you can create a
.projectile
file and prefix things to remove with -
2.
There is a lot more to projectile, but these are the basics!
Demo
Eshell
Eshell is a shell written entirely in Emacs, why on earth would you want that? Because it's awesome!
It allows you to evaluate list directly in the shell, which can be really nice.
Invoke it with M-x eshell
. If you invoke it a second time it will jump back to
the same buffer. To create an additional shell, use the prefix argument C-u M-x
eshell
.
You can redirect the output of processes directly to buffers by using a special syntax:
cat log.txt | grep "foo" >> #<buffer *scratch*>
Additionally, if you set eshell-buffer-shorthand
to t
you can use a
shorthand for the buffers:
cat log.txt | grep "bar" >> #'*scratch*
Demo
Additional Eshell Resources
- Complete Guide to Mastering Eshell
- eshell-prompt-extras package (comes with some nice prompts)