Custom syntax highlighting for {distill} part 1

Modifying the default theme

A guide to modifying the default {distill} syntax highlighting theme, including colour choice considerations, and its implementation in {distilltools}

distill
distilltools
colour
accessibility
R
Author

Ella Kaye

Published

May 25, 2021

Update: August 2022

Since this post was originally written, I have replaced my {disill} website with this one built with Quarto.

arrow is the default syntax highlighting theme for Quarto, as it was for {distill}, although it is implemented slightly differently here, and the process of modifying it for Quarto is not the same as it was before. I’ll lay out the differences in a future post. Moreover, as a result, the highlighting you’ll see below is not quite the same as it appeared on my previous site, and if you’re reading this in dark mode, it’s a whole different scheme again.1 That said, I am keeping this post otherwise as was on the blog for posterity.

Overview

The {distill} package for R can be used to build easy-to-maintain websites written only in R Markdown, such as this one.2

I wrote a function to modify the default syntax highlighting theme. This modify_default_highlighting function is now part of the {distilltools} package.

Here’s the function in action, to get the theme used on this site:

Toggle the code
# get distilltools, requires remotes >= 2.2
remotes::install_github("EllaKaye/distilltools") 

library(distilltools)
modify_default_highlighting(
  name = "ek_syntax_highlighting",
  numeric = "#B6005B", # replaces the default red
  strings = "#008643", # replaces the default green
  functions = "#005BB6", # replaces the default purple
  control = "#5B00B6", # replaces the default blue
  constant = "#B65B00" # replaces the default brown
)

And here’s the theme in action:

Toggle the code
library(dplyr)
library(palmerpenguins)

penguins %>%
  mutate(
    long_flipper = case_when(
      species == "Adelie" & flipper_length_mm > 195 ~ TRUE,
      species == "Chinstrap" & flipper_length_mm > 200 ~ TRUE,
      species == "Gentoo" & flipper_length_mm > 225 ~ TRUE,
      TRUE ~ FALSE
    )
  ) %>%
  mutate(
    long_bill = case_when(
      species == "Adelie" & bill_length_mm > 42 ~ TRUE,
      species == "Chinstrap" & bill_length_mm > 52 ~ TRUE,
      species == "Gentoo" & bill_length_mm > 50 ~ TRUE,
      TRUE ~ FALSE
    )
  )

This is the first in a series of two posts on implementing a custom syntax highlighting theme for a website or blog built with {distill}. Read on here for why and how this function was built, what it does and doesn’t do, and its inclusion in the {distilltools} package. This post also outlines the criteria that were important to me when choosing colours for my theme, but take a look at part 2, Custom syntax highlighting for {distill}: Creating the palette, for a deep dive into considerations about colour choices, in respect to both colour theory and accessibility, and how I ensured my criteria were met.

But first, the default

In praise of the default

Before I delve into how to create a custom syntax highlighting scheme, I want to take a minute to admire the default. The authors of {distill}, in particular Alison Hill, have thought and worked hard to ensure that {distill} provides a good user experience, both for the site’s author AND for those reading it. One of the key considerations for the latter is a default syntax highlighting scheme with colours that are optimised for accessibility and colour contrast. I say more about what that means below and in part 2. Also, it appears that the colours in the scheme work well together, and overall, on the many {distill} websites where I’ve seen the scheme used, I think it looks really good! For a great example of the default in action, check out this code-chunk-heavy post by Tom Mock.

So, why change?

When I used the default syntax highlighting theme on my site I found, to my eye, that the red used for numeric variables clashed with the bright pink (closest colour name “razzmatazz”) I’ve used in my logo and elsewhere throughout the site. So, I decided to tweak the default theme swapping the red for my pink and, to match it, more vibrant versions of the remaining colours.

Distill/pandoc documentation

