Fix Rmarkdown fontification issues in Emacs

John Haman

2020/08/22

Categories: Emacs R Tags: Emacs R

Using markdown-mode with Polymode in Emacs is a bit of a challenge for statisticians. But it’s still probably the best way to work with .Rmd files. I’ll describe the issues and my solution in this post.

Polymode, the software that lets Emacs have multiple major modes in a single buffer, generally works well and provides a lot of premade polymodes for working with different types of code. The most important premade polymode is the one for .Rmd files. This lets Emacs users run ESS in the R code chunks and markdown-mode in the rest of the buffer. It is very convenient.

Lately markdown-mode has had a lot of screwy fontification issues. When editing polymode buffers, lots of users are finding that fonts in the code fences are ‘randomly’ changing typeface. Worse, sometimes syntax highlighting will completely stop in ESS code chunks, and it can only sometimes be brought back by editing the code chunks. It’s hard to describe how grating this is.

I realized this week that, for the most part markdown-mode, is not a requirement for editing .Rmd files – The files can easily be edited in Emacs’ built-in text mode. But this is not great for editing files with lots of R code, because the code chunks won’t be edited with ESS. Luckily markdown isn’t much more than text – it just adds the #s and a YAML header. Compared to the R code, markdown doesn’t really add a lot of machinery that is required to write a blog post or a report.

My solution to the fontification problems caused by markdown-mode is to drop markdown. I wrote a simple polymode that treats text mode as the host mode, and lets Emacs users use ESS in the R code fences. I’ve been testing this polymode a little bit and have not encountered a single fontification issue.

Here is the polymode code:

(use-package outline
  :custom
  (outline-regexp "[#]+"))

(use-package polymode
  :ensure t
  :mode ("\\.[rR]md\\'" . poly-text-R-mode)
  :hook (poly-text-R-mode . outline-mode)
  :config
  (define-innermode poly-text-R-innermode
    :indent-offset 2
    :head-matcher (cons "^[ \t]*\\(```[ \t]*{?[[:alpha:]].*\n\\)" 1)
    :tail-matcher (cons "^[ \t]*\\(```\\)[ \t]*$" 1)
    :mode 'ess-r-mode
    :head-mode 'host
    :tail-mode 'host)
  (define-polymode poly-text-R-mode
    :hostmode 'pm-host/text
    :innermodes '(poly-text-R-innermode)))

A little explanation: Outline mode can be used to replace some of the functionality of markdown-mode, but you need to tell it what form the headings are with a regular expression. The polymode poly-text-R-mode uses text-mode as the host mode, and the function poly-text-R-innermode looks for where the code fences are and activates ESS therein. All of this is automatic when an .Rmd file is visited by Emacs.

The only downside I have found in using this Polymode is that my function polymode-export no longer works. I think I can fix this issue by writing a new function.

Happy Emacsing.