├── .Rbuildignore ├── .gitignore ├── .travis.yml ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── NEWS.md ├── R ├── package.R ├── utils-pipe.R └── zzz.R ├── README.md ├── WrapRmd.Rproj ├── demo.gif ├── inst ├── rstudio │ └── addins.dcf └── test_strings.R ├── man ├── WrapRmd.Rd ├── knit_selection_addin.Rd ├── pipe.Rd ├── str_rmd_wrap.Rd └── wrap_rmd_addin.Rd ├── multi_paragraph.gif └── tests ├── testthat.R └── testthat ├── test-escaping.R ├── test-interleave.R ├── test-options.R └── test-wrap.R /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | demo.gif 4 | multi_paragraph.gif 5 | ^\.travis\.yml$ 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # Example code in package build process 9 | *-Ex.R 10 | 11 | # RStudio files 12 | .Rproj.user/ 13 | 14 | # produced vignettes 15 | vignettes/*.html 16 | vignettes/*.pdf 17 | 18 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 19 | .httr-oauth 20 | .Rproj.user 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: r 2 | r: 3 | - oldrel 4 | - release 5 | - devel 6 | sudo: false 7 | cache: packages 8 | warnings_are_errors: true 9 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: WrapRmd 2 | Title: RStudio Addin for Wrapping RMarkdown Paragraphs 3 | Version: 0.0.0.9006 4 | Authors@R: c( 5 | person("Tristan", "Mahr", , "tristan.mahr@wisc.edu", role = c("aut", "cre")), 6 | person("Shir", "Dekel", email = "shirdekel@yahoo.com.au", role = "ctb", comment = c(ORCID = "0000-0003-1773-2446")) 7 | ) 8 | Description: Provides an RStudio addin for wrapping paragraphs in an RMarkdown 9 | document without inserting linebreaks into spans of inline R code. 10 | License: MIT + file LICENSE 11 | Depends: 12 | R (>= 3.1.0) 13 | Imports: 14 | commonmark, 15 | rstudioapi, 16 | stringi, 17 | stringr (>= 1.0.0), 18 | magrittr, 19 | knitr 20 | Suggests: 21 | testthat 22 | LazyData: true 23 | RoxygenNote: 7.3.1 24 | Encoding: UTF-8 25 | Roxygen: list(markdown = TRUE) 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2016 2 | COPYRIGHT HOLDER: Tristan Mahr 3 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("%>%") 4 | export(knit_selection_addin) 5 | export(str_rmd_wrap) 6 | export(wrap_rmd_addin) 7 | import(stringr) 8 | importFrom(magrittr,"%>%") 9 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # WrapRmd 0.0.0.9006 2 | 3 | - LaTeX-looking words (matching `\[A-Za-z]{`) are unescaped if 4 | commonmark escaped the leading backslash. 5 | 6 | # WrapRmd 0.0.0.9005 7 | 8 | - Inline math (that starts and ends with a `$` character) is treated like R 9 | code during text-wrapping. It is protected from line breaks and having 10 | `\` escapes from being inserted. 11 | 12 | # WrapRmd 0.0.0.9004 13 | 14 | - Added support for avoiding inserting line breaks into `bookdown` 15 | cross-references (such as `Figure\ \@ref(fig:example)`). 16 | 17 | # WrapRmd 0.0.0.9003 18 | 19 | * Added a `NEWS.md` file to track changes to the package. 20 | -------------------------------------------------------------------------------- /R/package.R: -------------------------------------------------------------------------------- 1 | #' WrapRmd 2 | #' 3 | #' Provides an RStudio addin for wrapping paragraphs in an RMarkdown document 4 | #' without inserting linebreaks into spans of inline R code. 5 | #' 6 | #' @name WrapRmd 7 | #' @import stringr 8 | "_PACKAGE" 9 | 10 | 11 | #' Wrap text but don't insert lines breaks into inline R code 12 | #' 13 | #' Call this addin to wrap paragraphs in an R Markdown document. 14 | #' 15 | #' The maximum line width can be set via \code{options(WrapRmd.width)}. 16 | #' Further fine tuning can be done via options \dQuote{WrapRmd.smart} and 17 | #' \dQuote{WrapRmd.extensions}, which are passed down to 18 | #' \code{\link{markdown_commonmark}}. 19 | #' 20 | #' @export 21 | wrap_rmd_addin <- function() { 22 | context <- rstudioapi::getActiveDocumentContext() 23 | selection <- context$selection 24 | text <- unlist(selection)["text"] 25 | rstudioapi::insertText(str_rmd_wrap(text)) 26 | } 27 | 28 | 29 | #' Run selection through knitr and commonmark 30 | #' 31 | #' Call this addin to preview output of an R markdown selection. 32 | #' 33 | #' @export 34 | knit_selection_addin <- function() { 35 | context <- rstudioapi::getActiveDocumentContext() 36 | selection <- context$selection 37 | 38 | text <- unlist(selection)["text"] 39 | cat( 40 | commonmark::markdown_commonmark( 41 | knitr::knit(text = text, quiet = TRUE) 42 | ) 43 | ) 44 | } 45 | 46 | 47 | #' Wrap text but don't insert lines breaks into inline R code 48 | #' 49 | #' @param string a string to wrap 50 | #' @param width desired line width. Defaults to \code{options("WrapRmd.width")}. 51 | #' @return a wrapped copy of the string 52 | #' 53 | #' @details This function finds all inline R code spans in a string, replaces 54 | #' all non-word characters in the R spans with `"Q"`s, re-wraps the 55 | #' string, and restores the original inline R spans. 56 | #' 57 | #' This function preserves blanks lines between paragraphs. 58 | #' @export 59 | str_rmd_wrap <- function(string, width = getOption("WrapRmd.width", 80)) { 60 | # Assume paragraphs are separated by [newline][optional spaces][newline]. 61 | re_paragraph_sep <- "(\\n\\s*\\n)" 62 | 63 | # Need to preserve blank lines at start and end 64 | re_blanks_at_start <- "(^\\s*\\n)" 65 | re_blanks_at_close <- "(\\s*\\n$)" 66 | 67 | re_start_or_sep_or_close <- paste( 68 | re_blanks_at_start, 69 | re_paragraph_sep, 70 | re_blanks_at_close, 71 | sep = "|" 72 | ) 73 | 74 | # Find paragraph separations 75 | paragraph_seps <- string %>% 76 | str_extract_all(re_start_or_sep_or_close) %>% 77 | unlist() 78 | 79 | # Split at those points to get paragraphs. 80 | paragraphs <- string %>% 81 | str_split(re_start_or_sep_or_close) %>% 82 | unlist() %>% 83 | unname() 84 | 85 | # Wrap each paragraph. 86 | paragraphs <- Map(function(...) str_rmd_wrap_one(..., width), paragraphs) %>% 87 | unlist() %>% 88 | unname() 89 | 90 | str_interleave(paragraphs, paragraph_seps) 91 | } 92 | 93 | 94 | # Interleave two vectors of strings 95 | str_interleave <- function(strings, interleaves) { 96 | if (length(strings) == 1) return(strings) 97 | stopifnot(length(strings) - length(interleaves) == 1) 98 | 99 | # Pop the first string off. Concatenate pairs of interleaves and strings. 100 | start <- utils::head(strings, 1) 101 | left <- utils::tail(strings, -1) 102 | body <- paste0(interleaves, left, collapse = "") 103 | 104 | # Reattach head 105 | paste0(start, body) 106 | } 107 | 108 | 109 | str_rmd_wrap_one <- function(string, width) { 110 | output <- string 111 | 112 | # Patterns to protect from line breaks 113 | re_inline_code <- "(`r)( )([^`]+`)" 114 | re_inline_math <- "((?!=[$])[$][^$]+[$])" 115 | re_cross_references <- "(\\w+\\\\ \\\\@ref\\(.*?\\))" 116 | 117 | # Locate the patterns 118 | inline_code <- string %>% 119 | str_extract_all(re_inline_code) %>% 120 | unlist() 121 | 122 | inline_math <- string %>% 123 | str_extract_all(re_inline_math) %>% 124 | unlist() 125 | 126 | cross_references <- string %>% 127 | str_extract_all(re_cross_references) %>% 128 | unlist() 129 | 130 | # Substrings that match the patterns 131 | wrap_ignore <- c( 132 | inline_code, 133 | inline_math, 134 | cross_references 135 | ) 136 | 137 | # Just wrap if no substrings need to be protected 138 | if (length(wrap_ignore) == 0) { 139 | return(md_wrap(string, width)) 140 | } 141 | 142 | # Make protected strings spans into long words 143 | 144 | # Replace all nonwords and _'s with Qs 145 | re_nonword <- "\\W|_" 146 | spaceless_wrap_ignore <- str_replace_all(wrap_ignore, re_nonword, "Q") 147 | 148 | for (i in seq_along(wrap_ignore)) { 149 | output <- str_replace( 150 | string = output, 151 | pattern = coll(wrap_ignore[i]), 152 | replacement = spaceless_wrap_ignore[i] 153 | ) 154 | } 155 | 156 | # Wrap the text now that the strings are protected 157 | output <- md_wrap(output, width) 158 | 159 | # Put original versions of protected strings back in 160 | for (i in seq_along(wrap_ignore)) { 161 | output <- stringi::stri_replace_first_coll( 162 | str = output, 163 | pattern = md_wrap(spaceless_wrap_ignore[i], width), 164 | replacement = wrap_ignore[i] 165 | ) 166 | } 167 | 168 | output 169 | } 170 | 171 | 172 | md_wrap <- function( 173 | string, 174 | width, 175 | hardbreaks = FALSE, 176 | smart = getOption("WrapRmd.smart", FALSE), 177 | normalize = FALSE, 178 | extensions = getOption("WrapRmd.extensions", TRUE) 179 | ) { 180 | raw_string <- string 181 | 182 | wrapped <- string %>% 183 | commonmark::markdown_commonmark( 184 | hardbreaks = hardbreaks, 185 | extensions = extensions, 186 | normalize = normalize, 187 | smart = smart, 188 | width = width 189 | ) %>% 190 | str_replace("\\n$", "") 191 | 192 | wrapped %>% 193 | unescape(raw_string, stringr::fixed("[")) %>% 194 | unescape(raw_string, stringr::fixed("]")) %>% 195 | unescape(raw_string, stringr::fixed("!")) %>% 196 | unescape_latex_words(raw_string) %>% 197 | restore_escape(raw_string, "\\@ref") 198 | } 199 | 200 | restore_escape <- function(string, raw_string, target) { 201 | # Find any instances of the unescaped form in the text 202 | location_in_raw <- str_locate_all(raw_string, unesc(target))[[1]] %>% 203 | as.data.frame() 204 | 205 | # Check each use of see if it was escaped in the raw string 206 | for (i in seq_along(location_in_raw$start)) { 207 | char_loc <- location_in_raw$start[[i]] 208 | char_end <- location_in_raw$end[[i]] 209 | escaped <- substr(raw_string, char_loc - 1, char_end) == target 210 | location_in_raw$escaped[[i]] <- escaped 211 | } 212 | 213 | # Find the target in the wrapped string 214 | location_in_wrapped <- str_locate_all(string, unesc(target))[[1]] %>% 215 | as.data.frame() 216 | 217 | # Check for corruptions in the wrapped text 218 | for (i in seq_along(location_in_wrapped[, 1])) { 219 | char_loc <- location_in_wrapped$start[[i]] 220 | char_end <- location_in_wrapped$end[[i]] 221 | escaped_in_wrapped <- substr(string, char_loc - 1, char_end) == target 222 | escaped_in_raw <- location_in_raw$escaped[[i]] 223 | 224 | # If escape removed, add it and update target locations 225 | if (!escaped_in_wrapped & escaped_in_raw) { 226 | str_sub(string, char_loc, char_end) <- target 227 | location_in_wrapped <- str_locate_all(string, unesc(target))[[1]] %>% 228 | as.data.frame() 229 | } 230 | } 231 | 232 | string 233 | } 234 | 235 | unescape_latex_words <- function(string, raw_string) { 236 | latex_words <- raw_string %>% 237 | stringr::str_extract_all("\\\\[A-Za-z]+\\{") %>% 238 | unlist() %>% 239 | unique() 240 | 241 | for (word in latex_words) { 242 | string <- unescape(string, raw_string, fixed(word)) 243 | } 244 | string 245 | 246 | } 247 | 248 | unescape <- function(string, raw_string, target) { 249 | esc_target <- if (inherits(target, "stringr_fixed")) { 250 | target 251 | } else { 252 | esc(target) 253 | } 254 | 255 | # Find the target in the original string 256 | location_in_raw <- raw_string %>% 257 | str_locate_all(esc_target) %>% 258 | getElement(1) %>% 259 | as.data.frame() 260 | 261 | # Check each use of target to see if it was escaped in the raw string 262 | for (i in seq_along(location_in_raw$start)) { 263 | char_loc <- location_in_raw$start[[i]] 264 | char_loc_end <- location_in_raw$end[[i]] 265 | escaped <- substr(raw_string, char_loc - 1, char_loc_end) == esc(esc_target) 266 | location_in_raw$escaped[[i]] <- escaped 267 | } 268 | 269 | # Find the target in the wrapped string 270 | location_in_wrapped <- str_locate_all(string, esc_target)[[1]] 271 | 272 | # Check for escapes in the wrapped text 273 | for (i in seq_along(location_in_wrapped[, 1])) { 274 | char_loc <- location_in_wrapped[i, 1] 275 | char_loc_end <- location_in_wrapped[i, 2] 276 | 277 | escaped_in_wrapped <- substr(string, char_loc - 1, char_loc_end) == esc(esc_target) 278 | escaped_in_raw <- location_in_raw$escaped[[i]] 279 | 280 | # If an escape was added, remove it and update target locations 281 | if (escaped_in_wrapped & !escaped_in_raw) { 282 | str_sub(string, char_loc - 1, char_loc_end) <- target 283 | location_in_wrapped <- str_locate_all(string, esc_target)[[1]] 284 | } 285 | } 286 | 287 | string 288 | } 289 | 290 | 291 | esc <- function(x) { 292 | paste0("\\", x) 293 | } 294 | 295 | unesc <- function(x) { 296 | str_replace(x, "^\\\\", "") 297 | } 298 | -------------------------------------------------------------------------------- /R/utils-pipe.R: -------------------------------------------------------------------------------- 1 | #' Pipe operator 2 | #' 3 | #' See \code{magrittr::\link[magrittr]{\%>\%}} for details. 4 | #' 5 | #' @name %>% 6 | #' @rdname pipe 7 | #' @keywords internal 8 | #' @export 9 | #' @importFrom magrittr %>% 10 | #' @usage lhs \%>\% rhs 11 | NULL 12 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) { 2 | op <- options() 3 | op_WrapRmd <- list( 4 | WrapRmd.width = 80, 5 | WrapRmd.smart = FALSE, 6 | WrapRmd.extensions = TRUE 7 | ) 8 | toset <- !(names(op_WrapRmd) %in% names(op)) 9 | if (any(toset)) options(op_WrapRmd[toset]) 10 | 11 | invisible() 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WrapRmd 2 | 3 | An [RStudio Addin](https://rstudio.github.io/rstudioaddins/) to wrap paragraphs 4 | of RMarkdown text without inserting line breaks into inline R code. 5 | 6 | ## Installation 7 | 8 | You can install the plain version WrapRmd from GitHub with: 9 | 10 | ``` r 11 | # install.packages("devtools") 12 | devtools::install_github("tjmahr/WrapRmd") 13 | ``` 14 | 15 | This package used the 16 | [commonmark](https://cran.r-project.org/web/packages/commonmark/index.html) 17 | package to wrap and reformat to markdown text. Using commonmark means that it 18 | can wrap links and markdown lists. The package does some additional work to 19 | handle inline R Markdown. 20 | 21 | ## Overview 22 | 23 | Here is some nice looking RMarkdown: 24 | 25 | ``` 26 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a `r max(iris$Sepal.Length)`, viverra nisl at, luctus ante = `r length(letters) * 2 + 100`. 27 | ``` 28 | 29 | You highlight the text, and hit `Ctrl/Cmd + Shift + /` to wrap the text and get: 30 | 31 | ``` 32 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a `r 33 | max(iris$Sepal.Length)`, viverra nisl at, luctus ante = `r length(letters) * 2 + 34 | 100`. 35 | ``` 36 | 37 | This RStudio Addin wraps text, but doesn't insert line breaks into inline R 38 | code, yielding: 39 | 40 | ``` 41 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum 42 | a `r max(iris$Sepal.Length)`, viverra nisl at, luctus ante = 43 | `r length(letters) * 2 + 100`. 44 | ``` 45 | 46 | ![An animation of the above](demo.gif) 47 | 48 | ## Notes 49 | 50 | Then go to Tools > Addins in RStudio to select and configure addins. I've mapped 51 | this one addin to the shortcut `Ctrl + Shift + Alt + /`. 52 | 53 | The package wraps lines using a maximum width set by `options("WrapRmd.width")` 54 | which currently defaults to `80` characters. 55 | 56 | It should work on multiple paragraphs: 57 | 58 | ![Animation of wrapping paragraphs separately](multi_paragraph.gif) 59 | -------------------------------------------------------------------------------- /WrapRmd.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: No 4 | SaveWorkspace: No 5 | AlwaysSaveHistory: No 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: XeLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | 18 | BuildType: Package 19 | PackageUseDevtools: Yes 20 | PackageInstallArgs: --no-multiarch --with-keep.source 21 | PackageRoxygenize: rd,collate,namespace,vignette 22 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjmahr/WrapRmd/b36c24caefd53d002e5233941085b42e6180d0fa/demo.gif -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Wrap Rmd 2 | Description: Wrap selected R Markdown text but don't insert lines breaks into inline R code 3 | Binding: wrap_rmd_addin 4 | Interactive: false 5 | 6 | Name: Knit Selection 7 | Description: Run selection through knitr/commonmark to preview results. 8 | Binding: knit_selection_addin 9 | Interactive: false 10 | -------------------------------------------------------------------------------- /inst/test_strings.R: -------------------------------------------------------------------------------- 1 | library("stringr") 2 | 3 | no_code <- "regular words on a line" 4 | str_rmd_wrap(no_code) %>% cat 5 | 6 | # Simple case 7 | text <- " 8 | `r hello` and `r 1 + 1` and `r 1 + b + b + c` and drop a line right here `r maybe_here` `r goodbye` 9 | " 10 | str_rmd_wrap(text) %>% cat 11 | 12 | 13 | 14 | 15 | string <- paragraphs_sttsts <- 16 | " 17 | text in p1. 18 | extra text in p1. 19 | 20 | text in p2. 21 | " 22 | 23 | 24 | 25 | 26 | # Eventually, make a way to associate strings with their expected paragraph 27 | # wrapping... 28 | 29 | # "text\n\ntext" would have an expected wrapping of "psp" (a paragraph, a 30 | # paragraph separation, a paragraph) 31 | 32 | # test_string <- function(string, expected_wrapping) { 33 | # list(string = string, expected = expected_wrapping, received = f(string)) 34 | # } 35 | # 36 | # get_paragraph_wrapping <- function(string) { 37 | # string %>% str_split() 38 | # } 39 | 40 | -------------------------------------------------------------------------------- /man/WrapRmd.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/package.R 3 | \docType{package} 4 | \name{WrapRmd} 5 | \alias{WrapRmd-package} 6 | \alias{WrapRmd} 7 | \title{WrapRmd} 8 | \description{ 9 | Provides an RStudio addin for wrapping paragraphs in an RMarkdown document 10 | without inserting linebreaks into spans of inline R code. 11 | } 12 | \author{ 13 | \strong{Maintainer}: Tristan Mahr \email{tristan.mahr@wisc.edu} 14 | 15 | Other contributors: 16 | \itemize{ 17 | \item Shir Dekel \email{shirdekel@yahoo.com.au} (\href{https://orcid.org/0000-0003-1773-2446}{ORCID}) [contributor] 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /man/knit_selection_addin.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/package.R 3 | \name{knit_selection_addin} 4 | \alias{knit_selection_addin} 5 | \title{Run selection through knitr and commonmark} 6 | \usage{ 7 | knit_selection_addin() 8 | } 9 | \description{ 10 | Call this addin to preview output of an R markdown selection. 11 | } 12 | -------------------------------------------------------------------------------- /man/pipe.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/utils-pipe.R 3 | \name{\%>\%} 4 | \alias{\%>\%} 5 | \title{Pipe operator} 6 | \usage{ 7 | lhs \%>\% rhs 8 | } 9 | \description{ 10 | See \code{magrittr::\link[magrittr]{\%>\%}} for details. 11 | } 12 | \keyword{internal} 13 | -------------------------------------------------------------------------------- /man/str_rmd_wrap.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/package.R 3 | \name{str_rmd_wrap} 4 | \alias{str_rmd_wrap} 5 | \title{Wrap text but don't insert lines breaks into inline R code} 6 | \usage{ 7 | str_rmd_wrap(string, width = getOption("WrapRmd.width", 80)) 8 | } 9 | \arguments{ 10 | \item{string}{a string to wrap} 11 | 12 | \item{width}{desired line width. Defaults to \code{options("WrapRmd.width")}.} 13 | } 14 | \value{ 15 | a wrapped copy of the string 16 | } 17 | \description{ 18 | Wrap text but don't insert lines breaks into inline R code 19 | } 20 | \details{ 21 | This function finds all inline R code spans in a string, replaces 22 | all non-word characters in the R spans with \code{"Q"}s, re-wraps the 23 | string, and restores the original inline R spans. 24 | 25 | This function preserves blanks lines between paragraphs. 26 | } 27 | -------------------------------------------------------------------------------- /man/wrap_rmd_addin.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/package.R 3 | \name{wrap_rmd_addin} 4 | \alias{wrap_rmd_addin} 5 | \title{Wrap text but don't insert lines breaks into inline R code} 6 | \usage{ 7 | wrap_rmd_addin() 8 | } 9 | \description{ 10 | Call this addin to wrap paragraphs in an R Markdown document. 11 | } 12 | \details{ 13 | The maximum line width can be set via \code{options(WrapRmd.width)}. 14 | Further fine tuning can be done via options \dQuote{WrapRmd.smart} and 15 | \dQuote{WrapRmd.extensions}, which are passed down to 16 | \code{\link{markdown_commonmark}}. 17 | } 18 | -------------------------------------------------------------------------------- /multi_paragraph.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tjmahr/WrapRmd/b36c24caefd53d002e5233941085b42e6180d0fa/multi_paragraph.gif -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(WrapRmd) 3 | 4 | test_check("WrapRmd") 5 | -------------------------------------------------------------------------------- /tests/testthat/test-escaping.R: -------------------------------------------------------------------------------- 1 | test_that("does not escape !, [, ]", { 2 | string <- "This is a confidence interval [0, 1], [0, 1]!" 3 | expect_equal(str_rmd_wrap(string), string) 4 | }) 5 | 6 | test_that("does not escape !, [, ] unless they are already escaped", { 7 | string <- "This is a confidence interval [0, 1]! \\[0, 1\\]\\!" 8 | expect_equal(str_rmd_wrap(string), string) 9 | }) 10 | 11 | test_that("does not unescape \\@ref", { 12 | string <- "Table \\@ref(tab:counts) shows counts; @ref(tab:counts) won't." 13 | expect_equal(str_rmd_wrap(string), string) 14 | }) 15 | 16 | 17 | test_that("escape \ in math mode", { 18 | string <- "a continuous parameter $\\beta$ with no $\\theta$s" 19 | expect_equal(str_rmd_wrap(string), string) 20 | }) 21 | 22 | 23 | test_that("no escape LaTeX", { 24 | string <- 25 | "Someone said something [@bibref-to-article], and I am doing something in 26 | \\experiment{1} to test it." 27 | 28 | expect_equal(str_rmd_wrap(string, 80), string) 29 | }) 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/testthat/test-interleave.R: -------------------------------------------------------------------------------- 1 | test_that("interleaving strings", { 2 | # sneak private function out 3 | str_interleave <- WrapRmd:::str_interleave 4 | 5 | # 2:1 interleave 6 | expect_equal( 7 | str_interleave(c("s1", "s2"), c("_l1_")), 8 | "s1_l1_s2") 9 | 10 | # 3:2 interleave 11 | expect_equal( 12 | str_interleave(c("s1", "s2", "s3"), c("_l1_", "_l2_")), 13 | "s1_l1_s2_l2_s3") 14 | 15 | # 1:X -- Ignore interleavers if just one string 16 | expect_equal(str_interleave(c("s1"), c("_l1_")), "s1") 17 | expect_equal(str_interleave(c("s1"), character(0)), "s1") 18 | 19 | # 3:1 -- error 20 | expect_error(str_interleave(c("s1", "s2", "s3"), c("_l1_"))) 21 | }) 22 | -------------------------------------------------------------------------------- /tests/testthat/test-options.R: -------------------------------------------------------------------------------- 1 | test_that("wrapping can use options()", { 2 | # Default is 80 3 | long_paragraph <- 4 | "`r hello` and `r 1 + 1` and `r 1 + b + b + c`---and drop a line right here `r maybe_here` `r goodbye`" 5 | 6 | wrap_80 <- 7 | "`r hello` and `r 1 + 1` and `r 1 + b + b + c`---and drop a line right here 8 | `r maybe_here` `r goodbye`" 9 | 10 | expect_equal(str_rmd_wrap(long_paragraph), wrap_80) 11 | 12 | options(WrapRmd.width = 40) 13 | 14 | wrap_40 <- 15 | "`r hello` and `r 1 + 1` and 16 | `r 1 + b + b + c`---and drop a line 17 | right here `r maybe_here` `r goodbye`" 18 | 19 | expect_equal(str_rmd_wrap(long_paragraph), wrap_40) 20 | 21 | options(WrapRmd.width = 80) 22 | }) 23 | -------------------------------------------------------------------------------- /tests/testthat/test-wrap.R: -------------------------------------------------------------------------------- 1 | test_that("basic wrapping", { 2 | no_code <- "regular words on a line" 3 | expect_equal( 4 | str_rmd_wrap(no_code), 5 | no_code 6 | ) 7 | 8 | long_paragraph <- "`r hello` and `r 1 + 1` and `r 1 + b + b + c` and drop a line right here `r maybe_here` `r goodbye`" 9 | 10 | long_paragraph_wrap <- 11 | "`r hello` and `r 1 + 1` and `r 1 + b + b + c` and drop a line right here 12 | `r maybe_here` `r goodbye`" 13 | 14 | expect_equal(str_rmd_wrap(long_paragraph), long_paragraph_wrap) 15 | 16 | # The lines from the gif 17 | gif_lines <- "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a `r max(iris$Sepal.Length)`, viverra nisl at, luctus ante = `r length(letters) * 2 + 100`." 18 | 19 | gif_lines_wrap <- 20 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a 21 | `r max(iris$Sepal.Length)`, viverra nisl at, luctus ante = 22 | `r length(letters) * 2 + 100`." 23 | 24 | expect_equal(str_rmd_wrap(gif_lines), gif_lines_wrap) 25 | }) 26 | 27 | 28 | test_that("does not break links (#7)", { 29 | string <- " 30 | It's very easy to make some words **bold** and a [link to Google!](http://google.com)! 31 | " 32 | 33 | string2 <- " 34 | It's very easy to make some words **bold** and a [link to 35 | Google!](http://google.com)! 36 | " 37 | 38 | expect_equal(str_rmd_wrap(string), string2) 39 | }) 40 | 41 | test_that("does not break cross-references (#16)", { 42 | string <- " 43 | Here is some inline code `r hello` and cross-references to Figure\\ \\@ref(fig:test-figure). Also Table\\ \\@ref(tab:test-table) and\\ \\@ref(fig:test-figure). 44 | " 45 | 46 | string2 <- " 47 | Here is some inline code `r hello` and cross-references to 48 | Figure\\ \\@ref(fig:test-figure). Also Table\\ \\@ref(tab:test-table) 49 | and\\ \\@ref(fig:test-figure). 50 | " 51 | 52 | expect_equal(str_rmd_wrap(string), string2) 53 | }) 54 | 55 | 56 | test_that("preserve paragraphs", { 57 | paragraph_preserving <- 58 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a `r max(iris$Sepal.Length)`, viverra nisl at, luctus ante = `r length(letters) * 2 + 100`. 59 | 60 | `r hello` and `r 1 + 1` and `r 1 + b + b + c` and drop a line right here `r maybe_here` `r goodbye` 61 | 62 | 63 | hello" 64 | 65 | paragraph_preserving_wrapped <- 66 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum a 67 | `r max(iris$Sepal.Length)`, viverra nisl at, luctus ante = 68 | `r length(letters) * 2 + 100`. 69 | 70 | `r hello` and `r 1 + 1` and `r 1 + b + b + c` and drop a line right here 71 | `r maybe_here` `r goodbye` 72 | 73 | 74 | hello" 75 | 76 | expect_equal(str_rmd_wrap(paragraph_preserving), paragraph_preserving_wrapped) 77 | 78 | # leading blank lines are preserved 79 | paragraphs_ssttst <- 80 | " 81 | 82 | text in p1. 83 | extra text in p1. 84 | 85 | text in p2." 86 | 87 | paragraphs_ssttst_wrapped <- 88 | " 89 | 90 | text in p1. extra text in p1. 91 | 92 | text in p2." 93 | 94 | expect_equal(str_rmd_wrap(paragraphs_ssttst), paragraphs_ssttst_wrapped) 95 | 96 | # trailing blank lines are preserved 97 | paragraphs_sttstss <- 98 | " 99 | text in p1. 100 | extra text in p1. 101 | 102 | text in p2. 103 | 104 | " 105 | 106 | paragraphs_sttstss_wrapped <- 107 | " 108 | text in p1. extra text in p1. 109 | 110 | text in p2. 111 | 112 | " 113 | 114 | expect_equal(str_rmd_wrap(paragraphs_sttstss), paragraphs_sttstss_wrapped) 115 | 116 | 117 | # trailing blank lines are preserved 118 | paragraphs_sttsts <- 119 | " 120 | text in p1. 121 | extra text in p1. 122 | 123 | text in p2. 124 | " 125 | 126 | paragraphs_sttsts_wrapped <- 127 | " 128 | text in p1. extra text in p1. 129 | 130 | text in p2. 131 | " 132 | expect_equal(str_rmd_wrap(paragraphs_sttsts), paragraphs_sttsts_wrapped) 133 | }) 134 | 135 | test_that("set column width directly", { 136 | long_paragraph <- paste(sprintf("a%02d", 3 + 4 * 0:20) , collapse = " ") 137 | 138 | long_paragraph_wrap_10 <- 139 | "a03 a07 140 | a11 a15 141 | a19 a23 142 | a27 a31 143 | a35 a39 144 | a43 a47 145 | a51 a55 146 | a59 a63 147 | a67 a71 148 | a75 a79 149 | a83" 150 | long_paragraph_wrap_40 <- 151 | "a03 a07 a11 a15 a19 a23 a27 a31 a35 a39 152 | a43 a47 a51 a55 a59 a63 a67 a71 a75 a79 153 | a83" 154 | long_paragraph_wrap_default <- 155 | "a03 a07 a11 a15 a19 a23 a27 a31 a35 a39 a43 a47 a51 a55 a59 a63 a67 a71 a75 a79 156 | a83" 157 | expect_equal(str_rmd_wrap(long_paragraph, 10), long_paragraph_wrap_10) 158 | expect_equal(str_rmd_wrap(long_paragraph, 40), long_paragraph_wrap_40) 159 | expect_equal(str_rmd_wrap(long_paragraph), long_paragraph_wrap_default) 160 | }) 161 | --------------------------------------------------------------------------------