User:Riviera/Emacs is a joy
Historical information about (Emacs) Lisp
Emacs Lisp is a variant of the Lisp programming language. Robert Chassel writes that ‘Lisp stands for LISt Processing, and the programming language handles lists (and lists of lists) by putting them between parentheses’ (Chassel, n.d., ch.1). Emacs is a self-documenting text editor. This wiki post, which was written with Emacs, discusses how to write a function in Emacs Lisp. The function I would like to create will write metadata blocks in markdown documents. These will resemble the following:
--- title: Some Document author: Me date: Saturday, February 10, 2024 ---
Whilst writing this macro I will simultaneously wander around Emacs as if it were a city. I will explain why by way of an analogy. When I wander around London, I often find it is possible to encounter places I have never been before. I like this about the city, there are always new places to find. Emacs is similar to London in this regard. There are always new features to find out about. Emacs is also congruent with my experience of cities insofar that there is a lot one can do in a city. Within Emacs I can send emails, browse the web, write files and play games. I can also configure it however I would like to by writing arbitrary code in Emacs Lisp. In other words, I can adjust the behaviour and capability of the software. This is possible because Emacs is free software.
Breaking down the metadata block
The metadata block is distinct because it begins and ends with three hyphens. Inside these hyphens there are three keywords. Each of these is followed by a colon, a space, and then some text. We can insert three hyphens into a buffer using the insert
macro.
(insert "---")
Emacs has the ability to evaluate Emacs lisp code during its execution. Emacs can be instructed to evaluate code using the key combination C-x C-e
. Firstly, place point (the cursor) at the end of the parenthetical expression. Then type C-x C-e
to insert the three hyphens into the current buffer. It’s going to be necessary to insert this twice, separated by several newlines, so our code can be refactored as:
(insert "---")
(newline)
(newline)
(insert "---")
Let
Let’s wrap a let expression around the insert and newline macros:
(let ()
(insert "---")
(newline)
(newline)
(insert "---"))
The let expression is composed of three parts. The keyword let
, the varlist and the body. Let permits people writing lisp code to declare local variables and then utilise those variables in the body of an expression. There are some strings in the metadata block such as “title:” and “author:” which do not change. However, other strings such as the title are likely to change from one document to the next. The string which makes up the title should therefore be a variable. The following code expands on the code written above:
(let ((title (read-string "Title: ")))
(insert "---")
(newline)
(insert "title: " title)
(newline)
(insert "---"))
On the one hand, the list of variables has changed. The variable title
has been created. Because read-string
is used, the value of title
will depend upon input given by the user. The argument to read string "Title: "
is the prompt which Emacs gives to the user when asking for input. In the body of the let expression one line has been added. It inserts the word “title” followed by a colon, then a space, then the title string provided by the user.
Next it will be necessary to do something similar for the author variable. This time, however, a default value will be provided to the author variable. This promotes efficiency as the user can leave this field blank in which case the default value will be used instead.
(let ((title (read-string "Title: "))
(author (read-string "Author: " nil nil "Riviera Taylor")))
(insert "---")
(newline)
(insert "title: " title)
(newline)
(insert "author: " author)
(newline)
(insert "---"))
The read-string macro expects a prompt as the first argument. It can then be passed several optional arguments in the order of initial-input, history, default-value and inherit-input-method. In this case the value of nil
is passed to the first two optional arguments. Meanwhile, I passed my name as the value of default-value.
Functions
To define a function, let’s wrap a defun
around the let expression.
(defun markdown-insert-metadata-block ()
"Insert a metadata block in markdown documents."
(let ((title (read-string "Title: "))
(author (read-string "Author: " nil nil "Riviera Taylor")))
(insert "---")
(newline)
(insert "title: " title)
(newline)
(insert "author: " author)
(newline)
;; (insert "date: ")
;; (insert-current-date))
;; (newline)
(insert "---")))
Following the defun
keyword there is the name of the function to be defined. The empty list following it is where arguments would go if the function expected any. This function takes no arguments, so an empty list is okay. On the following line, there is a documentation string stating what the function does. Next there is the body of the function being defined.
The lines beginning with semicolons are comments. I have commented out these lines because insert-current-date is a function which I am borrowing from the Emacs wiki. I will come back to this after discussing interactive
.
Interactive, a segue
I probably should have declared that the function above is interactive. interactive
ensures that the macros will be more readily available to the user. The macro becomes callable via the M-x
menu. It can also be attached to a key combination and called that way. I give an example of how to declare an interactive function in my discussion of the insert-current-date
command.
M-x help
Emacs uses short-hand notation to describe particular keys on the keyboard. For example, C-h
means press the control key and the h key at the same time. M-h
on the other hand means press the meta (alt) key and the h key at the same time. These keybindings are bound to different commands and thus have different consequences when pressed. To some extent, the first command highlights Emacs’ help menu and the second command highlights text.
On closer inspection I found that M-h
is bound to the mode-specific command markdown-mark-paragraph
. Likewise, C-h
is a prefix keybinding which means that nothing is bound to C-h
itself. C-h C-h
is an extended keybinding, as is C-h ?
. Both are bound to the interactive command help-for-help
. It’s also possible to type M-x help-for-help
followed by the return key to call the command. In other words, M-x
is a way of calling commands by name; particularly commands without keybindings attached to them. These commands are interactive commands.
Example: Insert Current Date
;; borrowed from the emacs wiki
;; https://www.emacswiki.org/emacs/InsertingTodaysDate
(require 'calendar)
(defun insert-current-date (&optional omit-day-of-week-p)
"Insert today's date using the current locale.
With a prefix argument, the date is inserted without the day of
the week."
(interactive "P*")
(insert (calendar-date-string (calendar-current-date) nil
omit-day-of-week-p)))
To further illustrate the differences between interactive and non-interactive commands, take the example of require
. It is a non-interactive function. require tells Emacs to load some Emacs lisp code provided elsewhere. Here the code for Emacs’ built in calendar is required. It would be possible to write an interactive version of the require command, however that is not the focus of this discussion. It is necessary for Emacs to load the calendar code to understand calendar-specific commands in the body of the function being defined.
Simplification
Themarkdown-insert-metadata-block
function can be simplified to the following.
(require 'calendar)
(defun markdown-insert-metadata-block ()
"Insert a metadata block in markdown documents."
(interactive)
(let ((title (read-string "Title: "))
(author (read-string "Author: " nil nil "Riviera Taylor")))
(insert "---")
(newline)
(insert "title: " title)
(newline)
(insert "author: " author)
(newline)
(insert "date: " (calendar-date-string (calendar-current-date)))
(newline)
(insert "---")))
This does away with the need to define a new command and call it from within another command. The most critical part of the insert-current-date command has been implemented in the markdown-insert-metadata-block command. Furthermore require is called upon to load the calendar commands.
dotemacs
The ~/.emacs
file is the file which Emacs reads on start-up. I’d like Emacs to load a specific file on start-up. It’s the code above which I’m going to save to ~/Code/elisp/markdown/insert-metadata-block.el
. C-x d <RET>
is the quickest way to open the current directory within Emacs. Directories can be viewed and acted upon using the Dired programme built into Emacs. Inside a Dired buffer the +
key can be pressed to create a new directory. Alternatively, M-x mkdir
is a practical way of creating directories inside Emacs. Lastly, C-x C-f
can be used to find files. It’s possible to find a file which doesn’t exist yet and then save the file to a given location. After saving the contents of the code above, it’s necessary to tell Emacs about the code.
This is where it’s necessary to edit the ~/.emacs
file. Appending the following lines are sufficient for telling Emacs about the additional code.
(add-hook 'markdown-mode-hook (load "~/Code/elisp/markdown/insert-metadata-block.el"))
It’s time to test this by opening a markdown file in Emacs and attempting to execute markdown-insert-metadata-block
. It works, the command is only available after Emacs loads markdown-mode.
Bibliography
Chassel, Robert. An Introduction to Programming in Emacs Lisp. https://www.gnu.org/software/emacs/manual/html_node/eintr/index.html Accessed 2024-02-10.