---
title: "Custom syntax highlighting for {distill} part 1"
subtitle: "Modifying the default theme"
description: |
A guide to modifying the default {distill} syntax highlighting theme, including colour choice considerations, and its implementation in {distilltools}
date: 2021-05-25
author:
- name: Ella Kaye
##url: httep://twitter.com/ellakaye
site-url: https://ellakaye.co.uk
image: more-theme-in-action.png
#image-alt: |
categories:
- distill
- distilltools
- colour
- accessibility
- R
twitter-card:
# uncomment lines below for different title and description to post
# title: |
# description: |
# uncomment for different image to post
# image: |
# image-alt: |
# defaults to 500 x 500 summary: uncomment lines below for large card
image-width: 600
image-height: 314
card-style: "summary_large_image"
draft: false
---
```{r}
#| echo: false
#| results: 'hide'
long_slug <- "2021-05-25_custom-highlighting-distill-1"
# NOTE: after finishing post, run renv::snapshot() and copy the renv.lock file
# from the project root into the post directory
renv::use(lockfile = "renv.lock")
```
:::{.callout-note collapse="false" .top-callout}
## Update: August 2022
Since this post was originally written, I have replaced my {disill} website with this one built with [Quarto](https://quarto.org).
`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.^[If you'd like to see the original theme in action, you can see it over at the [distillery](https://distillery.rbind.io/posts/2021-05-25-custom-syntax-highlighting-for-distill-part-1-modifying-the-default-theme/).] That said, I am keeping this post otherwise as was on the blog for posterity.
:::
## Overview
The [{distill}](https://rstudio.github.io/distill/) package for R can be used to build easy-to-maintain websites written only in R Markdown, such as this one.^[For more on resources and inspirations for setting up a {distill} website, see my previous post, [Welcome to my {distill} website](../2021-05-08_welcome-distill/index.qmd).]
I wrote a function to modify the default syntax highlighting theme. This `modify_default_highlighting` function is now part of the [{distilltools}](https://ellakaye.github.io/distilltools) package.
Here's the function in action, to get the theme used on this site:
```{r eval = FALSE}
# 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:
```{r eval = FALSE}
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}](https://github.com/EllaKaye/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](../2021-05-26_custom-highlighting-distill-2/index.qmd), 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](https://alison.rbind.io), 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](../2021-05-26_custom-highlighting-distill-2/index.qmd). 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](https://themockup.blog/posts/2021-03-07-creating-a-custom-gt-function-for-aligning-first-row-text-and-testing-it-with-testthat/) by [Tom Mock](https://twitter.com/thomas_mock).
### So, why change?
```{r echo = FALSE}
library(htmltools)
library(coloratio)
bg_col <- function(bg_col, text = NULL, text_col = NULL) {
if(is.null(text)) text <- bg_col
if(is.null(text_col)) text_col <- coloratio::cr_choose_bw(bg_col)
style <- paste0("background-color: ", bg_col, "; color: ", text_col, "; padding:3px")
htmltools::span(style = style, text)
}
```
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 `r bg_col("#D4006A", text = "bright pink")` (closest [colour name](https://chir.ag/projects/name-that-color/#D4006A) "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](https://rstudio.github.io/distill/#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](https://pandoc.org/MANUAL.html#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](https://github.com/rstudio/distill) and opened it in RStudio.^[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'] 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:
```{r eval = FALSE}
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](https://colorslurp.com).^[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](https://alternativeto.net/software/colorslurp/).] The basic version is free, though the pro version has useful features for testing accessibility - more on that in [part 2](../2021-05-26_custom-highlighting-distill-2/index.qmd). 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, `r bg_col("#5E5E5E")`, things like `Comment` and `Documentation`
- 1 is off-black, `r bg_col("#111111")`, `Variable`
- 3 are blue, `r bg_col("#007BA5")`, `Other`, `ControlFlow` and `Keyword`
- 4 are green, `r bg_col("#20794D")`, corresponding to various types of string
- 1 is purple, `r bg_col("#4758AB")`, `Function`
- 7 are red, `r bg_col("#AD0000")`, a mix of numeric (e.g. `BaseN`, `Float`) and things like `Alert` and `Error`
- 1 is a brown, `r bg_col("#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 <span style="color:#D4006A">pink</span> 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](https://www.w3.org/WAI/standards-guidelines/wcag/), 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 long^[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!], so I've spun it out into a separate post, [Custom syntax highlighting for {distill} part 2: Creating the palette ](../2021-05-26_custom-highlighting-distill-2/index.qmd).
At the end of the process, the palette for my syntax highlighting scheme is as in @fig-final-palette-light.
```{r fig-ek-syntax-highlighting-palette, echo = FALSE, eval = FALSE, fig.cap="The color palette used for syntax highlighting throughout this site.", fig.alt="A swatch of five colours, a bright pink, orange, green, blue and purple."}
knitr::include_graphics("ek-syntax-highlighting-palette.png")
```
```{r fig-final-palette-light}
#| echo: false
#| eval: true
#| fig-cap: "The palette I settled on for my syntax highlighting scheme."
#| fig-alt: "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."
#| fig-width: 6.5
#| fig-height: 4
#|
ek_highlight_colours <- c("#5B00B6", "#005BB6", "#008643", "#B65B00", "#B6005B")
library(ggplot2)
ggplot() +
geom_tile(aes(x=1:5, y = rep(1,5)), fill = rev(ek_highlight_colours)) +
theme_void() +
#theme(plot.background = element_rect(fill='transparent', color=NA))
theme(plot.background = element_rect(fill="#F8F9FA", color = "#F8F9FA"))
#swatchplot(rev(ek_highlight_colours), border = "transparent")
# Using the above code gives too much white around the swatches, so I ran the code, took a screen shot to crop it, then include that image back in.
```
```{r fig-final-palette-dark}
#| echo: false
#| eval: false
#| fig-cap: "The palette I settled on for my syntax highlighting scheme."
#| fig-alt: "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."
#| fig-width: 6.5
#| fig-height: 4
#| classes: dark-mode
#|
ek_highlight_colours <- c("#5B00B6", "#005BB6", "#008643", "#B65B00", "#B6005B")
library(ggplot2)
ggplot() +
geom_tile(aes(x=1:5, y = rep(1,5)), fill = rev(ek_highlight_colours)) +
theme_void() +
#theme(plot.background = element_rect(fill='transparent', color=NA))
theme(plot.background = element_rect(fill="#212129", color = "#212129"))
#swatchplot(rev(ek_highlight_colours), border = "transparent")
# Using the above code gives too much white around the swatches, so I ran the code, took a screen shot to crop it, then include that image back in.
```
## 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.
```{r eval = FALSE}
# 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 `null`s 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}](https://ellakaye.github.io/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()](https://rdrr.io/r/grDevices/colors.html). For a list of the colour names available in R, see page 3 of the [R color cheatsheet](https://www.nceas.ucsb.edu/sites/default/files/2020-04/colorPaletteCheatsheet.pdf) for a one page summary, or [Colors in R](http://www.stat.columbia.edu/~tzheng/files/Rcolor.pdf) 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):
```{r eval = FALSE}
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](https://rstudio.github.io/distill/#syntax-highlighting), 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](https://www.garrickadenbuie.com/blog/pandoc-syntax-highlighting-examples/). 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](https://github.com/rstudio/distill/issues/370) 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.^[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](https://distillery.rbind.io).] 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()`](https://ellakaye.github.io/distilltools/reference/create_post_from_template.html).^[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.)]
<!--------------- appendices go here ----------------->
```{r appendix}
#| echo: false
source("../../R/appendix.R")
insert_appendix(
repo_spec = "EllaKaye/ellakaye.co.uk",
name = long_slug
)
```
##### Session info {.appendix}
<details><summary>Toggle</summary>
```{r}
#| echo: false
library(sessioninfo)
# save the session info as an object
pkg_session <- session_info(pkgs = "attached")
# get the quarto version
quarto_version <- system("quarto --version", intern = TRUE)
# inject the quarto info
pkg_session$platform$quarto <- paste(
system("quarto --version", intern = TRUE),
"@",
quarto::quarto_path()
)
# print it out
pkg_session
```
</details>