├── .Rbuildignore ├── LICENSE ├── NAMESPACE ├── inst └── rstudio │ └── addins.dcf ├── R ├── clean_up.R ├── rstudio.R └── rmd_help.R ├── man ├── rs_rmd_help.Rd └── rmd_help.Rd ├── DESCRIPTION ├── LICENSE.md ├── README.md └── README.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^README\.Rmd$ 2 | ^LICENSE\.md$ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2021 2 | COPYRIGHT HOLDER: rmdocs authors 3 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export("?") 4 | export(help) 5 | export(rmd_help) 6 | export(rs_rmd_help) 7 | -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: RMarkdown help() on object 2 | Description: help() as Rmd for word at cursor or selection 3 | Binding: rs_rmd_help 4 | Interactive: false 5 | -------------------------------------------------------------------------------- /R/clean_up.R: -------------------------------------------------------------------------------- 1 | .onLoad <- function(libname, pkgname) { 2 | backports::import(pkgname, "dir.exists") 3 | backports::import(pkgname, "R_user_dir", force = TRUE) 4 | pkg_user_dir <- get_pkg_user_dir() 5 | file_info_df <- fs::file_info(list.files(pkg_user_dir, full.names = TRUE)) 6 | # Remove all files from previous days 7 | old_files <- as.numeric(Sys.Date() - as.Date(file_info_df$birth_time)) > 0 8 | unlink(file_info_df[old_files,]$path) 9 | } 10 | -------------------------------------------------------------------------------- /man/rs_rmd_help.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rstudio.R 3 | \name{rs_rmd_help} 4 | \alias{rs_rmd_help} 5 | \title{RMarkdown help() on cursor word or selection} 6 | \usage{ 7 | rs_rmd_help() 8 | } 9 | \value{ 10 | nothing. Opens document as side effect 11 | } 12 | \description{ 13 | Analagous to the default \code{F1} shortcut in RStudio, except it opens the help 14 | for the thing the cursor is on, or is selected, in Rmd format. 15 | } 16 | \details{ 17 | Bind the 'RMarkdown help() on object addin to a keyboard shortcut to use 18 | this. 19 | } 20 | -------------------------------------------------------------------------------- /man/rmd_help.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rmd_help.R 3 | \name{rmd_help} 4 | \alias{rmd_help} 5 | \title{Browse a help file as an Rmd} 6 | \usage{ 7 | rmd_help(topic, package = NULL) 8 | } 9 | \arguments{ 10 | \item{topic}{bare symbol to search for help on. pkg::func syntax is supported and if used \code{package} is ignored.} 11 | 12 | \item{package}{package name to resolve symbol in} 13 | } 14 | \value{ 15 | nothing. Opens help as side effect. 16 | } 17 | \description{ 18 | A drop-in replacement for \code{help()} that opens the help file as Rmd. 19 | } 20 | \details{ 21 | You're better off binding this to a key or using the RStudio addin. See 22 | README. 23 | } 24 | \examples{ 25 | \dontrun{ 26 | rmd_help(help) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: rmdocs 2 | Title: Open R help files in RMarkdown format 3 | Version: 0.1.3 4 | Authors@R: 5 | person(given = "Miles", 6 | family = "McBain", 7 | role = c("aut", "cre"), 8 | email = "miles.mcbain@gmail.com", 9 | comment = c(ORCID = "YOUR-ORCID-ID")) 10 | Description: Open R Help files in RMarkdown format in a new editor tab. 11 | Includes an RStudio addin to do this for the current selection or word cursor is on. 12 | Compatible with RStudio and VSCode. 13 | License: MIT + file LICENSE 14 | Encoding: UTF-8 15 | LazyData: true 16 | Roxygen: list(markdown = TRUE) 17 | RoxygenNote: 7.1.1 18 | Imports: 19 | backports, 20 | fs, 21 | readr, 22 | rstudioapi, 23 | tools, 24 | utils 25 | Depends: R (>= 3.0.0) 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2021 rmdocs authors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # rmdocs 5 | 6 | 7 | 8 | 9 | 10 | Browse help files as RMarkdown source documents. 11 | 12 | ## Installation 13 | 14 | ``` r 15 | # install.packages("devtools") 16 | devtools::install_github("milesmcbain/rmdocs") 17 | ``` 18 | 19 | ## Usage 20 | 21 | ``` r 22 | rmdocs::rmd_help(help) 23 | ``` 24 | 25 | Use option `rmd_doc_width` to control the text width of the 26 | documentation output. Defaults to 80. 27 | 28 | ### Replacing `help` and `?` 29 | 30 | So in love with Rmd help that you can’t imagine it any other way? I HEAR 31 | you. If you call `library(rmdocs)` Rmarkdown generating replacements for 32 | `utils::help` and `utils::?` will be loaded into your session. Avoid the 33 | `library` call if you don’t want that. 34 | 35 | ### VSCode 36 | 37 | In `keybindings.json`, assuming you have `{rstudioapi}` emulation 38 | enabled: 39 | 40 | ``` json 41 | [ 42 | { 43 | "description": "Rmd helpfile for object", 44 | "key": "ctrl+shift+h", 45 | "command": "r.runCommandWithSelectionOrWord", 46 | "args": "rmddocs::rs_rmd_help()", 47 | "when": "editorTextFocus" 48 | }, 49 | ] 50 | ``` 51 | 52 | ### RStudio 53 | 54 | [Bind the 55 | addin](https://www.infoworld.com/article/3327573/do-more-with-r-rstudio-addins-and-keyboard-shortcuts.html) 56 | ‘Rmarkdown help() on object’ to a choice keyboard shortcut and away you 57 | go. 58 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r, include = FALSE} 8 | knitr::opts_chunk$set( 9 | collapse = TRUE, 10 | comment = "#>", 11 | fig.path = "man/figures/README-", 12 | out.width = "100%" 13 | ) 14 | ``` 15 | 16 | # rmdocs 17 | 18 | 19 | 20 | 21 | Browse help files as RMarkdown source documents. 22 | 23 | ## Installation 24 | 25 | ``` r 26 | # install.packages("devtools") 27 | devtools::install_github("milesmcbain/rmdocs") 28 | ``` 29 | ## Usage 30 | 31 | ```{r example, eval = FALSE} 32 | rmdocs::rmd_help(help) 33 | ``` 34 | 35 | Use option `rmd_doc_width` to control the text width of the documentation 36 | output. Defaults to 80. 37 | 38 | ### Replacing `help` and `?` 39 | 40 | So in love with Rmd help that you can't imagine it any other way? I HEAR you. 41 | If you call `library(rmdocs)` Rmarkdown generating replacements for 42 | `utils::help` and `utils::?` will be loaded into your session. Avoid the `library` call if you don't want that. 43 | 44 | ### VSCode 45 | 46 | In `keybindings.json`, assuming you have `{rstudioapi}` emulation enabled: 47 | 48 | ```{json} 49 | [ 50 | { 51 | "description": "Rmd helpfile for object", 52 | "key": "ctrl+shift+h", 53 | "command": "r.runCommandWithSelectionOrWord", 54 | "args": "rmddocs::rs_rmd_help()", 55 | "when": "editorTextFocus" 56 | }, 57 | ] 58 | ``` 59 | 60 | ### RStudio 61 | 62 | [Bind the addin](https://www.infoworld.com/article/3327573/do-more-with-r-rstudio-addins-and-keyboard-shortcuts.html) 'Rmarkdown help() on object' to a choice keyboard shortcut and away you go. -------------------------------------------------------------------------------- /R/rstudio.R: -------------------------------------------------------------------------------- 1 | #' RMarkdown help() on cursor word or selection 2 | #' 3 | #' Analagous to the default `F1` shortcut in RStudio, except it opens the help 4 | #' for the thing the cursor is on, or is selected, in Rmd format. 5 | #' 6 | #' Bind the 'RMarkdown help() on object addin to a keyboard shortcut to use 7 | #' this. 8 | #' 9 | #' @return nothing. Opens document as side effect 10 | #' @export 11 | rs_rmd_help <- function() { 12 | word_or_selection <- get_word_or_selection() 13 | help_call <- bquote(rmd_help(.(as.symbol(word_or_selection)))) 14 | eval(help_call) 15 | } 16 | 17 | get_word_or_selection <- function() { 18 | context <- rstudioapi::getActiveDocumentContext() 19 | current_selection <- rstudioapi::primary_selection(context) 20 | if (!is_zero_length_selection(current_selection)) { 21 | return(current_selection$text) 22 | } 23 | cursor_line <- get_cursor_line(context, current_selection) 24 | cursor_col <- get_cursor_col(current_selection) 25 | symbol_locations <- get_symbol_locations(cursor_line) 26 | cursor_symbol <- 27 | symbol_locations[symbol_locations$start <= cursor_col & 28 | symbol_locations$end >= cursor_col, ] 29 | if (nrow(cursor_symbol) == 0) { 30 | return(character(0)) 31 | } 32 | substring(cursor_line, cursor_symbol$start, cursor_symbol$end) 33 | } 34 | 35 | is_zero_length_selection <- function(selection) { 36 | all(selection$range$start == selection$range$end) 37 | } 38 | 39 | get_cursor_line <- function(context, current_selection) { 40 | line_num <- current_selection$range$start["row"] 41 | context$contents[[line_num]] 42 | } 43 | 44 | get_cursor_col <- function(current_selection) { 45 | current_selection$range$start["column"] 46 | } 47 | 48 | get_symbol_locations <- function(code_line) { 49 | matches <- gregexpr("(?:[A-Za-z]|[.][A-Za-z])[A-Za-z0-9_.]+(?::{2,3}(?:[A-Za-z]|[.][A-Za-z])[A-Za-z0-9_.]+)?", 50 | code_line, 51 | perl = TRUE 52 | ) 53 | match_df <- data.frame( 54 | start = c(matches[[1]]), 55 | length = attr(matches[[1]], "match.length") 56 | ) 57 | match_df$end <- match_df$start + match_df$length - 1 58 | match_df 59 | } -------------------------------------------------------------------------------- /R/rmd_help.R: -------------------------------------------------------------------------------- 1 | #' Browse a help file as an Rmd 2 | #' 3 | #' A drop-in replacement for `help()` that opens the help file as Rmd. 4 | #' 5 | #' You're better off binding this to a key or using the RStudio addin. See 6 | #' README. 7 | #' 8 | #' @param topic bare symbol to search for help on. pkg::func syntax is supported and if used `package` is ignored. 9 | #' @param package package name to resolve symbol in 10 | #' @return nothing. Opens help as side effect. 11 | #' @export 12 | #' @examples 13 | #' \dontrun{ 14 | #' rmd_help(help) 15 | #' } 16 | rmd_help <- function(topic, package = NULL) { 17 | the_topic <- deparse(substitute(topic)) 18 | is_namespaced <- grepl(":{2,3}", the_topic) 19 | help_call_args <- list() 20 | if (is_namespaced && !is.null(package)) package <- NULL ## namespace takes priority 21 | if (is_namespaced) { 22 | the_topic_split <- strsplit(the_topic, ":{2,3}")[[1]] 23 | help_call_args$package <- the_topic_split[[1]] 24 | help_call_args$topic <- the_topic_split[[2]] 25 | } else { 26 | help_call_args$topic <- the_topic 27 | if (!is.null(package)) help_call_args$package <- package 28 | } 29 | help_matches <- do.call(utils::help, help_call_args) 30 | if (length(help_matches) < 1) stop("Couldn't find help for ", as.character(the_topic)) 31 | 32 | help_file <- help_matches[[1]] 33 | help_file_name <- fs::path_file(help_file) 34 | help_file_path_split <- fs::path_split(help_file)[[1]] 35 | help_file_folder <- help_file_path_split[[length(help_file_path_split) - 2]] # /help 36 | pkg_user_dir <- get_pkg_user_dir() 37 | target_file_name <- paste0(help_file_name, "_help.rmd") 38 | target_file <- fs::file_create(file.path(pkg_user_dir, target_file_name)) 39 | 40 | x <- tools::Rd2txt( 41 | utils:::.getHelpFile(help_file), 42 | out = target_file, 43 | options = list( 44 | width = getOption("rmd_doc_width", default = 80), 45 | itemBullet = "* ", 46 | underline_titles = FALSE, 47 | sectionIndent = 0, 48 | code_quote = TRUE, 49 | showURLs = TRUE 50 | ), 51 | outputEncoding = "UTF-8" 52 | ) 53 | 54 | help_text <- 55 | paste0( 56 | readLines(target_file, encoding = "UTF-8"), 57 | collapse = "\n" 58 | ) 59 | 60 | 61 | ## Set headings to markdown style 62 | with_headings <- gsub( 63 | "(\\r?\\n\\r?\\n)([A-Z].*)(?<=:)(\\r?\\n\\r?\\n)", 64 | "\\1### \\2\\3", 65 | help_text, 66 | perl = TRUE 67 | ) 68 | without_heading_colons <- gsub("(\\r?\\n###[^:]+):", "\\1", with_headings) 69 | md_help <- strsplit(without_heading_colons, "\\r?\\n")[[1]] 70 | 71 | examples_line <- which(grepl("###\\sExamples", md_help)) 72 | if (length(examples_line) > 0) { 73 | rmd_help <- c( 74 | md_help[1:examples_line + 1], 75 | "```{r}", 76 | md_help[(examples_line + 2):length(md_help)], 77 | "```" 78 | ) 79 | } else { 80 | rmd_help <- md_help 81 | } 82 | usage_line <- which(grepl("###\\sUsage", rmd_help)) 83 | if (length(usage_line) > 0) { 84 | headings <- which(grepl("^###", rmd_help)) 85 | usage_end_line <- min(headings[headings > usage_line]) - 2 86 | rmd_help <- c( 87 | rmd_help[1:usage_line + 1], 88 | # Mark this usage chunk for non-evalualtion 89 | "```{r eval=FALSE}", 90 | rmd_help[(usage_line + 2):usage_end_line], 91 | "```", 92 | rmd_help[(usage_end_line + 1):length(rmd_help)] 93 | ) 94 | } 95 | 96 | rmd_help <- c(paste0("# {", help_file_folder, "} / ", help_file_name), "", rmd_help) 97 | readr::write_file(paste0(rmd_help, collapse = "\n"), target_file) 98 | rstudioapi::navigateToFile(target_file) 99 | } 100 | 101 | get_pkg_user_dir <- function() { 102 | rmdocs_user_dir <- R_user_dir("rmdocs") 103 | if (!dir.exists(rmdocs_user_dir)) { 104 | dir.create(rmdocs_user_dir, recursive = TRUE) 105 | } 106 | rmdocs_user_dir 107 | } 108 | 109 | #' @noRd 110 | #' @export 111 | `?` <- function(e1, e2) eval(bquote(rmdocs::rmd_help(.(substitute(e1))))) 112 | 113 | #' @noRd 114 | #' @export 115 | help <- rmd_help 116 | 117 | .onAttach <- function(libname, pkgname) { 118 | packageStartupMessage("{rmdocs} is masking `?` and `help` to bring you {rmarkdown} help. Long Live RMD!") 119 | } 120 | --------------------------------------------------------------------------------