内容简介:This post is part two of a series of 3 posts. View the other parts:This is the first post in what I hope is an ongoing series of posts that show you how to get started with Emacs Lisp by writing practical Emacs commands and customizing Emacs in other ways.
This post is part two of a series of 3 posts. View the other parts:
This is the first post in what I hope is an ongoing series of posts that show you how to get started with Emacs Lisp by writing practical Emacs commands and customizing Emacs in other ways. For me, one of the best things about Emacs is that you can customize it to work however you want, but learning how to do so is easier said than done, and while the official Emacs Lisp documentation is extremely detailed, it can be hard to approach.
I’m hoping I can demonstrate that Emacs Lisp is fun to write and not as hard as
you might think. I’m going to assume experience using Emacs. For example, you
should have a basic understanding of things like windows
and buffers
, and know
how to read and type keyboard shortcuts written in the Emacs style ( C-x C-s
the world).
I will also assume experience programming in other languages, but not
necessarily Lisp ones; however, you should still have a very basic
understanding of how Lisp languages work, for example what the result of (+ 1 2)
is. If you don’t, the GNU Intro to Emacs Lisp
is a good starting point.
In this series of posts I’m going to show you step-by-step how to set write a command to render a preview of a Markdown file and open it directly in Emacs. Then I’ll show you how to have it run automatically whenever you save a Markdown file.
For this tutorial you’ll want to install
pandoc
which we will use for rendering Markdown files, Emacs 25 or newer, and the
markdown-mode
package.
Evaluating forms and ielm
Before we get started, I want to briefly mention how to follow along with the
code in this post. For the functions we’re working on, you’ll probably want to
add them to your ~/.emacs.d/init.el
file. You can evaluate function
definitions by moving the cursor to some point in the function definition and
then doing C-M-x
. You can also evaluate the form immediately preceding your
cursor with the key binding C-x C-e
. Give them a try!
A big part of Lisp development is using a REPL. ielm
is the Emacs Lisp REPL.
You can start it with M-x ielm
and use it to evaluate Emacs Lisp forms. This
is especially useful for evaluating the smaller bits of code like (+ 1 2)
that
eventually make it in to larger functions.
Writing a preview-markdown
command
Without further ado, let’s start writing our command.
Defining a function
Let’s start off with an empty function declaration:
(defun cam/preview-markdown ())
If you’re completely new to Emacs Lisp, especially if you come from a Clojure
background, you might think the cam/
portion of the name of the function has
some sort of special significance. It doesn’t! Emacs Lisp doesn’t have the
notion of separate namespaces that other languages have, so every function and
variable name is global; like Objective-C, it’s a best-practice to prefix all
function and variable names with something that makes it clear they’re part of a
particular package. Usually you’ll see names prefixed with <package-name>-
,
for example helm-mark-ring
, but <package-name>/
and <package-name>:
are
reasonably common conventions as well. The /
character itself has no special
significance in symbol names. I prefer prefixing all of my own functions and
macros with cam/
, so I’ll use that in the code in this post.
Making it a command
Now this function itself doesn’t do anything, and you can’t invoke it with M-x
. Emacs Lisp functions can either be invoked programmatically (e.g. by
other Emacs Lisp functions, or the ielm
REPL, or via M-:
) or interactively
(via M-x
or a keybinding). A function that be invoked interactively is known
as a command
.
To make a function a command, you add an interactive
declaration, which always
goes right after the argument list (or docstring, if you have one):
(defun cam/preview-markdown () (interactive))
Now the function can be invoked interactively. Try it out! M-x cam/preview-markdown
. Cool! But it still doesn’t do
anything.
Logging Messages
Let’s start by having our command log a message. message
is the Emacs Lisp
equivalent of printf
, so this is sort of a “Hello World” example. Exciting!
We’ll add a call message
in the function body:
(defun cam/preview-markdown () (interactive) (message "Rendering Markdown preview of %s" buffer-file-name))
The variable buffer-file-name
is bound to the string name of the file associated with
the current buffer (i.e., the one from which you invoked the command). Create a
markdown file and switch to it in Emacs, then run M-x cam/preview-markdown
again.
You’ll see something like
Rendering Markdown preview of /home/cam/README.markdown
appear in the minibuffer, and it will be appended to the *Messages*
buffer as
well. When writing any code, it’s important to be able to debug it, and until we
talk about using the Emacs Lisp debugger being able to print stuff out is a good first step.
Calling external commands
Our plan is to:
*Preview Markdown Output* *Preview Markdown Output* *Preview Markdown Output*
So let’s add a command to render the Markdown to HTML using the external pandoc
command:
(defun cam/preview-markdown () (interactive) (message "Rendering Markdown preview of %s" buffer-file-name) (shell-command-on-region (point-min) (point-max) "pandoc -f gfm" "*Preview Markdown Output*"))
shell-command-on-region
runs a command ( pandoc -f gfm
) on a region
. The
simplest explanation of a region is that it’s all the text between two positions
in a buffer – if you think of the current buffer as one giant string, then a
region would be a substring.
Since we want to render the entire buffer as HTML rather than just part of it, we want
our region to be the entire buffer. The functions point-min
and point-max
get the minimum possible position and maximum possible position in the current
buffer respectively; together those positions represent the region we’re operating on.
Finally, we pass the string "*Preview Markdown Output*"
to shell-command-on-region
which tells it the name to use for the buffer it
writes the output of the shell command to. Emacs Lisp buffers used for
internally or for other special purposes are conventionally given names with
asterisks (affectionately known as earmuffs
), but you can actually name the
buffer anything you want. shell-command-on-region
will clear any existing
contents of the buffer before writing the command output.
Go ahead and give it a try in your Markdown file buffer, and you’ll see a buffer
called *Preview Markdown Output*
pop up with the HTML output.
Before moving on, let’s take a quick break to discuss some essentials that you’ll really want to know when writing Emacs Lisp.
Looking up documentation
To look up documentation for a function, you can use C-h f <name-of-function>
.
If you want write Emacs Lisp, commit that keybinding to
memory right away – you’ll be using it all the time. Try using it to learn more
about one of functions we’re using.
Similarly, C-h v
opens documentation for a variable, and C-h k
will tell you
the function that gets run for given keybinding. Write these down on a Post-It
and stick it to your computer until they are burned into your brain.
Evaluating Emacs Lisp from the minibuffer with M-:
Another keybinding you’ll want to commit to memory is M-:
, which allows you to
evaluate an Emacs Lisp form from the minibuffer. The code is evaluated in the
context of the current buffer. If you want to look at certain
values of variables or test how certain code works when evaluated in a certain
buffer, M-:
is one of the easiest ways to do so.
Try it from you markdown buffer – M-:
and then type or paste buffer-file-name
and hit return. The output is visible in the minibuffer and *Messages*
. Try it with a different buffer – you’ll see a different result.
Viewing the HTML
We can use the built-in Simple HTML Renderer (SHR) to render HTML directly in Emacs. Change our function to the following and give this a try:
(defun cam/preview-markdown () (interactive) ;; [1] bind the value of buffer-file-name to local variable filename (let ((filename buffer-file-name)) (message "Rendering Markdown preview of %s" filename) (shell-command-on-region (point-min) (point-max) "pandoc -f gfm" "*Preview Markdown Output*") ;; [2] switch to a different window and bring up the *Preview Markdown Output* buffer (switch-to-buffer-other-window "*Preview Markdown Output*") ;; [3] Parse the HTML source in *Preview Markdown Output*, and store it in document (let ((document (libxml-parse-html-region (point-min) (point-max)))) ;; [4] Clear the contents of *Preview Markdown Output* (erase-buffer) ;; [5] Render the parsed HTML into *Preview Markdown Output* (shr-insert-document document) ;; [6] Move back to the beginning of the buffer (goto-char (point-min)))))
We’re doing a few new things here, and I’ll explain each in turn:
Binding local variables with let
(let ((filename buffer-file-name)) ...)
Not all buffers are associated with files. *Preview Markdown Output*
isn’t
actually associated with a file, so buffer-file-name
will be nil
if we
evaluate it with *Preview Markdown Output*
as the current buffer. To work
around this, we can evaluate buffer-file-name
before
we switch buffers and
save it for later as the local variable local variable filename
. (let ...)
is used to introduce local bindings.
(let ((x 100)) (+ x x)) ;; -> 200 (let ((x 100) (y 200)) (+ x y)) ;; -> 300
Switching to a different window
(switch-to-buffer-other-window "*Preview Markdown Output*")
It would be annoying to replace the window with your Markdown file with *Preview Markdown Output*
, so it’s a nicer experience to show it in a window
you’re not currently using. If *Preview Markdown Output*
is already visible in
a different window, it will switch to that window; otherwise it will pick
another window (creating one if needed) and then switch to the *Preview
Markdown Output*
buffer in that window.
There are a lot of ways to create and split windows in Emacs. You might decide
that you’d rather have *Preview Markdown Output*
open a new window rather than
replace the contents of an existing one; you can use functions like split-window-sensibly
to split the current window instead. A full discussion
of other options is outside the scope of this article, but hopefully I can
include more examples in the future.
Parsing the HTML source
(libxml-parse-html-region (point-min) (point-max))
Before we can render the HTML, we have to parse it. libxml-parse-html-region
parses HTML in the region. Again, we’re using point-min
and point-max
to
tell it to parse the entire buffer; we’re saving the output of that function as document
.
Clearing the contents of *Preview Markdown Output*
(erase-buffer)
We’re going to put the rendered HTML back into *Preview Markdown Output*
, so
we need to clear out the HTML source first. (erase-buffer)
clears the contents
of the current buffer.
Render the parsed HTML document with SHR
(shr-insert-document document)
Renders the parsed HTML document in the current buffer.
Moving back to the beginning of the buffer
(goto-char (point-min))
At this point, the cursor will be all the way at the end of the buffer. It’s
nicer behavior if our preview shows us the beginning of the file rather than the
end. goto-char
moves the cursor, and we’re moving it to the beginning of the
buffer, again using point-min
to determine the beginning.
Next Steps
InPart 2, we’ll make improvements to the command, and discuss Emacs Lisp lists, cons cells, backquote-splicing, and macros.
InPart 3, we’ll have
the command prompt us for a file to preview when running interactively, and have
Emacs run it automatically whenever we save a Markdown file. We’ll discuss
optional arguments, interactive
code characters, progn
forms, hooks,
buffer-local variables, and Lisp-2s.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Imperfect C++中文版
威尔逊 / 荣耀、刘未鹏 / 人民邮电出版社 / 2006-1 / 75.0
汇集实用的C++编程解决方案,C++虽然是一门非凡的语言,但并不完美。Matthew Wilson使用C++十年有余,其间发现C++存在一些固有的限制,需要一些颇具技术性的工作进行弥补。本书不仅指出了C++的缺失,更为你编写健壮、灵活、高效、可维护的代码提供了实用的技术和工具。Wilson向你展示了如何克服C++的复杂性,穿越C++庞大的范式阵列。夺回对代码的控制权,从而获得更理想的结果。一起来看看 《Imperfect C++中文版》 这本书的介绍吧!