Emacs Markdown Editing and Viewing
There has been an Emacs Markdown mode for many years (2007 to be exact), which I have been using for editing files such as this one for my blog. Written by Jason Blevins, it is full-featured and a very capable editing mode for Markdown content. However, Emacs v31 brought out a new “experimental” markdown-ts-mode that I have been using, and although tagged as experimental, it is now what I have enabled for editing Markdown files.
The article I linked to above by Rahul M. Juliato does a pretty good job describing how to get markdown-ts-mode working properly. In short, I execute the following function at startup to make sure that there is a Markdown Tree-sitter grammar available to use. And I make sure that any attempts to use markdown-mode will map instead to the new markdown-ts-mode.
(defun my/markdown-ts-mode-init ()
"Initialization for `markdown-ts-mode'."
(setq treesit-auto-install-grammar t
treesit-enabled-modes t)
(markdown-ts-mode-install-parsers t)
(setopt markdown-ts-default-converter '(html . pandoc)
markdown-ts-convert-display-function #'my/markdown-render-display)
(add-to-list 'major-mode-remap-alist '(markdown-mode . markdown-ts-mode)))
For completeness, Here is what is run when I start editing a markdown file:
(defun my/markdown-ts-mode-hook ()
"Customization hook for `markdown-ts-mode'."
(keymap-local-set "C-c *" #'my/markdown-escape-last-word)
(keymap-local-set "C-c `" #'my/markdown-codify-last-word)
(keymap-local-set "H-c e" #'my/replace-region-with-calc-eval)
(keymap-local-set "M-<left>" #'backward-word)
(keymap-local-set "M-<right>" #'forward-word)
(keymap-local-set "C-c <space>" #'my/markdown-make-link)
(keymap-local-set "H-c r" #'my/markdown-render)
(keymap-local-set "H-c H-c" #'my/markdown-render)
(flyspell-mode t))
Just some custom bindings (hi hyper!), most that map to some custom functions. The my/markdown-escape-last-word function escapes the “_” and “*” characters so that markdown-ts-mode will not treat them as inline Markdown formatting characters. So the string “this_is_a_test” becomes “this\_is\_a\_test” in the Markdown document but when rendered to HTML it will just be “this_is_a_test”[1].
(defun my/markdown-escape-last-word ()
"Escape '_' and '*' characters in the word before point."
(interactive)
(let ((pos (point)))
(save-excursion
(re-search-forward "\\s " nil nil -1)
(format-replace-strings '(("_" . "\\_") ("*" . "\\*")) nil (point) pos))))
The my/markdown-codify-last-word wraps the word before the cursor (point) with backquotes, so “blah” becomes `blah` and when rendered appears as blah.
(defun my/markdown-codify-last-word ()
"Escape wrap the last word with backquotes."
(interactive)
(save-excursion
(re-search-forward "\\s " nil nil -1)
(when (looking-at "\\s ")
(forward-char 1))
(insert-char ?`))
(insert-char ?`))
Finally, my/markdown-render asks for an HTML rendering of the Markdown content using pandoc (installed separately via brew install pandoc).
(defun my/markdown-render ()
"Render to tmp file and show/update in `eww' buffer."
(interactive)
(markdown-ts-convert nil ".markdown_convert.html" '(html . pandoc) t t t))
When the conversion is done, markdown-ts-mode invokes the function found in the markdown-ts-convert-display-function variable, which points to my/markdown-render-display, a function that presents the HTML output in a buffer below the Markdown source using the Emacs Web Wowser (eww) to do the HTML rendering.
(defun my/markdown-render-display (file)
"Show the HTML in FILE."
(other-window-prefix)
(eww-open-file file))
Thus a quick H-c H-c will process the Markdown in a buffer and render the HTML output below. The output buffer is view-only, so a quick q will dismiss the HTML output. Very nice!
this was an issue with the older
markdown-modemode, but it appears that the Tree-sitter version does the right thing without any escaping needed. ↩
![Validate my RSS feed [Valid RSS]](/images/valid-rss-rogers.png)