My first task was to find out whether this was possible, and if, so, how. Thankfully, the {distill} documentation contains a section on syntax highlighting, showing that there is an option to give distill_article a path to a custom .theme file. The linked pandoc documentation on syntax highlighting demonstrates how to use pandoc in the command line to save a personal version of the pygments highlighting theme. The documentation points out that that copy of the pygments .theme file can then be edited to create a custom theme.

Finding and saving the default

Once I had a general strategy of copying and editing an existing .theme file, my next task was to find the .theme file for the default used in {distill}, because that’s what I’d already decided to take as my starting point.

I cloned the distill repo from GitHub and opened it in RStudio.3 From there I began my detective work with one of my favourite RStudio features, ‘Find in Files’ (⇧ + ⌘ + F on a Mac), which searches across all files in a project. I searched for “highlight” and followed various trails until I discovered that the default is called arrow.theme and is stored in inst/rmarkdown/templates/distill_article/resources/. Thankfully, because it’s in the inst folder, the file is accessible to users who have the {distill} package installed. In the RStudio project for my website, I created a new script, syntax_highlighting.R, in the R folder I have in my root directory, then I ran the following to save a copy of arrow.theme into my website’s root directory:

Toggle the code
arrow_theme_path <- system.file(
  "rmarkdown/templates/distill_article/resources/arrow.theme", 
  package = "distill"
)

file.copy(arrow_theme_path, "arrow.theme")

When I had run that once, I commented out the above lines. I don’t want any future changes in arrow.theme in {distill} to break what I do next.

Closer inspection of the default

From there I could open up my copy of the arrow.theme file and manually inspect it. I use a great Mac app for building colour palettes, ColorSlurp.4 The basic version is free, though the pro version has useful features for testing accessibility - more on that in part 2. I set up a new palette in ColorSlurp and, for each hex colour code I encountered, I saved it there.

There are 29 types of text-styles in the theme, of which:

  • 8 are assigned a grey, #5E5E5E, things like Comment and Documentation

  • 1 is off-black, #111111, Variable

  • 3 are blue, #007BA5, Other, ControlFlow and Keyword

  • 4 are green, #20794D, corresponding to various types of string

  • 1 is purple, #4758AB, Function

  • 7 are red, #AD0000, a mix of numeric (e.g. BaseN, Float) and things like Alert and Error

  • 1 is a brown, #8F5902, for Constant

  • 4 types are not assigned a colour - they are left as null

I was happy to stick with the groupings, grey, off-black and null in the default, so now I knew I had to pick five colours for my theme.

Building my own palette

I had four criteria for building a colour palette to use for my syntax highlighting theme:

  • It be based on the pink that I use in my logo and elsewhere throughout this site
  • It uses colour theory to pick colours that look appealing together
  • It meets WCAG web accessibility guidelines, by ensuring sufficient colour contrast, i.e. a ratio of 4.5:1, between each of the colours in the theme and this site’s background colour (white)
  • The colours in the palette are colourblind-friendly, i.e. still distinguishable to people with various difference types of colourblindness.

I was originally going to write up how I went about building such a palette, both in terms of the thought process and tools used, as part of this post, but it was getting a little long5, so I’ve spun it out into a separate post, Custom syntax highlighting for {distill} part 2: Creating the palette.

At the end of the process, the palette for my syntax highlighting scheme is as in Figure 1.

A swatch of five colours, a pink, orange, green, blue and purple, all quite bright, but still all with a contrast ratio of 4.5:1 or better against white.

Figure 1: The palette I settled on for my syntax highlighting scheme.

Modifying arrow.theme

With all the pieces in place, it’s now just a case of swapping out the default colour codes for our own choices. Although it’s possible to manually edit the arrow.theme file we now have in our directory, to aid reproducibility, and with a view to writing this up as a function, I edited it using R code instead. I read in the file, substituted the hex codes, then saved the resulting theme into a new ek_syntax_highlighting.theme file (leaving arrow.theme unchanged). Below are two approaches, one using base R, the other in the tidyverse, that I put in my syntax_highlighting.R script.

