Skip to content
Draft
57 changes: 36 additions & 21 deletions src/resources/rmd/execute.R
Original file line number Diff line number Diff line change
Expand Up @@ -142,36 +142,51 @@ execute <- function(input, format, tempDir, libDir, dependencies, cwd, params, r
df_print = df_print
)

# we need ojs only if markdown has ojs code cells
# inspect code cells for spaces after line breaks

needs_ojs <- grepl("(\n|^)[[:space:]]*```+\\{ojs[^}]*\\}", markdown)
# FIXME this test isn't failing in shiny mode, but it doesn't look to be
# breaking quarto-shiny-ojs. We should make sure this is right.
if (
!is_shiny_prerendered(knitr::opts_knit$get("rmarkdown.runtime")) &&
needs_ojs
) {
local({
# create a hidden environment to store specific objects
.quarto_tools_env <- attach(NULL, name = "tools:quarto")
# source ojs_define() function and save it in the tools environment
source(file.path(resourceDir, "rmd", "ojs_static.R"), local = TRUE)
assign("ojs_define", ojs_define, envir = .quarto_tools_env)
})
# create a hidden environment to store specific objects
# Beware to use non conflicted name as this will be in second position right after globalenv.
.quarto_tools_env <- attach(NULL, name = "tools:quarto")
.quarto_tools_env$.assignToQuartoToolsEnv <- function(name, value) {
assign(name, value, envir = .quarto_tools_env)
}
.quarto_tools_env$.getFromQuartoToolsEnv <- function(name) {
get0(name, envir = .quarto_tools_env)
}
.quarto_tools_env$.rmFromQuartoToolsEnv <- function(name) {
if (exists(name, envir = .quarto_tools_env)) {
rm(list = c(name), envir = .quarto_tools_env)
}
}

env <- globalenv()
env$.QuartoInlineRender <- function(v) {
# special internal function for rendering inline code using Quarto syntax
.assignToQuartoToolsEnv(".QuartoInlineRender", function(v) { # nolint: object_usage_linter, line_length_linter.
if (is.null(v)) {
"NULL"
} else if (inherits(v, "AsIs")) {
v
} else if (is.character(v)) {
gsub(pattern="(\\[|\\]|[`*_{}()>#+-.!])", x=v, replacement="\\\\\\1")
gsub(
pattern = "(\\[|\\]|[`*_{}()>#+-.!])",
x = v, replacement = "\\\\\\1"
)
} else {
v
}
})

# we need ojs only if markdown has ojs code cells
# inspect code cells for spaces after line breaks
needs_ojs <- grepl("(\n|^)[[:space:]]*```+\\{ojs[^}]*\\}", markdown)
# FIXME this test isn't failing in shiny mode, but it doesn't look to be
# breaking quarto-shiny-ojs. We should make sure this is right.
if (
!is_shiny_prerendered(knitr::opts_knit$get("rmarkdown.runtime")) &&
needs_ojs
) {
# source ojs_define() function into the tools environment
source(
file = file.path(resourceDir, "rmd", "ojs_static.R"),
local = .quarto_tools_env
)
}

render_output <- rmarkdown::render(
Expand All @@ -180,7 +195,7 @@ execute <- function(input, format, tempDir, libDir, dependencies, cwd, params, r
knit_root_dir = knit_root_dir,
params = params,
run_pandoc = FALSE,
envir = env
envir = globalenv()
)
knit_meta <- attr(render_output, "knit_meta")
files_dir <- attr(render_output, "files_dir")
Expand Down
25 changes: 23 additions & 2 deletions src/resources/rmd/hooks.R
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ knitr_hooks <- function(format, resourceDir, handledLanguages) {
}
delegating_output_hook = function(type, classes) {
delegating_hook(type, function(x, options) {
### START Knitr hack:
# since knitr 1.49, we can detect if output: asis
# was set by an R function itself (not cell option)
# We save the information for our other processing
# after output hook (i.e after sew method is called)
if (identical(options[["results"]], "asis")) {
.assignToQuartoToolsEnv("cell_options", list(asis_output = TRUE)) # nolint: object_usage_linter, line_length_linter.
}
### END

if (identical(options[["results"]], "asis") ||
isTRUE(options[["collapse"]])) {
x
Expand All @@ -182,6 +192,16 @@ knitr_hooks <- function(format, resourceDir, handledLanguages) {
# entire chunk
knit_hooks$chunk <- delegating_hook("chunk", function(x, options) {

## START knitr hack:
## catch knit_asis output from save output hook state
asis_output <- .getFromQuartoToolsEnv("cell_options")$asis_output # nolint: object_usage_linter, line_length_linter.
if (isTRUE(asis_output)) {
options[["results"]] <- "asis"
}
# chunk hook is called last and we can clean the cell storage
on.exit(.rmFromQuartoToolsEnv("cell_options"), add = TRUE) # nolint: object_usage_linter, line_length_linter.
## END

# Do nothing more for some specific chunk content -----

# Quarto language handler
Expand Down Expand Up @@ -400,7 +420,7 @@ knitr_hooks <- function(format, resourceDir, handledLanguages) {
# if there is a label, additional classes, a forwardAttr, or a cell.cap
# then the user is deemed to have implicitly overridden results = "asis"
# (as those features don't work w/o an enclosing div)
needCell <- isTRUE(nzchar(label)) ||
needCell <- isTRUE(nzchar(label)) ||
length(classes) > 1 ||
isTRUE(nzchar(forwardAttr)) ||
isTRUE(nzchar(cell.cap))
Expand All @@ -409,7 +429,8 @@ knitr_hooks <- function(format, resourceDir, handledLanguages) {
} else {
paste0(
options[["indent"]], "::: {",
labelId(label), paste(classes, collapse = " ") ,forwardAttr, "}\n", x, "\n", cell.cap ,
labelId(label), paste(classes, collapse = " "),
forwardAttr, "}\n", x, "\n", cell.cap,
options[["indent"]], ":::"
)
}
Expand Down
23 changes: 19 additions & 4 deletions src/resources/rmd/patch.R
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ wrap_asis_output <- function(options, x) {
if (identical(options[["html-table-processing"]], "none")) {
attrs <- paste(attrs, "html-table-processing=none")
}

# if this is an html table then wrap it further in ```{=html}
# (necessary b/c we no longer do this by overriding kable_html,
# which is in turn necessary to allow kableExtra to parse
Expand All @@ -112,9 +112,17 @@ wrap_asis_output <- function(options, x) {
!grepl('^<div class="kable-table">', x)) {
x <- paste0("`````{=html}\n", x, "\n`````")
}

# If asis output, don't include the output div
if (identical(options[["results"]], "asis")) return(x)

# If asis output, don't include the output div,
# unless a feature requiring it is used
# if there additional classes, some added attr,
# then the user is deemed to have implicitly overridden results = "asis"
# (as those features don't work w/o an enclosing div)
needCell <- length(classes) > 1 || isTRUE(nzchar(attrs)) # nolint: object_name_linter, line_length_linter.

if (identical(options[["results"]], "asis") && !needCell) {
return(x)
}

output_div(x, output_label_placeholder(options), classes, attrs)
}
Expand Down Expand Up @@ -152,6 +160,13 @@ if (utils::packageVersion("knitr") >= "1.32.8") {
} else {
knitr:::sew.knit_asis(x, options, ...)
}
## START knitr hack:
## catch knit_asis output from save output hook state
asis_output <- .getFromQuartoToolsEnv("cell_options")$asis_output # nolint: object_usage_linter, line_length_linter
if (isTRUE(asis_output)) {
options[["results"]] <- "asis"
}
## END

# if it's an html widget then it was already wrapped
# by add_html_caption
Expand Down
38 changes: 38 additions & 0 deletions tests/docs/smoke-all/inline-execution/inline-jupyter.md.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
_quarto: {}
title: inline jupyter expressions
toc-title: Table of contents
---

::: {.cell execution_count="1"}
``` {.python .cell-code}
from IPython.display import Markdown
x = 1
y = "foo"
z = '"foo"'
a = '"foo'
b = '*foo*'
c = "*foo*"
d = "'foo"
e = "1"
f = False
g = True
h = None
i = Markdown("*foo*")
```
:::

Here's inline output:

- 1
- foo
- "foo"
- "foo
- \*foo\*
- \*foo\*
- 'foo
- 1
- False
- True
- None
- *foo*
5 changes: 5 additions & 0 deletions tests/docs/smoke-all/inline-execution/inline-jupyter.qmd
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
---
title: "inline jupyter expressions"
format: markdown
engine: jupyter
_quarto:
tests:
markdown:
ensureSnapshotMatches: true
---

```{python}
Expand Down
38 changes: 38 additions & 0 deletions tests/docs/smoke-all/inline-execution/inline-knitr.md.snapshot
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
_quarto: {}
title: inline knitr expressions
toc-title: Table of contents
---

::: cell
``` {.r .cell-code}
x = 1
y = "foo"
z = '"foo"'
a = '"foo'
b = '*foo*'
c = "*foo*"
d = "'foo"
e = "1"
f = FALSE
g = TRUE
h = NULL
i = "*foo*"
class(i) <- c("character", "asis")
```
:::

Here's inline output:

- 1
- foo
- "foo"
- "foo
- \*foo\*
- \*foo\*
- 'foo
- 1
- FALSE
- TRUE
- NULL
- \*foo\*
36 changes: 19 additions & 17 deletions tests/docs/smoke-all/inline-execution/inline-knitr.qmd
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
---
title: "inline knitr expressions"
engine: knitr
_quarto: {}
title: inline knitr expressions
toc-title: Table of contents
---

```{r}
::: cell
``` {.r .cell-code}
x = 1
y = "foo"
z = '"foo"'
Expand All @@ -18,19 +20,19 @@ h = NULL
i = "*foo*"
class(i) <- c("character", "asis")
```
:::

Here's inline output:

- `{r} x`
- `{r} y`
- `{r} z`
- `{r} a`
- `{r} b`
- `{r} c`
- `{r} d`
- `{r} e`
- `{r} f`
- `{r} g`
- `{r} h`
- `{r} i`
Here's inline output:

- 1
- foo
- "foo"
- "foo
- \*foo\*
- \*foo\*
- 'foo
- 1
- FALSE
- TRUE
- NULL
- \*foo\*
Loading