Emacs Lisp in 15 minutes
This gives an introduction to Emacs Lisp in 15 minutes (v0.2d)
Author: Bastien / @bzg2 / http://bzg.fr (the Org maintainer for many years)
First make sure you read this text by Peter Norvig: http://norvig.com/21-days.html
Going through this tutorial won't damage your computer unless you get so angry that you throw it on the floor. In that case, I hereby decline any responsibility. Have fun!
Write and execute lisp
Fire up Emacs.
Hit the q key to dismiss the welcome message.
Now look at the gray line at the bottom of the window:
"*scratch*" is the name of the editing space you are now in. This editing space is called a "buffer".
The scratch buffer is the default buffer when opening Emacs. You are never editing files: you are editing buffers that you can save to a file.
"Lisp interaction" refers to a set of commands available here.
Emacs has a built-in set of commands available in every buffer, and several subsets of commands available when you activate a specific mode. Here we use the `lisp-interaction-mode', which comes with commands to evaluate and navigate within Elisp code.
Semi-colons start comments anywhere on a line.
Elisp programs are made of symbolic expressions ("sexps"):
(+ 2 2) ;; this is a comment
This symbolic expression reads as "Add 2 to 2".
Sexps are enclosed into parentheses, possibly nested:
(+ 2 (+ 1 1))
A symbolic expression contains atoms or other symbolic expressions. In the above examples, 1 and 2 are atoms, (+ 2 (+ 1 1)) and (+ 1 1) are symbolic expressions.
From `lisp-interaction-mode' you can evaluate sexps. Put the cursor right after the closing parenthesis then hold down the control and hit the j keys ("C-j" for short).
(+ 3 (+ 1 2))
[ C-j ] => 6
[ C-j ] inserts the result of the evaluation in the buffer.
[ C-xC-e ] displays the same result in Emacs bottom line, called the "minibuffer". We will generally use `C-xC-e', as we don't want to clutter the buffer with useless text.
Variables
setq stores a value into a variable:
(setq my-name "Bastien")
[ C-xC-e ] => "Bastien" (displayed in the mini-buffer)
Insert text
insert will insert "Hello!" where the cursor is:
(insert "Hello!")
[ C-xC-e ] => "Hello!"
We used insert with only one argument "Hello!", but we can pass more arguments -- here we use two:
(insert "Hello" " world!")
[ C-xC-e ] => "Hello world!"
You can use variables instead of strings:
(insert "Hello, I am " my-name)
[ C-xC-e ] => "Hello, I am Bastien"
Functions
You can combine sexps into functions:
(defun hello () (insert "Hello, I am " my-name))
[ C-xC-e ] => hello
You can evaluate functions:
(hello)
[ C-xC-e ] => Hello, I am Bastien
The empty parentheses in the function's definition means that it does not accept arguments. But always using `my-name' is boring, let's tell the function to accept one argument (here the argument is called "name"):
(defun hello (name) (insert "Hello " name))
[ C-xC-e ] => hello
Now let's call the function with the string "you" as the value for its unique argument:
(hello "you")
[ C-xC-e ] => "Hello you"
Yeah!
Take a breath.
Switch buffers
Now switch to a new buffer named "*test*" in another window:
(switch-to-buffer-other-window "*test*")
[ C-xC-e ] => [screen has two windows and cursor is in the *test* buffer]
Mouse over the top window and left-click to go back. Or you can use `C-xo' (i.e. hold down control-x and hit o) to go to the other window interactively.
Expression blocks
You can combine several sexps with progn:
(progn
(switch-to-buffer-other-window "*test*")
(hello "you"))
[ C-xC-e ] => [The screen has two windows and cursor is in the *test* buffer]
Now if you don't mind, I'll stop asking you to hit `C-xC-e': do it for every sexp that follows.
Always go back to the *scratch* buffer with the mouse or `C-xo'.
It's often useful to erase the buffer:
(progn
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(hello "there"))
Or to go back to the other window:
(progn
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(hello "you")
(other-window 1))
Local variables
You can bind a value to a local variable with let:
(let ((local-name "you"))
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(hello local-name)
(other-window 1))
No need to use progn in that case, since let also combines
several sexps.
Strings handling
Let's format a string:
(format "Hello %s!\n" "visitor")
%s is a place-holder for a string, replaced by "visitor". \n is the newline character.
Let's refine our function by using format:
(defun hello (name)
(insert (format "Hello %s!\n" name)))
(hello "you")
Let's create another function which uses let:
(defun greeting (name)
(let ((your-name "Bastien"))
(insert (format "Hello %s!\n\nI am %s."
name ; the argument of the function
your-name ; the let-bound variable "Bastien"
))))
And evaluate it:
(greeting "you")
Interactive functions
Some function are interactive:
(read-from-minibuffer "Enter your name: ")
Evaluating this function returns what you entered at the prompt.
Let's make our greeting function prompt for your name:
(defun greeting (from-name)
(let ((your-name (read-from-minibuffer "Enter your name: ")))
(insert (format "Hello!\n\nI am %s and you are %s."
from-name ; the argument of the function
your-name ; the let-bound var, entered at prompt
))))
(greeting "Bastien")
Let's complete it by displaying the results in the other window:
(defun greeting (from-name)
(let ((your-name (read-from-minibuffer "Enter your name: ")))
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(insert (format "Hello %s!\n\nI am %s." your-name from-name))
(other-window 1)))
Now test it: (greeting "Bastien")
Take a breath.
Lists
Let's store a list of names:
(setq list-of-names '("Sarah" "Chloe" "Mathilde"))
Get the first element of this list with car:
(car list-of-names)
Get a list of all but the first element with cdr:
(cdr list-of-names)
Add an element to the beginning of a list with push:
(push "Stephanie" list-of-names)
NOTE: car and cdr don't modify the list, but push does. This is an important difference: some functions don't have any side-effects (like car) while others have (like push).
Let's call hello for each element in `list-of-names':
(mapcar 'hello list-of-names)
Refine greeting to say hello to everyone in `list-of-names':
(defun greeting ()
(switch-to-buffer-other-window "*test*")
(erase-buffer)
(mapcar 'hello list-of-names)
(other-window 1))
(greeting)
Remember the hello function we defined above? It takes one argument, a name. mapcar calls hello, successively using each element of `list-of-names' as the argument for hello.
Now let's arrange a bit what we have in the displayed buffer:
(defun replace-hello-by-bonjour ()
(switch-to-buffer-other-window "*test*")
(goto-char (point-min))
(while (search-forward "Hello")
(replace-match "Bonjour"))
(other-window 1))
Moving around
(goto-char (point-min)) goes to the beginning of the buffer. (search-forward "Hello") searches for the string "Hello". (while x y) evaluates the y sexp(s) while x returns something. If x returns nil (nothing), we exit the while loop.
(replace-hello-by-bonjour)
You should see all occurrences of "Hello" in the *test* buffer replaced by "Bonjour".
You should also get an error: "Search failed: Hello".
To avoid this error, you need to tell `search-forward' whether it should stop searching at some point in the buffer, and whether it should silently fail when nothing is found:
(search-forward "Hello" nil t) does the trick:
The nil argument says: the search is not bound to a position. The t argument says: silently fail when nothing is found.
We use this sexp in the function below, which doesn't throw an error:
(defun hello-to-bonjour ()
(switch-to-buffer-other-window "*test*")
(erase-buffer)
;; Say hello to names in `list-of-names'
(mapcar 'hello list-of-names)
(goto-char (point-min))
;; Replace "Hello" by "Bonjour"
(while (search-forward "Hello" nil t)
(replace-match "Bonjour"))
(other-window 1))
(hello-to-bonjour)
Change fonts
Let's colorize the names:
(defun boldify-names ()
(switch-to-buffer-other-window "*test*")
(goto-char (point-min))
(while (re-search-forward "Bonjour \\(.+\\)!" nil t)
(add-text-properties (match-beginning 1)
(match-end 1)
(list 'face 'bold)))
(other-window 1))
Searching regexps
This functions introduces `re-search-forward': instead of searching for the string "Bonjour", you search for a pattern, using a "regular expression" (abbreviated in the prefix "re-").
The regular expression is "Bonjour \\(.+\\)!" and it reads: the string "Bonjour ", and a group of (this is the \\( ... \\) construct) any character (this is the .) possibly repeated (this is the +) and the "!" string.
Ready? Test it!
(boldify-names)
`add-text-properties' adds... text properties, like a face.
OK, we are done. Happy hacking!
How to get documentation
If you want to know more about a variable or a function:
C-h v a-variable RET C-h f a-function RET
To read the Emacs Lisp manual with Emacs:
C-h i m elisp RET
To read an online introduction to Emacs Lisp: https://www.gnu.org/software/emacs/manual/html_node/eintr/index.html
Thanks to these people for their feedback and suggestions:
- Wes Hardaker
- notbob
- Kevin Montuori
- Arne Babenhauserheide
- Alan Schmitt
- LinXitoW
- Aaron Meurer