Toggle the code
# read in the default theme
theme <- readLines("arrow.theme")

# base R approach
theme <- gsub("#AD0000", "#B6005B", theme) # red -> pink
theme <- gsub("#8f5902", "#B65B00", theme) # brown -> brown 
theme <- gsub("#007BA5", "#5B00B6", theme) # blue -> purple
theme <- gsub("#20794D", "#B65B00", theme) # green -> green
theme <- gsub("#4758AB", "#005BB6", theme) # purple -> blue

# alternatively, tidyverse approach
library(stringr)
library(magrittr)

theme <- readLines("arrow.theme") %>%
   str_replace_all("#AD0000", "#B6005B") %>% # red -> pink
   str_replace_all("#8f5902", "#B65B00") %>% # brown -> brown 
   str_replace_all("#007BA5", "#5B00B6") %>% # blue -> purple
   str_replace_all("#20794D", "#008643") %>% # green -> green
   str_replace_all("#4758AB", "#005BB6")     # purple -> blue

# save new theme
writeLines(theme, "ek_syntax_highlighting.theme")

I now have the file ek_syntax_highlighting.theme in my root directory, with my colour choices.

It is, of course, possible to modify it further, either manually or by making further substutions in the code above. There is a quirk, though: if I swap one of the default hex codes for my own colour choice, that implements just fine, but if I swap any of the nulls for a colour, that doesn’t show up when I apply the theme.

Wrapping in a {distilltools} function

When I figure out code to do something that I think I might want to do again, or think others might find useful, I generally like to write it up as a function, and that’s what I’ve done with the above, wrapping it in a function called modify_default_highlighting and putting it in the {distilltools} package. {distilltools} is in the very early stages of development, an (expanding) collection of tools to support the creation and styling of content on websites created using {distill}.

When I first announced the package, I included in the ‘future functionality’ section of the package README the intention to add a create_highlight_theme function. I don’t think what I’ve done is quite versatile or fully-featured enough to warrant that name. Instead, I called it modify_default_highlighting because that’s all it does, allowing you to swap out the five colours in the default scheme for five colours of your choosing. It does, however, create a .theme file in your working directory, that can be further edited manually, or with your own R code, if further modifications are desired.

The first argument to modify_default_highlighting is name, the name you want to give your theme (which will create the file name.theme). It then takes five colour arguments, which can be specified either in the hex form “#RRGGBB” or as a named colour, from the colour names in grDevices::colors(). For a list of the colour names available in R, see page 3 of the R color cheatsheet for a one page summary, or Colors in R for a slightly less visually overwhelming list. The final argument, overwrite (defaults to TRUE) specifies whether to overwrite name.theme if it already exists in the working directory. Here’s the function in action again, this time using colour names (note that I have not tested the visual properties of this as a palette, just dropped in some colour names into the function):

Toggle the code
library(distilltools)
modify_default_highlighting(
  name = "ek_syntax_highlighting",
  numeric = "deeppink", # replaces the default red
  strings = "forestgreen", # replaces the default green
  functions = "darkorchid3", # replaces the default purple
  control = "royalbkue3", # replaces the default blue
  constant = "darkorange1" # replaces the default brown
)

Using the theme

Once you have you custom .theme file, you’ll want to apply it to your site. According to the {distill} documentation, you can apply a syntax highlighting theme with the following YAML:

---
output:
  distill::distill_article:
    highlight: my_highlighting.theme
---  

Here, the available options for highlight include default, rstudio (the default RStudio editor theme), and the haddock, kate, monochrome, pygments and tango pandoc highlighting themes. Also, most importantly for us, it can also take the path to a .theme file.

Presumably, we should be able to add this to our _site.yml file to have the theme apply site-wide (though note you’ll have to rebuild the site and re-knit any posts where you want to it apply). However, that’s not working for me. I have raised an issue about it. If you face the same problem, it would be helpful if you could comment there too.

