Intro to Emacs Lisp: Adding Live Previews when Editing Markdown Files (Part 1 of 3)

栏目: IT技术 · 发布时间: 4年前

内容简介: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.

Intro to Emacs Lisp: Adding Live Previews when Editing Markdown Files (Part 1 of 3)

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++中文版

Imperfect C++中文版

威尔逊 / 荣耀、刘未鹏 / 人民邮电出版社 / 2006-1 / 75.0

汇集实用的C++编程解决方案,C++虽然是一门非凡的语言,但并不完美。Matthew Wilson使用C++十年有余,其间发现C++存在一些固有的限制,需要一些颇具技术性的工作进行弥补。本书不仅指出了C++的缺失,更为你编写健壮、灵活、高效、可维护的代码提供了实用的技术和工具。Wilson向你展示了如何克服C++的复杂性,穿越C++庞大的范式阵列。夺回对代码的控制权,从而获得更理想的结果。一起来看看 《Imperfect C++中文版》 这本书的介绍吧!

html转js在线工具
html转js在线工具

html转js在线工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具