Skip to content

Commit 07cd5d7

Browse files
authored
Improve html preview (#470)
* This wasn't doing anything anyway. The reason we have had syntax highlighting in the html preview is because of some styling added at the end of the github css. * Add a helper to detect dark mode Positron doesn't support dark vs. light mode detection from R yet (posit-dev/positron#2986), but reprex will just use light mode unconditionally for now. Which is still better than the current fugliness. * Grab github CSS: the opening move * Fixup the github CSS for our application * Take github css from here, in reprex; stop using a template * Add a custom syntax highlighting theme * Vendor the R syntax definition * Fixup r.xml * Install pandoc in the test-coverage job * Go back to `verbose = FALSE`
1 parent 38ba49f commit 07cd5d7

File tree

15 files changed

+3772
-15
lines changed

15 files changed

+3772
-15
lines changed

.github/workflows/test-coverage.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
steps:
2020
- uses: actions/checkout@v4
2121

22+
- uses: r-lib/actions/setup-pandoc@v2
23+
2224
- uses: r-lib/actions/setup-r@v2
2325
with:
2426
use-public-rspm: true

R/reprex_render.R

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -182,26 +182,21 @@ reprex_render_impl <- function(input,
182182
invisible(reprex_file)
183183
}
184184

185-
# heavily influenced by the post_processor() function of github_document()
185+
# influenced by the post_processor() function of github_document()
186+
# but with substantial reprex-specific updates in 2024-09
186187
preview <- function(input) {
187-
css <- rmarkdown::pandoc_path_arg(
188+
mode <- if (is_dark_mode()) "dark" else "light"
189+
res_dir <- rmarkdown::pandoc_path_arg(
188190
path_package(
189-
"rmarkdown",
190-
"rmarkdown/templates/github_document/resources/github.css"
191-
)
192-
)
193-
css <- glue("github-markdown-css:{css}")
194-
template <- rmarkdown::pandoc_path_arg(
195-
path_package(
196-
"rmarkdown",
197-
"rmarkdown/templates/github_document/resources/preview.html"
191+
"reprex",
192+
glue("rmarkdown/templates/reprex_document/resources")
198193
)
199194
)
200195
args <- c(
201-
"--standalone", "--self-contained",
202-
"--highlight-style", "pygments",
203-
"--template", template,
204-
"--variable", css,
196+
"--standalone", "--embed-resources",
197+
"--highlight-style", path(res_dir, glue("starry-nights-{mode}.theme")),
198+
"--css", path(res_dir, glue("github-{mode}.css")),
199+
"--syntax-definition", path(res_dir, "r.xml"),
205200
"--metadata", "pagetitle=PREVIEW",
206201
"--quiet"
207202
)

R/utils.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,12 @@ newline <- function(x) paste0(x, "\n")
6363
is_testing <- function() {
6464
identical(Sys.getenv("TESTTHAT"), "true")
6565
}
66+
67+
is_dark_mode <- function() {
68+
if (rstudioapi::isAvailable() && rstudioapi::hasFun("getThemeInfo")) {
69+
theme_info <- rstudioapi::getThemeInfo()
70+
theme_info$dark
71+
} else {
72+
FALSE
73+
}
74+
}

data-raw/github_css.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# use the CSS found here:
2+
# https://github.com/sindresorhus/github-markdown-css
3+
# which aims to be:
4+
# "The minimal amount of CSS to replicate the GitHub Markdown style"
5+
6+
library(usethis)
7+
8+
use_directory("inst/rmarkdown/templates/reprex_document/resources")
9+
10+
use_github_file(
11+
repo_spec = "sindresorhus/github-markdown-css",
12+
path = "github-markdown-dark.css",
13+
save_as = "inst/rmarkdown/templates/reprex_document/resources/github-dark.css"
14+
)
15+
16+
use_github_file(
17+
repo_spec = "sindresorhus/github-markdown-css",
18+
path = "github-markdown-light.css",
19+
save_as = "inst/rmarkdown/templates/reprex_document/resources/github-light.css"
20+
)
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
# write Pandoc's default (pygments) highlight style to a JSON file
2+
# pandoc --print-highlight-style pygments > data-raw/pygments.theme
3+
# use this as a scaffold, but modify colors to match the Starry Night theme
4+
# that GitHub uses
5+
6+
library(tidyverse)
7+
8+
pyg <- jsonlite::fromJSON("data-raw/pygments.theme")
9+
pyg |> pluck("text-styles") |> names()
10+
11+
# How does Pandoc do syntax highlighting?
12+
# https://pandoc.org/chunkedhtml-demo/13-syntax-highlighting.html
13+
# "The Haskell library skylighting is used for highlighting."
14+
15+
# Pandoc/skylighting store themes using the
16+
# Syntax-Highlighting framework from KDE Frameworks
17+
18+
# Learn the meaning of the styles (classes) supported by Pandoc here:
19+
# https://docs.kde.org/stable5/en/kate/katepart/highlight.html
20+
# see "Available Default Styles"
21+
# https://docs.kde.org/stable5/en/kate/katepart/color-themes.html
22+
# see "Default Text Styles"
23+
24+
# this is me taking the styles Pandoc/skylighting expects,
25+
# noting what they are meant for conceptually,
26+
# and picking the starry-night color alias/variable that seems the best fit
27+
28+
x <- str_glue('
29+
Alert,"Text style for special words in comments, such as TODO, FIXME, XXXX and WARNING.",\\
30+
comment,
31+
Annotation,"Text style for annotations in comments or documentation commands, such as @param in Doxygen or JavaDoc.",\\
32+
comment,
33+
Attribute,"Text style for annotations or attributes of functions or objects, e.g. @override in Java, or __declspec(...) and __attribute__((...)) in C++.",\\
34+
entity-tag,
35+
BaseN,"(Base-N Integer): Text style for numbers with base other than 10.",\\
36+
constant,
37+
BuiltIn,"(Built-in): Text style for built-in language classes, functions and objects.",\\
38+
entity,
39+
Char,"(Character): Text style for single characters such as \'x\'.",\\
40+
string,
41+
Comment, "Text style for normal comments.",\\
42+
comment,
43+
CommentVar,"(Comment Variable): Text style that refers to variables names used in above commands in a comment, such as foobar in \'@param foobar\', in Doxygen or JavaDoc.",\\
44+
comment,
45+
Constant,"Text style for language constants and user defined constants, e.g. True, False, None in Python or nullptr in C/C++; or math constants like PI.",\\
46+
constant,
47+
ControlFlow,"(Control Flow): Text style for control flow keywords, such as if, then, else, return, switch, break, yield, continue, etc.",\\
48+
keyword,
49+
DataType,"(Data Type): Text style for built-in data types such as int, char, float, void, u64, etc.",\\
50+
constant,
51+
DecVal,"(Decimal/Value): Text style for decimal values.",\\
52+
constant,
53+
Documentation,"Text style for comments that reflect API documentation, such as /** doxygen comments */ or \"\"\"docstrings\"\"\".",\\
54+
comment,
55+
Error,"Text style indicating error highlighting and wrong syntax.",\\
56+
invalid-illegal-text,invalid-illegal-bg
57+
Extension,"Text style for well-known extensions, such as Qt™ classes, functions/macros in C++ and Python or boost.",\\
58+
entity,
59+
Float,"(Floating Point): Text style for floating point numbers.",\\
60+
constant,
61+
Function,"Text style for function definitions and function calls.",\\
62+
entity,
63+
Import,"(Imports, Modules, Includes): Text style for includes, imports, modules or LATEX packages.",\\
64+
storage-modifier-import,
65+
Information,"Text style for information, notes and tips, such as the keyword @note in Doxygen.",\\
66+
comment,
67+
Keyword,"Text style for built-in language keywords.",\\
68+
keyword,
69+
Operator,"Text style for operators, such as +, -, *, /, %, etc.",\\
70+
keyword,
71+
Others,"Text style for attributes that do not match any of the other default styles.",\\
72+
,
73+
Preprocessor,"Text style for preprocessor statements or macro definitions.",\\
74+
keyword,
75+
SpecialChar,"(Special Character): Text style for escaped characters in strings, e.g. \"hello\\n\", and other characters with special meaning in strings, such as substitutions or regex operators.",\\
76+
carriage-return-text,carriage-return-bg
77+
SpecialString,"(Special String): Text style for special strings, such as regular expressions in ECMAScript, the LATEX math mode, SQL, etc.",\\
78+
string-regexp,
79+
String,"Text style for strings like \"hello world\".",\\
80+
string,
81+
Variable,"Text style for variables, if applicable. For instance, variables in PHP/Perl typically start with a $, so all identifiers following the pattern $foo are highlighted as variable.",\\
82+
variable,
83+
VerbatimString,"(Verbatim String): Text style for verbatim or raw strings like \'raw \backlash\' in Perl, CoffeeScript, and shells, as well as r\'\raw\' in Python, or such as HERE docs.",\\
84+
string,
85+
Warning,"Text style for warnings, such as the keyword @warning in Doxygen.",\\
86+
comment,
87+
')
88+
89+
dat <- read_csv(x, col_names = c("pyg_class", "pyg_note", "pl_text_style", "pl_background_style"))
90+
dat
91+
92+
# get the starry-night css in order to excavate the colors
93+
use_github_file(
94+
repo_spec = "wooorm/starry-night",
95+
path = "style/dark.css",
96+
save_as = "data-raw/starry-night-dark.css"
97+
)
98+
99+
use_github_file(
100+
repo_spec = "wooorm/starry-night",
101+
path = "style/light.css",
102+
save_as = "data-raw/starry-night-light.css"
103+
)
104+
105+
raw_css_lines <- readLines("data-raw/starry-night-light.css")
106+
sn_light <- raw_css_lines |>
107+
str_subset(" --color-prettylights-syntax-") |>
108+
tibble() |>
109+
set_names("raw") |>
110+
mutate(comment = str_extract(raw, ".+:")) |>
111+
mutate(color = str_extract(raw, "#[0-9a-fA-F]{6}")) |>
112+
mutate(comment = str_remove(comment, " --color-prettylights-syntax-")) |>
113+
mutate(comment = str_remove(comment, ":$")) |>
114+
select(comment, color)
115+
116+
raw_css_lines <- readLines("data-raw/starry-night-dark.css")
117+
sn_dark <- raw_css_lines |>
118+
str_subset(" --color-prettylights-syntax-") |>
119+
tibble() |>
120+
set_names("raw") |>
121+
mutate(comment = str_extract(raw, ".+:")) |>
122+
mutate(color = str_extract(raw, "#[0-9a-fA-F]{6}")) |>
123+
mutate(comment = str_remove(comment, " --color-prettylights-syntax-")) |>
124+
mutate(comment = str_remove(comment, ":$")) |>
125+
select(comment, color)
126+
127+
dat
128+
sn_light
129+
sn_dark
130+
131+
dat_light <- dat |>
132+
rename(cls = pyg_class) |>
133+
select(-pyg_note) |>
134+
left_join(
135+
sn_light,
136+
by = join_by(pl_text_style == comment)
137+
) |>
138+
rename(txt_col = color) |>
139+
left_join(
140+
sn_light,
141+
by = join_by(pl_background_style == comment)
142+
) |>
143+
rename(bg_col = color) |>
144+
select(-starts_with("pl_"))
145+
dat_light
146+
147+
dat_dark <- dat |>
148+
rename(cls = pyg_class) |>
149+
select(-pyg_note) |>
150+
left_join(
151+
sn_dark,
152+
by = join_by(pl_text_style == comment)
153+
) |>
154+
rename(txt_col = color) |>
155+
left_join(
156+
sn_dark,
157+
by = join_by(pl_background_style == comment)
158+
) |>
159+
rename(bg_col = color) |>
160+
select(-starts_with("pl_"))
161+
dat_dark
162+
163+
g <- function(cls, txt_col, bg_col) {
164+
list(
165+
`text-color` = txt_col,
166+
`background-color` = bg_col,
167+
bold = FALSE,
168+
italic = FALSE,
169+
underline = FALSE
170+
)
171+
}
172+
173+
theme_light <- theme_dark <- pyg
174+
theme_light[["text-styles"]] <- dat_light |>
175+
pmap(g) |>
176+
set_names(dat_light$cls)
177+
theme_dark[["text-styles"]] <- dat_dark |>
178+
pmap(g) |>
179+
set_names(dat_dark$cls)
180+
181+
jsonlite::write_json(
182+
theme_light,
183+
"inst/rmarkdown/templates/reprex_document/resources/starry-nights-light.theme",
184+
null = "null",
185+
auto_unbox = TRUE,
186+
pretty = TRUE
187+
)
188+
189+
jsonlite::write_json(
190+
theme_dark,
191+
"inst/rmarkdown/templates/reprex_document/resources/starry-nights-dark.theme",
192+
null = "null",
193+
auto_unbox = TRUE,
194+
pretty = TRUE
195+
)

0 commit comments

Comments
 (0)