There is a workaround, though, which is that the above YAML can also be included in individual .Rmd articles, in which case the theme applies just fine, though note that if your .theme file is in you root directory, you’ll need to give the full path to it.6 And if it sounds like a bit of a pain to have to add that every time you write a new post, consider creating a template for your posts, including those lines, and then starting new posts with the {distilltools} function create_post_from_template().7

Last updated

2022-11-13 11:43:53 GMT

Details

Session info

Toggle
─ Session info ───────────────────────────────────────────────────────────────
 setting  value
 version  R version 4.2.1 (2022-06-23)
 os       macOS Monterey 12.6.1
 system   aarch64, darwin20
 ui       X11
 language (EN)
 collate  en_US.UTF-8
 ctype    en_US.UTF-8
 tz       Europe/London
 date     2022-11-13
 pandoc   2.19.2 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/ (via rmarkdown)
 quarto   1.2.247 @ /usr/local/bin/quarto

─ Packages ───────────────────────────────────────────────────────────────────
 ! package     * version    date (UTC) lib source
 P coloratio   * 0.0.0.9004 2022-10-23 [?] Github (matt-dray/coloratio@852ac49)
 P ggplot2     * 3.3.6      2022-05-03 [?] CRAN (R 4.2.0)
 P htmltools   * 0.5.3      2022-07-18 [?] CRAN (R 4.2.0)
 P sessioninfo * 1.2.2      2021-12-06 [?] CRAN (R 4.2.0)

 [1] /private/var/folders/xf/jb2591gj41xbj0c4y2d8_7ch0000gn/T/RtmpRPolvI/renv-library-682552373a24
 [2] /Users/ellakaye/Rprojs/mine/ellakaye-quarto/renv/library/R-4.2/aarch64-apple-darwin20
 [3] /Library/Frameworks/R.framework/Versions/4.2-arm64/Resources/library

 P ── Loaded and on-disk path mismatch.

──────────────────────────────────────────────────────────────────────────────

Footnotes

  1. If you’d like to see the original theme in action, you can see it over at the distillery.↩︎

  2. For more on resources and inspirations for setting up a {distill} website, see my previous post, Welcome to my {distill} website.↩︎

  3. From RStudio, go to ‘File’ in the menu bar, then ‘New Project…’ Chose ‘Version Control’, then ‘Git’, then enter https://github.com/rstudio/distill.git as the ‘Repository URL:’ and click ‘Create Project’↩︎

  4. I don’t have experience of colour apps on other operating systems, but a quick search for ColorSlurp alternatives suggest there are a bunch to pick from.↩︎

  5. As part of the process of writing the post, I learnt much more about colour theory, and alternatives to the approach that I had originally taken, and I wanted to share it all!↩︎

  6. If you happen to inspect the source code for this post, you might notice that I have copied ek_syntax_highlighting.theme into the directory. This is not necessary for my site (using the full path to the .theme folder in my root directory is sufficient). However, I needed to do this to get the theme to show in the version of this post in the distillery.↩︎

  7. Note that the create_post_from_template() function is likely to get wrapped into {distill} itself, or its functionality included in distill::create_post(), and will be depreciated from {distilltools} if so. (I have had some discussion with the {distill} team about this and will be submitting a PR in the near future.)↩︎

Reuse

Citation

BibTeX citation:
@online{kaye2021,
  author = {Ella Kaye},
  title = {Custom Syntax Highlighting for \{Distill\} Part 1},
  date = {2021-05-25},
  url = {https://ellakaye.co.uk/posts/2021-05-25_custom-highlighting-distill-1},
  langid = {en}
}
For attribution, please cite this work as:
Ella Kaye. 2021. “Custom Syntax Highlighting for {Distill} Part 1.” May 25, 2021. https://ellakaye.co.uk/posts/2021-05-25_custom-highlighting-distill-1.