├── .Rbuildignore ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── NAMESPACE ├── R ├── RMD_utils.R ├── misc.R └── str_render.R ├── README.md ├── inst ├── css │ └── str_render.css ├── demo │ ├── benchmark.gif │ ├── external_funs.png │ ├── flip.gif │ ├── listview.gif │ ├── render.gif │ ├── unwrap.gif │ └── view_df2.gif └── rstudio │ └── addins.dcf ├── man ├── benchmark.Rd ├── clip_2_df.Rd ├── flip_windows_path.Rd ├── format_console.Rd ├── printc.Rd ├── profv.Rd ├── render_addin.Rd ├── render_rmd.Rd ├── render_str.Rd ├── scan_externals.Rd ├── scan_file.Rd ├── scan_fun.Rd ├── time_it.Rd ├── unwrap_extra_blank.Rd └── view_list.Rd └── mischelper.Rproj /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Ruserdata 5 | man/ 6 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: mischelper 2 | Type: Package 3 | Title: Collections Of Miscellaneous Helper Functions To Save Some Time 4 | Version: 0.3.0 5 | Author: dracodoc 6 | Maintainer: dracodoc 7 | Description: Remove unneeded hard line breaks of text in clipboard, then paste. 8 | flip windows path separator in clipboard. 9 | microbenchmark selected code. 10 | License: MIT + file LICENSE 11 | Encoding: UTF-8 12 | LazyData: true 13 | Imports: 14 | stringr, 15 | rstudioapi, 16 | magrittr, 17 | rmarkdown, 18 | knitr, 19 | data.table, 20 | lubridate, 21 | purrr, 22 | shiny, 23 | miniUI, 24 | clipr 25 | Suggests: 26 | crayon, 27 | microbenchmark, 28 | profvis, 29 | listviewer, 30 | codetools 31 | RoxygenNote: 7.3.2 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2016 2 | COPYRIGHT HOLDER: Xianghui Dong -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(benchmark) 4 | export(charac) 5 | export(check_print) 6 | export(clip_2_df) 7 | export(current_timestamp) 8 | export(export_chunks) 9 | export(file_cd_current) 10 | export(flip_windows_path) 11 | export(format_console) 12 | export(log_print) 13 | export(printc) 14 | export(profv) 15 | export(render_addin) 16 | export(render_rmd) 17 | export(render_str) 18 | export(run_shiny_as_job) 19 | export(scan_externals) 20 | export(scan_file) 21 | export(scan_fun) 22 | export(show_duplicates) 23 | export(source_chunks) 24 | export(time_it) 25 | export(unwrap_clip) 26 | export(unwrap_extra_blank) 27 | export(unwrap_selection) 28 | export(view_current) 29 | export(view_list) 30 | import(data.table) 31 | import(stringr) 32 | -------------------------------------------------------------------------------- /R/RMD_utils.R: -------------------------------------------------------------------------------- 1 | 2 | #' Show rows with same column value in data.table 3 | #' 4 | #' Data have some key column which is supposed to be unique for every row. 5 | #' However there could be duplicated key column values in data due to various 6 | #' reasons. The rows may have duplicated key column value but different value in 7 | #' other columns. It's beneficial to check this subset. Note the common function 8 | #' of duplicated will only show the extra rows duplicate to the source row, 9 | #' while this function will show all rows having common column value. 10 | #' 11 | #' @param dt data.table 12 | #' @param key_col key column name 13 | #' 14 | #' @return data.table subset of rows with same column values 15 | #' @export 16 | #' 17 | #' @examples 18 | #' dt <- data.table(a = letters, keys = c(1:25, 7)) 19 | #' show_duplicates(dt, "keys") 20 | show_duplicates <- function(dt, key_col) { 21 | dupe_keys <- dt[duplicated(dt[[key_col]])][[key_col]] 22 | dt[dt[[key_col]] %in% dupe_keys] 23 | } 24 | #' Print vector literal code for a string Vector 25 | #' 26 | #' Sometimes we need to write a vector which values come from data, which means 27 | #' to manually copy values and write into a vector definition. This function 28 | #' will take a vector (often came from data, like all unique values of a 29 | #' category column) and print a vector definition string, then it can be copied 30 | #' to source code. 31 | #' 32 | #' @param x a string vector 33 | #' 34 | #' @export 35 | #' 36 | #' @examples 37 | #' printc(letters[1:3]) 38 | printc <- function(x){ 39 | cat(charac(x)) 40 | } 41 | 42 | #' Convert a string vector into vector literal code 43 | #' 44 | #' Given a string vector, generate a string literal of the vec in vec definition 45 | #' format. This can be used internally in other function or to print the vec 46 | #' definition. 47 | #' 48 | #' @param x a string vector 49 | #' 50 | #' @return vector literal code 51 | #' @export 52 | #' 53 | charac <- function(x) { 54 | paste0('c("', paste(x, collapse = '", "'), '")') 55 | } 56 | 57 | #' Export code chunks in RMD into R source code file 58 | #' 59 | #' After developing code in RMarkdown document, export the code chunks into R 60 | #' source code file. It will also generate a file header in comments to show the 61 | #' command how the file was generated to remind it need to be generated from 62 | #' source RMD instead being updated manually. If you turn on the document 63 | #' outline in RStudio, there will be a table of contents to organize the 64 | #' functions by code chunks. 65 | #' 66 | #' @param rmd_path Source RMD file path 67 | #' @param labels code chunk label vector 68 | #' @param output_path Target R source code file path 69 | #' 70 | #' @export 71 | #' 72 | export_chunks <- function(rmd_path, labels, output_path) { 73 | # reprint the call expression, need to do before labels get names. we have the value of each parameter, just call them. the line need to be in single line for easier copy (multiple line need # in beginning) 74 | generating_cmd <- str_c('# export_chunks("', rmd_path, '", ', charac(labels), ', "', output_path, '")') 75 | file_header <- c("# ******* Auto generated from chunks in RMD, Do not edit manually *******", 76 | generating_cmd, 77 | "# ***********************************************************************") 78 | lines <- readLines(rmd_path, encoding = "UTF-8") 79 | # get all chunk start/end lines. RMD require the symbols start from line beginning. need to escape `, {} 80 | chunk_start_indice <- str_which(lines, "^\`\`\`\\{r\\s.*\\}") 81 | chunk_labels <- str_match(lines, "^\`\`\`\\{r\\s(.*)\\}")[, 2] %>% 82 | # there could be option after , so remove the option part. cannot put indexing part immediately as that will break pipe interpretation 83 | str_split(",", simplify = TRUE) %>% 84 | .[, 1] 85 | chunk_end_indice <- str_which(lines, "^\`\`\`$") 86 | names(labels) <- labels 87 | label_start_indice <- map(labels, ~ which(chunk_labels == .)) 88 | if(any(map_lgl(label_start_indice, ~ length(.) == 0))) { 89 | cat("some chunk not found: \n") 90 | print(label_start_indice) 91 | } 92 | # for every label, get next chunk end indice. findInterval get the index of left side of each label 93 | label_end_indice <- chunk_end_indice[findInterval(label_start_indice, chunk_end_indice) + 1] 94 | names(label_end_indice) <- labels 95 | picked_chunks <- map(labels, ~ 96 | # add chunk label as header 97 | c(str_c("# FROM: {", ., "} ----"), 98 | lines[(label_start_indice[[.]] + 1):(label_end_indice[[.]] - 1)])) 99 | # open connection with specified encoding instead by system option 100 | output_con <- file(output_path, encoding = "UTF-8") 101 | writeLines(c(file_header, unlist(picked_chunks)), con = output_con) 102 | close(output_con) 103 | } 104 | # extract chunks, write to temp file and source it. note this cannot take the chunk with extra options? 105 | 106 | 107 | #' Source the code chunks into global environment 108 | #' 109 | #' In developing, it's easier to just load specific code chunks into environment 110 | #' in one click by running a command. Otherwise you may need to click multiple 111 | #' times for each code chunk, and Run chunks above in RStudio may not work 112 | #' because the code to load may intervene with other exploration code chunks. 113 | #' 114 | #' @param rmd_path Source RMD file path 115 | #' @param labels code chunk label vector 116 | #' 117 | #' @export 118 | #' 119 | source_chunks <- function(rmd_path, labels) { 120 | temp_script <- tempfile() 121 | export_chunks(rmd_path, labels, temp_script) 122 | source(temp_script) 123 | } 124 | # print info with color, possibly with a note. use simple color name represent a color combination. ideally will want to show color in rmd output, but that's too difficult now, have to use css style which doesn't apply to output easily. 125 | # in console we usually set benchmark to be yellow. 126 | # we can use different note tags to make it easier to separate in html output. also the note will be in color as string, easy to find in code chunk part. 127 | 128 | 129 | #' Print message and information with color 130 | #' 131 | #' A data processing pipeline may have lots of print out. It will be helpful if 132 | #' some quality checking output can be printed with color so it can be catched 133 | #' easily in long output. This function will print the given object, add some 134 | #' notes if provided in color. Note it also return the input object so it can be 135 | #' used in the middle of a pipe. 136 | #' 137 | #' @param obj obj to print out 138 | #' @param note message 139 | #' @param color 140 | #' 141 | #' @export 142 | #' 143 | #' @examples 144 | #' log_print(3, note = "should not be negative number") 145 | #' log_print(-2 > 0, color = "yellow", note = "should not be negative number") 146 | log_print <- function(obj, note = "", color = "cyan") { 147 | bg_color_vec <- c(cyan = crayon::bgCyan$white, yellow = crayon::bgYellow) 148 | color_vec <- c(cyan = crayon::cyan, yellow = crayon::yellow) 149 | # knit result don't have color, add a tag for easier search 150 | # the R default print out is easier to read and align, always use that when possible 151 | # first need to capture print results in string, then cat with color. note should not use background color as it will be hard to read. 152 | cat(stringr::str_c(bg_color_vec[[color]]("-- [INFO]"), " ", color_vec[[color]](note)), 153 | capture.output(print(obj)), 154 | sep = "\n") 155 | # so pipe can continue 156 | return(invisible(obj)) 157 | } 158 | # log print for information display. some condition are key control metrics, need more obvious way to show it passed or not. 159 | # use ensurer pattern, object pipe to function call, then we can use it with multiple expressions. we can also switch between expression itself, log and check easily 160 | # expression %>% log_print %>% check_print 161 | # note expression need to be in () otherwise the . will pick last item 162 | # 1st expression get result, 2nd check condition, if passed, show ---- and in green. if not passed, show xxxx and in orange. 163 | # need to ignore first parameter for our usage 164 | 165 | #' Check condition and print result in color 166 | #' 167 | #' For more critical data quality checks, this can be put in last step of data 168 | #' processing pipeline, do some quality check, and will print green message if 169 | #' check passed, red message if the check failed. It can be used with log_print 170 | #' together to give more detailed information. 171 | #' 172 | #' @param pipe_value Take the object transferred from a pipe expression 173 | #' @param test_expression test the object in test expression 174 | #' 175 | #' @export 176 | #' 177 | #' @examples 178 | #' nrow(mtcars) %>% 179 | #' log_print("row count should be more than 50") %>% 180 | #' check_print(. > 50) 181 | check_print <- function(pipe_value, test_expression) { 182 | if (test_expression) { 183 | cat(crayon::cyan(glue::glue("---------------------------- Check Passed ----------------------------"))) 184 | } else { 185 | cat(crayon::bgYellow$red(glue::glue("xxxxxxxxxxxxxxxxxxxxxxxxxxxx Check Failed xxxxxxxxxxxxxxxxxxxxxxxxxxxx"))) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /R/misc.R: -------------------------------------------------------------------------------- 1 | # check package availability, so that user don't need to install every possible dependency at once 2 | check_pkg <- function(pkg_name) { 3 | if (!requireNamespace(pkg_name, quietly = TRUE)) { 4 | stop(sprintf('%s is needed but not installed. Please run install.packages("%s") first.', pkg_name, pkg_name), 5 | call. = FALSE) 6 | } 7 | } 8 | 9 | #' Run Commented lines 10 | #' 11 | #' Turn commented out lines back to normal and run them. If there is selection, 12 | #' convert selection. If there is no selection, convert current line. 13 | run_commented_out_lines <- function() { 14 | selected <- get_selection() 15 | # expecting code to be start after # , with a space. it's possible there is leading spaces, such like comment line inside a function with indentation. Need () with :: syntax in pipe 16 | striped_lines <- stringr::str_split(selected, "\n")[[1]] %>% (stringr::str_trim) 17 | run_command(selected, stringr::str_sub(striped_lines, 3)) 18 | } 19 | 20 | #' Run Shiny app as background job 21 | #' 22 | #' Run current file in source editor as shiny app in background job, open in 23 | #' browser window, live reload with source changes (if you set 24 | #' `options(shiny.autoreload = TRUE)`). 25 | #' 26 | #' @export 27 | #' 28 | run_shiny_as_job <- function() { 29 | # options(shiny.autoreload = TRUE) 30 | current_context <- rstudioapi::getSourceEditorContext() 31 | app_dir <- dirname(current_context$path) 32 | # need a label for job. app_dir could be too long to be shown completely. take last two parts 33 | dir_parts <- stringr::str_split(app_dir, "/")[[1]] # rstudio api always return / style even in windows 34 | part_count <- length(dir_parts) 35 | if (part_count > 2) { 36 | job_name <- stringr::str_c(dir_parts[(part_count - 1):part_count], collapse = "/") 37 | } else { 38 | job_name <- app_dir 39 | } 40 | script_content <- sprintf("shiny::runApp('%s', launch.browser = TRUE)\n", 41 | app_dir) 42 | # use a meaningful file name as this is shown in jobs pane. 43 | temp_script <- tempfile(fileext = ".R") 44 | cat(script_content, file = temp_script) 45 | rstudioapi::jobRunScript(temp_script, name = job_name, workingDir = app_dir) 46 | } 47 | #' read clipboard into data frame 48 | #' 49 | #' Read windows/mac/linux clipboard, convert csv into data frame, open data 50 | #' viewer, write markdown table back to clipboard, return the data frame 51 | #' 52 | #' @return the data frame 53 | #' @export 54 | #' 55 | clip_2_df <- function(){ 56 | lines <- clipr::read_clip() 57 | # paste0(lines, collapse = "\n") 58 | # getting regular data frame so we can use the matrix indexing in next line 59 | df <- data.table::fread(paste0(lines, collapse = "\n"), na.strings = NULL, data.table = FALSE) 60 | # we don't want to print NA in table since the actual data don't have NA returned 61 | df[is.na(df)] <- "" 62 | View(df) 63 | clipr::write_clip(knitr::kable(df, format = "markdown")) 64 | return(df) 65 | } 66 | 67 | #' Get current time in ISO format 68 | #' 69 | #' The time string in this format can be used in file name, directory name etc 70 | #' 71 | #' @return current date time in ISO format %Y-%m-%d_%H-%M-%S 72 | #' @export 73 | #' 74 | current_timestamp <- function() { 75 | format(Sys.time(), "%Y-%m-%d_%H-%M-%S") 76 | } 77 | 78 | # get text in selection, which could be source editor or console editor. if no selection, return current line. 79 | get_selection <- function(editor = "active", or_current_line = TRUE) { 80 | # context <- rstudioapi::getActiveDocumentContext() 81 | context <- switch(editor, 82 | active = rstudioapi::getActiveDocumentContext(), 83 | source = rstudioapi::getSourceEditorContext(), 84 | console = rstudioapi::getConsoleEditorContext()) 85 | selection_start <- context$selection[[1]]$range$start 86 | selection_end <- context$selection[[1]]$range$end 87 | selection <- NULL 88 | if (any(selection_start != selection_end)) { # text selected 89 | selection <- context$selection[[1]]$text 90 | } else if (or_current_line) { 91 | current_row_no <- context$selection[[1]]$range$start[1] 92 | selection <- context[["contents"]][current_row_no] 93 | } 94 | return(selection) 95 | } 96 | # given selected content, and new formated command and run in console. need the original content to check if it's NULL 97 | run_command <- function(selected, formated_cmd) { 98 | if (!is.null(selected)) { 99 | rstudioapi::sendToConsole(formated_cmd, execute = TRUE) 100 | } 101 | } 102 | # insert text in editor cursor 103 | insert_text <- function(text_formated) { 104 | context <- rstudioapi::getSourceEditorContext() 105 | rstudioapi::insertText(context$selection[[1]]$range, text_formated, id = context$id) 106 | } 107 | 108 | unwrap_core <- function(source, extra_blank_line = FALSE) { 109 | text_original <- if (source == "clip") { 110 | # clipboard <- stringr::str_trim(utils::readClipboard(), side = "right") 111 | stringr::str_trim(clipr::read_clip(), side = "right") 112 | } else { 113 | # clipboard function always return all line as a line vector, the code was expecting this structure, so we need to split by new line. 114 | stringr::str_split(get_selection(), "\n")[[1]] 115 | } 116 | if (is.null(text_original)) return() 117 | # use character class [] because each symbol are single characters, no need to use |. the Chinese quote have to be inside a double quote 118 | non_terminating_match <- stringr::str_c("[^\\.!?:", "\\u201d", "]") # terminating punctuation in end of line. 119 | # str_view_all(clipboard, str_c(".*", non_terminating_match, "$")) 120 | to_remove_wrap <- stringr::str_detect(text_original, stringr::str_c(".*", non_terminating_match, "$")) 121 | # use space for soft wrap lines, new line for other wrap 122 | line_connector <- rep(ifelse(extra_blank_line, "\n\n", "\n"), length(text_original)) 123 | line_connector[to_remove_wrap] <- " " 124 | text_formated <- stringr::str_c(text_original, line_connector, collapse = "") 125 | # # remove extra white spaces caused by end of line 126 | text_formated <- stringr::str_replace_all(text_formated, "\\s+", " ") 127 | insert_text(text_formated) 128 | } 129 | 130 | #' Unwrap clipboard 131 | #' 132 | #' \code{unwrap} Remove unneeded hard line breaks of text in clipboard, then 133 | #' insert text. 134 | #' 135 | #' @param extra_blank_line Whether to insert extra blank line between paragraphs. 136 | #' 137 | #' @export 138 | #' 139 | unwrap_clip <- function() { 140 | unwrap_core(source = "clip") 141 | } 142 | 143 | #' Unwrap selection 144 | #' 145 | #' \code{unwrap} Remove unneeded hard line breaks of selected, then 146 | #' update the selection. 147 | #' 148 | #' @param extra_blank_line Whether to insert extra blank line between paragraphs. 149 | #' 150 | #' @export 151 | #' 152 | unwrap_selection <- function() { 153 | unwrap_core(source = "selection") 154 | } 155 | 156 | #' Unwrap with blank line 157 | #' 158 | #' Remove unneeded hard line breaks of text in clipboard, insert extra blank 159 | #' line between paragraphs, then paste into current cursor position. 160 | #' 161 | #' Need this helper because RStudio Addin doesn't support function with 162 | #' parameters. 163 | #' 164 | #' @export 165 | #' 166 | unwrap_extra_blank <- function(){ 167 | unwrap(source = "selection", extra_blank_line = TRUE) 168 | } 169 | 170 | #' Flip windows path 171 | #' 172 | #' \code{flip_windows_path} convert "\" in clipboard to "/", then paste into 173 | #' current cursor position 174 | #' 175 | #' @export 176 | #' 177 | flip_windows_path <- function(){ 178 | p2 <- stringr::str_replace_all(clipr::read_clip(), "\\\\", "/") 179 | context <- rstudioapi::getActiveDocumentContext() 180 | rstudioapi::insertText(context$selection[[1]]$range, p2, id = context$id) 181 | } 182 | 183 | #' microbenchmark selected 184 | #' 185 | #' microbenchmark selected code for 10 runs in console without changing the 186 | #' source code. 187 | #' 188 | #' \code{microbenchmark()} parameters can be changed by recalling history in 189 | #' console then editing the last line. 190 | #' 191 | #' @export 192 | #' 193 | benchmark <- function(runs = 10){ 194 | check_pkg("microbenchmark") 195 | # context <- rstudioapi::getActiveDocumentContext() 196 | # selection_start <- context$selection[[1]]$range$start 197 | # selection_end <- context$selection[[1]]$range$end 198 | selected <- get_selection() 199 | formated <- stringr::str_c("microbenchmark::microbenchmark(selected_code = {\n", 200 | selected, "}, times = ", runs, ")") 201 | run_command(selected, formated) 202 | # if (!is.null(selected)) { 203 | # # } 204 | # # if (any(selection_start != selection_end)) { # text selected 205 | # # selected <- context$selection[[1]]$text 206 | # formated <- stringr::str_c("microbenchmark::microbenchmark(selected_code = {\n", 207 | # selected, "}, times = ", runs, ")") 208 | # rstudioapi::sendToConsole(formated, execute = TRUE) 209 | # } 210 | } 211 | 212 | #' time selected code 213 | #' 214 | #' Easier way to measure time cost of expression or selected code. Underthehood 215 | #' it's just microbenchmark run once instead of 10. 216 | #' 217 | #' @export 218 | #' 219 | time_it <- function(){ 220 | benchmark(1) 221 | } 222 | 223 | #' Render RMarkdown in global environment 224 | #' 225 | #' Knit document will start from scratch, this will use global environment 226 | #' instead. So you don't have to run some expensive operations like read huge 227 | #' file from disk every time in rendering. 228 | #' 229 | #' @export 230 | #' 231 | render_rmd <- function(){ 232 | context <- rstudioapi::getActiveDocumentContext() 233 | formated <- paste0('rmarkdown::render("', context$path, '")') 234 | run_command(selected, formated) 235 | # rstudioapi::sendToConsole(formated, execute = TRUE) 236 | } 237 | 238 | #' profvis selected 239 | #' 240 | #' profvis selected code in console without changing source code. RStudio have a 241 | #' similar builtin menu in editor toolbar, but that only works with .R script, 242 | #' not working in .Rmd or unsaved file. 243 | #' 244 | #' @export 245 | #' 246 | profv <- function(){ 247 | # if (!requireNamespace("profvis", quietly = TRUE)) { 248 | # stop("profvis needed but not automatically installed.\nInstall the package with install.packages(\"profvis\")", 249 | # call. = FALSE) 250 | # } 251 | check_pkg("profvis") 252 | # context <- rstudioapi::getActiveDocumentContext() 253 | # selection_start <- context$selection[[1]]$range$start 254 | # selection_end <- context$selection[[1]]$range$end 255 | # if (any(selection_start != selection_end)) { # text selected 256 | selected <- get_selection() 257 | if (!is.null(selected)) { 258 | # selected <- context$selection[[1]]$text 259 | formated <- stringr::str_c("profvis::profvis({\n", 260 | selected, "})") 261 | rstudioapi::sendToConsole(formated, execute = TRUE) 262 | } 263 | } 264 | 265 | #' View selected list with listviewer 266 | #' 267 | #' Select a list, view it with listviewer in viewer pane. This is less relevant 268 | #' now with RStudio data viewer started to support list. 269 | #' 270 | #' @export 271 | view_list <- function(){ 272 | check_pkg("listviewer") 273 | # if (!requireNamespace("listviewer", quietly = TRUE)) { 274 | # stop("listviewer needed but not automatically installed.\nInstall the package with install.packages(\"listviewer\")", 275 | # call. = FALSE) 276 | # } 277 | # context <- rstudioapi::getActiveDocumentContext() 278 | # selection_start <- context$selection[[1]]$range$start 279 | # selection_end <- context$selection[[1]]$range$end 280 | # if (any(selection_start != selection_end)) { # text selected 281 | selected <- get_selection() 282 | formated_cmd <- stringr::str_c("listviewer::jsonedit(", 283 | selected, ', mode = "view", modes = c("code", "view"))') 284 | run_command(selected, formated_cmd) 285 | # if (!is.null(selected)) { 286 | # # selected <- context$selection[[1]]$text 287 | # formated <- stringr::str_c("listviewer::jsonedit(", 288 | # selected, ', mode = "view", modes = c("code", "view"))') 289 | # rstudioapi::sendToConsole(formated, execute = TRUE) 290 | # } 291 | } 292 | 293 | #' Open Data Viewer on Selected expression 294 | #' 295 | #' The RStudio Environment pane variable name column could be too narrow for 296 | #' long names, and it can be difficult to identify one among similar names. 297 | #' Sometimes it's also useful to check an filter expression on a data.frame. 298 | #' Select a variable or expression then use this feature to open the data viewer 299 | #' for it. With RStudio Viewer working on list/objects now, this become even 300 | #' more useful. 301 | #' 302 | #' @export 303 | 304 | view_current <- function(){ 305 | # context <- rstudioapi::getActiveDocumentContext() 306 | # selection_start <- context$selection[[1]]$range$start 307 | # selection_end <- context$selection[[1]]$range$end 308 | # if (any(selection_start != selection_end)) { # text selected 309 | selected <- get_selection() 310 | run_command(selected, stringr::str_c("View(", selected, ')')) 311 | # if (!is.null(selected)) { 312 | # # selected <- context$selection[[1]]$text 313 | # # this will show "View(get(selected))" in viewer, not optimal 314 | # # View(get(selected)) 315 | # formated <- stringr::str_c("View(", selected, ')') 316 | # rstudioapi::sendToConsole(formated, execute = TRUE) 317 | # } 318 | } 319 | 320 | #' Convert Console Print Out to Script 321 | #' 322 | #' Read console input and output from clipboard, format as script(remove the > 323 | #' prompt, convert output as comments). 324 | #' 325 | #' Formated script is written back to clipboard, and inserted to current cursor 326 | #' location 327 | #' 328 | #' @export 329 | #' 330 | format_console <- function(){ 331 | # clipboard only work in windows/mac. switch to clipr to work on linux too. 332 | input_lines <- clipr::read_clip() 333 | # this doesn't work. console editor only get the current editing area, i.e. commands not executed. Selected lines of console output is not part of console editor. have to use clipboard. 334 | # input_lines <- stringr::str_split(get_selection(editor = "console"), "\n")[[1]] 335 | empty_index <- stringr::str_detect(input_lines, "^\\s*$") 336 | commands_index <- stringr::str_detect(input_lines, "^\\+|^>") 337 | input_lines[!(commands_index | empty_index)] <- 338 | stringr::str_c("# ", input_lines[!(commands_index | empty_index)], sep = "") 339 | input_lines[commands_index] <- 340 | stringr::str_replace_all(input_lines[commands_index], "^\\+", " ") 341 | input_lines[commands_index] <- 342 | stringr::str_replace_all(input_lines[commands_index], "^>\\s?", "") 343 | clipr::write_clip(input_lines) 344 | output <- stringr::str_c(input_lines, "\n", collapse = "") 345 | context <- rstudioapi::getSourceEditorContext() 346 | rstudioapi::insertText(context$selection[[1]]$range, output, id = context$id) 347 | } 348 | #' Generate the literal c() of a character vector 349 | #' 350 | #' It's often needed to create a vector literally even the vector can be 351 | #' obtained in code. For example a sbuset of column names can be get with number 352 | #' index, but it's not safe to use number index in code. Use this function to 353 | #' turn the output of number index into literal format, then you can copy the 354 | #' output to code. 355 | #' 356 | #' @param x a vector holding items x1, x2, ... 357 | #' 358 | #' @return string of "c("x1", "x2")" 359 | #' @export 360 | #' 361 | #' @examples 362 | #' printc(names(mtcars)[1:3]) 363 | #' 364 | printc <- function(x){ 365 | cat(paste0('c("', paste(x, collapse = '", "'), '")')) 366 | } 367 | get_current_editor_path <- function() { 368 | context <- rstudioapi::getSourceEditorContext() 369 | dirname(context$path) 370 | } 371 | #' Navigate File Pane to folder of current file in editor 372 | #' 373 | #' @export 374 | #' 375 | file_cd_current <- function() { 376 | rstudioapi::filesPaneNavigate(get_current_editor_path()) 377 | } 378 | # scan external functions ---- 379 | # should organize by funs_inside, so we can replace all usage in one run 380 | organize_fun_table <- function(dt) { 381 | new_dt <- dt[!(fun_inside %in% fun)][, usage := list(list(fun)), 382 | by = fun_inside] 383 | # data.table order sort by C-locale, which sort capitalized letter first 384 | unique(new_dt[, .(fun_inside, usage)], by = 385 | "fun_inside")[base::order(fun_inside)] 386 | } 387 | #' Scan Function Object For External Functions 388 | #' 389 | #' @param fun_obj 390 | #' 391 | #' @return result table 392 | #' @export 393 | #' 394 | #' @examples mischelper::scan_fun(sort) 395 | scan_fun <- function(fun_obj) { 396 | check_pkg("codetools") 397 | # function parameter lost its name so have to use generic name 398 | organize_fun_table(data.table(fun = "fun_obj", 399 | fun_inside = codetools::findGlobals( 400 | fun_obj, merge = FALSE)$functions)) 401 | } 402 | # code string 403 | scan_string <- function(code_string) { 404 | check_pkg("codetools") 405 | temp_fun <- eval(parse(text = paste0("function() {\n", code_string, "\n}"))) 406 | organize_fun_table(data.table(fun = "code_string", 407 | fun_inside = codetools::findGlobals( 408 | temp_fun, merge = FALSE)$functions)) 409 | } 410 | #' Scan Source File For External Functions 411 | #' 412 | #' The file must be able to be sourced without error to be scanned, so packages 413 | #' or data need to be prepared before scanning. 414 | #' 415 | #' @param code_file The path of source file 416 | #' @param organize If FALSE, return table of `fun`, `fun_inside`; If TRUE, return 417 | #' table of `fun_inside`, `list of fun` 418 | #' 419 | #' @return Result table 420 | #' @export 421 | #' 422 | scan_file <- function(code_file, organize = TRUE) { 423 | check_pkg("codetools") 424 | source(code_file, local = TRUE, chdir = TRUE) 425 | names_in_fun <- ls(sorted = FALSE) 426 | funs_in_each_name <- lapply(names_in_fun, function(f_name) { 427 | obj <- get(f_name, parent.env(environment())) 428 | if (is.function(obj)) { 429 | data.table(fun = f_name, 430 | fun_inside = codetools::findGlobals( 431 | obj, merge = FALSE)$functions) 432 | } 433 | }) 434 | res <- rbindlist(funs_in_each_name) 435 | if (organize) { 436 | organize_fun_table(res) 437 | } else { 438 | res 439 | } 440 | } 441 | #' Scan External Functions 442 | #' 443 | #' If some code was selected, scan selected code, otherwise scan current file. 444 | #' Result table will also be opened in RStudio data viewer. The current file 445 | #' must be able to be sourced without error to be scanned, so packages or data 446 | #' need to be prepared before scanning. 447 | #' 448 | #' @return A data.table of functions. 449 | #' @export 450 | #' 451 | scan_externals <- function() { 452 | # context <- rstudioapi::getActiveDocumentContext() 453 | # selection_start <- context$selection[[1]]$range$start 454 | # selection_end <- context$selection[[1]]$range$end 455 | # if (any(selection_start != selection_end)) { # text selected 456 | selected <- get_selection() 457 | if (!is.null(selected)) { 458 | # selected <- context$selection[[1]]$text 459 | external_funs <- scan_string(selected) 460 | View(external_funs) 461 | external_funs 462 | } else { 463 | file_path <- rstudioapi::getSourceEditorContext()$path 464 | external_funs <- scan_file(file_path) 465 | View(external_funs) 466 | external_funs 467 | } 468 | } -------------------------------------------------------------------------------- /R/str_render.R: -------------------------------------------------------------------------------- 1 | #' Render str() output in html with style 2 | #' 3 | #' If the output of \code{str()} is long, user can easily get lost in scrolling. 4 | #' This function convert output into html with tag classes and render it with 5 | #' style. You can customize the style by experimenting with chrome/firefox 6 | #' developer tools first, then save to css. The css is located in 7 | #' \code{system.file("css", "str_render.css", package = "mischelper")} 8 | #' 9 | #' By default a browser window will be opened because it is intended for long 10 | #' output. The \code{info} label and the name of input object are added for clarity. Since they are not part of original \code{str()} output, \code{(info)} are used. 11 | #' 12 | #' @param obj the object/variable to be inspected 13 | #' 14 | #' @export 15 | #' @import data.table stringr 16 | render_str <- function(obj) { 17 | str_lines <- capture.output(str(obj)) 18 | lines_dt <- data.table(line = str_lines) 19 | lines_dt[, depth := str_count(str_extract(line, "^[\\.\\s]*"), "\\.\\.")] 20 | lines_dt[, row_no := .I] 21 | # symbols work as type identifier only in leading position 22 | lines_dt[str_detect(line, "^[^.\\s]"), type := "info"] 23 | lines_dt[str_detect(line, "[.\\s]*\\$"), type := "list"] 24 | lines_dt[str_detect(line, "[.\\s]*@"), type := "slot"] 25 | lines_dt[str_detect(line, "[.\\s]*- attr"), type := "attribute"] 26 | # key of info and obj is added by my program, not original output of str() 27 | # thus use () to seperate. sometimes there are slot name as info. this can separate them. 28 | lines_dt[type == "info", c("key", "value") := 29 | list("(info)", line), by = row_no] 30 | # the : in "key:value"" : can be matched to : in "int [1:2]" 31 | lines_dt[type == "list", c("key", "value") := 32 | as.list(str_match(line, ".*(\\$\\s[^:]*):(.*)"))[2:3], 33 | by = row_no] 34 | lines_dt[type == "slot", c("key", "value") := 35 | as.list(str_match(line, ".*(@\\s[^:]*):(.*)"))[2:3], 36 | by = row_no] 37 | lines_dt[type == "attribute", c("key", "value") := 38 | as.list(str_match(line, ".*(-\\sattr.*)=(.*)"))[2:3], 39 | by = row_no] 40 | # trim spaces. esp the slot part have some spaces after key 41 | lines_dt[, key := str_trim(key)] 42 | lines_dt[, value := str_trim(value)] 43 | # top line can get same treament with info, just use different key. no need for special css class since it is the first line anyway. 44 | # need to get obj name outside of data.table, otherwise it will look at another obj 45 | obj_name <- deparse(substitute(obj)) 46 | lines_dt[1, key := paste0("(", obj_name, ")")] 47 | # add some item specific class 48 | lines_dt[, item_class := paste0("str__", type, " depth_", depth)] 49 | lines_dt[, value_class := paste0("str__value ", 50 | str_trim(str_extract(value, "^\\s?[[:word:]]*")))] 51 | # ui ---- 52 | ui <- miniUI::miniPage( 53 | shiny::includeCSS(system.file("css", "str_render.css", package = "mischelper")), 54 | miniUI::gadgetTitleBar(paste0("html view of str(", deparse(substitute(obj)), ")")), 55 | miniUI::miniContentPanel( 56 | shiny::htmlOutput("main") 57 | ) 58 | ) 59 | # server ---- 60 | server <- function(input, output, session) { 61 | ## Your reactive logic goes here. 62 | getPage <- function() { 63 | # return(includeHTML("str_buff.html")) 64 | tag_list <- vector("list", length = nrow(lines_dt)) 65 | for (i in 1:nrow(lines_dt)) { 66 | tag_list[[i]] <- shiny::tags$div(class = lines_dt[i, item_class], 67 | shiny::tags$span(lines_dt[i, key], class = "str__key"), 68 | shiny::tags$span(" : ", class = "str__key_value_sp"), 69 | shiny::tags$span(class = lines_dt[i, value_class], 70 | lines_dt[i, value])) 71 | } 72 | return(tag_list) 73 | } 74 | output$main <- shiny::renderUI({getPage()}) 75 | shiny::observeEvent(input$done, { 76 | shiny::stopApp() 77 | }) 78 | } 79 | # run in browser since the main goal is to view bigger output 80 | shiny::runGadget(ui, server, viewer = shiny::browserViewer()) 81 | } 82 | 83 | #' View obj/variable \code{str()} output in html view by addin 84 | #' 85 | #' @export 86 | render_addin <- function() { 87 | context <- rstudioapi::getActiveDocumentContext() 88 | selection_start <- context$selection[[1]]$range$start 89 | selection_end <- context$selection[[1]]$range$end 90 | if (any(selection_start != selection_end)) { # text selected 91 | selected <- context$selection[[1]]$text 92 | cmd <- stringr::str_c("mischelper::render_str(", selected, ")") 93 | rstudioapi::sendToConsole(cmd, execute = TRUE) 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mischelper 2 | RStudio Addin that collected several miscellaneous helper functions to save some time. 3 | 4 | Used to be named [formatpaste](https://github.com/dracodoc/formatpaste). 5 | 6 | These functions are very simple in concept but may save you some time. 7 | 8 | ## Updates 9 | 10 | **2025.01.14** 11 | 12 | Added some functions to help quality check in data processing(`show_duplicates`, `printc`, `log_print`, `check_print`, see function help for usage), and functions to support Literate programming with RMarkdown. My workflow is like this: 13 | - Create a RMarkdown document, gather all the requirement, documents, links, notes in the document. 14 | - Start to explore various parts in RMD code chunks. There could be multiple version of same function, some exploration may include references, links, notes, records of why some approaches didn't work, what need to be noted for the current approach. 15 | - RMD allow text and code to be interlaced cleanly, you don't need to write large chunk of text in code comments, and you can keep the historical explorations for record. After some time the older code can be moved to a `Archive` section in the end of document so it will not interfere with the current development but still kept for record. 16 | - The RMD titles, code chunk labels and header comments in R code can generate an [outline](https://posit.co/blog/rstudio-v1-4-preview-little-things/), which is very helpful for long document or R source code. 17 | - After all the exploration, you can write one line of code like this, which will take some code chunks and save as R source code, put in a folder of Shiny app(or any other places you need to deploy the code). 18 | 19 | export_chunks("Back_End.Rmd", c("signal processing", "read input"), "app/1_util_export.R") 20 | 21 | Later if you need to update the code, just develop in the RMD (and with all the documentation and references for future you to revisit), then run this line of code again to deploy the code. 22 | 23 | **2022.02.28** 24 | 25 | Added a function to navigate File Pane to the folder of current file in editor, you can register the feature with keyboard shortcut if needed. If need to change directory in terminal, you can use File Pane menu `Open new terminal here`. If need to return to default project root, you can use File Pane menu `Go to working diretory`. 26 | 27 | **2021.03.15** 28 | 29 | Previously `format console` used R builtin clipboard support which only support windows/Mac. Per user request of linux support, this feature switched to `clipr` which has linux support. `clipr` is a required dependency package now. 30 | 31 | **2020.01.13** 32 | 33 | Addin will work on either selected text or current line. 34 | 35 | **2019.12.10** 36 | 37 | Removed some package depdencies so that the addin can be installed with minimal requirements, and only install needed packages when the feature requires it. 38 | 39 | Sometimes I have some code commented out but want to run it from time to time. For example I often have some test code right after a function definition. This is used in interactive development so not really part of test framework. New feature will take selected commented out lines, remove comments and run in console. 40 | 41 | **2019.10.18** 42 | 43 | Running Shiny apps from RStudio will see current global environment, this has burned me more than once when my app runs fine in development, but failed after deployed because it was depending some global environment data implicitly. A shiny app should almost always run in clean session as it was supposed to be deployed. There is [a issue here](https://github.com/rstudio/rstudio/issues/5190) documented various similar requests so it is in popular need. 44 | 45 | Since the issue has been around for some time and I want to use this as soon as possible, and the feature is easy enough to implement thanks to RStudio jobs and rstudioapi, I just implement it in `mischelper`. Now you can click a button in addin menu and launch your current Shiny app in background. 46 | 47 | Some details about my design choices because my implementation is somewhat different from the methods described in other places: 48 | - One method is to retrieve the random port of launched app then open with RStudio viewer, this also need to translate url for RStudio server. I tried this method then I need to use a fixed port number which is not ideal (otherwise I'm not sure how can I retrieve the port number programmingly), and the app was opened in viewer pane not maximized, which is almost never the user want. 49 | - Instead I just launch the app with system browser, this solve all the problems. 50 | - I also put the app directory as job name to help your identify different jobs. 51 | - If you want the app to auto reload with source changes, you can use the global option `options(shiny.autoreload = TRUE)`. 52 | - The job never use global environment, use the app dir as working directory, don't return result back to global environment. 53 | 54 | **2017.11.21** 55 | 56 | Sometimes I need to convert existing code into a package, then I need to find all external functions used in code, import package, and change the function usage into full qualified names. This is a cumbersome task. Previously I have to read code line by line, rely on error message of building package to find the functions. 57 | 58 | In reading book `Extending R` I found [`XRtools::makeImports`](https://github.com/johnmchambers/XRtools/blob/master/R/makeImports.R) can scan a package and find all packages need to be imported. This doesn't fit my need exactly because I'm still developing a package, and I want all the external functions not packages. Still the source code helped me a lot to get my current solution: 59 | - With the mischelper addin menu `Scan Externals`, current selected code or current opened file will be scanned, and the result functions table returned. 60 | ![external_funs](/inst/demo/external_funs.png) 61 | - Note the code or file must be able to be sourced properly to be checked, so you may need to load packages or prepare data before checking. 62 | - You can also use the exported function `mischelper::scan_fun`, `mischelper::scan_file` directly. 63 | 64 | I have another addin `namebrowser` which could scan all installed packages and build a name table of all functions in them. So it's also possible to use that database and give all candidate packages for every external functions found. I'm not sure how many people will want to use that so I didn't do that yet. For now I need to verify each function usage anyway, and with the packages properly loaded, pressing `F1` should tell me the proper package already. If I scan all installed packages, the database may actually give more false positives and make it harder to determine which is the right one. 65 | 66 | **2017.06.27** 67 | 68 | `printc` will generate the literal `c("a", "b")` format from the vector `c("a", "b")`. For example I want to rename some columns but I don't want to rely on number index which can break later, so I just use `printc(names(mtcars)[1:3])` then copy the result to code. 69 | 70 | **2017.06.21** 71 | 72 | The RStudio Environment pane variable name column is too narrow for longnames, so it's difficutl to find the right data frame in long list of similar names. Before RStudio make that column adjustable or use shorter summary description columns, you can select the variable and use `view data frame` to open in data viewer. 73 | 74 | You can also select an expression (or part of an expression) and use this shortcut to inspect the content. This way you don't need to save it into a temporary variable. It's a great way to quickly explore expressions, and I have found I'm using it many time everyday. 75 | 76 | You can assign a keyboard shortcut for it using the toolbar menu `Addins -> Browse addins -> keyboard shortcuts`. 77 | 78 | ![viewdf](/inst/demo/view_df2.gif) 79 | 80 | **2017.04.10** 81 | 82 | Added a feature that will read csv format data in clipboard, convert to data frame and open Viewer in RStudio, write markdown table format back to clipboard so you can use it in markdown document. The dataframe itself is also returned. 83 | 84 | **2017.04.06** 85 | 86 | I just found RStudio have the built-in menu `profvis selected lines` which is same with my `provis` feature. It can be accessed from the `Profile` menu, a wizard toolbar button (which is only available in `.R` file, not in `.Rmd`) or keyboard shortcut `shift-alt-cmd-P` in Mac. 87 | 88 | Thus I removed the same feature in my addin, added another feature I found useful: `Render RMarkdown`. 89 | 90 | When you `knit` a `.Rmd` document, all code are evaluated from fresh, independent from the global environment. This follows the reproducible research principles. 91 | 92 | If I do need a strict reproducible report I will knit the complete report, however sometimes I just want a rendered html with some results and plots, and I don't want to run some computational expensive operations again from beginning, like reading a huge dataset from disk and go through every previous step. 93 | 94 | `rmarkdown::render("your_report.Rmd")` will render the document in the global environment, so if you have the data/object exists already it can get the result immedidately. I found this can save a lot of time for me, especially sometimes I need to render several times in editing. 95 | 96 | **2017.02.01** 97 | 98 | I use `str()` a lot to inspect lists or objects. In RStudio you can expand a list/object in environment pane which is just `str()` output, however the space is often too limited to get an overview. If running `str()` in console, it could also need quite some scrolling. 99 | 100 | At first I tried to convert `str()` output into a nested list and view it with `listviewer`. However I didn't find a good way to show some meta info of an item, which should be at same level of item and attached to the item. 101 | 102 | Then I just parse the output and tag each line with tags, generate an html file then styling it with css. To use the feature just select the list/obj in RStudio source editor or console, click addin menu `Misc - View str()`. Or you can call function directly with `mischelper::render_str()`. 103 | 104 | ![render](/inst/demo/render.gif) 105 | 106 | The css styling can be customized easily. Use chrome/firefox developer tool to inspect the opened page, select the item and edit the cooresponding css, and you can see result immediately. Once you are satisfied with the changes, save the change to css in package, which is located in `system.file("css", "str_render.css", package = "mischelper")`. 107 | 108 | The `info` label and the name of input object are added for clarity. Since they are not part of original `str()` output, `()` are used to separate them. 109 | 110 | Note this only works for list,data frame or objects that have some nested structure. It doesn't work for simple vectors, but there is not much need of a html view for the simple structure anyway. 111 | 112 | **2017.01.29** 113 | 114 | Added feature to call [listviewer](https://github.com/timelyportfolio/listviewer) for selected list or object. The listviewer package itself registered an addin menu but I don't like it to be a modal dialog blocking R session, and I don't want to enable the edit mode. I think edit to list should be done in program so it can be tracked. 115 | 116 | Just select the list or object then click addin menu or assign a keyboard shortcut. 117 | 118 | ![listviewer](/inst/demo/listview.gif) 119 | 120 | **2017.01.24** Added mac os clipboard functions. Now the package should work in both windows and mac. The functions are also available for use if you want. Check `?mischelper::clip_read_lines`, `?mischelper::clip_write_lines` for more details. 121 | Update: the functions cannot read all lines in mac. Please use `clipr` package instead. 122 | 123 | **2017.01.19** Added a simple timing menu which is just one time run microbenchmark 124 | 125 | **2016.09.17** Used expression name instead of full expression in microbenchmark printout 126 | 127 | **2016.09.09** Added feature to format console input and output 128 | 129 | 130 | ## Installation 131 | To install: 132 | * Install RStudio newest release or preview version. (Version 0.99.903 has a bug to run microbenchmark 3 times. Newer preview version don't have this bug.) 133 | * Run following lines in RStudio console: 134 | 135 | 136 | install.packages("devtools") 137 | devtools::install_github("dracodoc/mischelper") 138 | 139 | The packages `stringr`, `stringi`, `magrittr`, `microbenchmark`, `profvis` will be installed if not already available. `microbenchmark` is only required for `benchmark` function, and `profvis` is only for `profvis` but I chose to install them automatically because they are small packages without much dependencies. 140 | 141 | Functions can be accessed from the drop down list in Addin toolbar button or with keyboard shortcuts. All functions are prefixed with `Misc`. 142 | 143 | If you feel you don't need all the functions and they take too much space in the Addin drop down list, you can prevent some to be registered by RStudio. 144 | - edit the control file by 145 | `file.edit(system.file("rstudio", "addins.dcf", package = "mischelper"))` 146 | - remove the sections you don't need. 147 | - restart R session. 148 | - You can always restore to default by installing the package again if there is something wrong with the edit. 149 | 150 | ### Keyboard shortcuts 151 | You can assign keyboard shortcut to functions: 152 | * Select `Browse Addins` from the Addin toolbar button. 153 | * Click `Keyboard Shortcuts` in left bottom. 154 | * Click the Shortcut column for each row to assign keyboard shortcut. 155 | 156 | ## benchmark selected code 157 | ![benchmark](/inst/demo/benchmark.gif) 158 | 159 | * Misc - microbenchmark 160 | 161 | Select code to be benchmarked, use keyboard shortcut or toolbar menu. The code will be benchmarked for 10 runs in console. `microbenchmark()` parameters can be changed by recalling history in console then editing the last line. 162 | Since the source code is not changed, you can continue to edit the code, select different code sections to benchmark, continue working on code without needing to remove bechmark code when you are done with benchmark. 163 | 164 | * Misc - profvis 165 | 166 | Similar to microbenchmark, using [`profvis`](https://github.com/rstudio/profvis) to visualize profiling result. 167 | 168 | Currently the source editor window must be in focus before calling function, i.e. if 169 | you selected some code in source editor but moved focus to console before calling function, the addin will not work. There is a `getSourceEditorContext()` function in `rstudioapi` to solve this, but it is only available after RStudio version `0.99.1111`, which is only available as preview version. I plan to move to this function in future. 170 | 171 | ## Helper with clipboard 172 | Copy text into clipboard, put cursor to desired position. Each function will insert formated text to current cursor position. This works in both source editor and console. 173 | 174 | ### Misc - Format console input and output 175 | 176 | It's very common to find R code examples copied directly from console, which have format like this: 177 | 178 | > x <- 3 179 | > switch(x, 2+2, mean(1:10), rnorm(5)) 180 | [1] 2.2903605 2.3271663 -0.7060073 1.3622045 -0.2892720 181 | 182 | 183 | > centre <- function(x, type) { 184 | + switch(type, 185 | + mean = mean(x), 186 | + median = median(x), 187 | + trimmed = mean(x, trim = .1)) 188 | + } 189 | > x <- rcauchy(10) 190 | > centre(x, "mean") 191 | [1] 0.8760325 192 | 193 | 194 | To run these examples as script, lots of manual edits are needed. Now you can copy them either from some documents or your console, click addin menu `Misc - Format console`, the formated script will be inserted to current cursor position, and written back to clipboard so you can paste to other applications: 195 | 196 | x <- 3 197 | switch(x, 2+2, mean(1:10), rnorm(5)) 198 | # [1] 2.2903605 2.3271663 -0.7060073 1.3622045 -0.2892720 199 | 200 | 201 | centre <- function(x, type) { 202 | switch(type, 203 | mean = mean(x), 204 | median = median(x), 205 | trimmed = mean(x, trim = .1)) 206 | } 207 | x <- rcauchy(10) 208 | centre(x, "mean") 209 | # [1] 0.8760325 210 | 211 | The [formatR](http://yihui.name/formatR/) package have a related feature, though it will only evaluate valid script and put results in comments. 212 | 213 | 214 | ### Misc - Unwrap text 215 | 216 | ![unwrap](/inst/demo/unwrap.gif) 217 | 218 | Remove unneeded hard line breaks of text in clipboard, then paste into current cursor position. 219 | ### Misc - Unwrap with blank 220 | 221 | Remove hard line breaks, add add extra blank line between paragraphs, then paste into the cursor position. 222 | ### Misc - Flip windows path 223 | 224 | Convert "\" in clipboard to "/", then paste into current cursor position. Thus windows path can be used in R. 225 | 226 | ![flip](/inst/demo/flip.gif) 227 | 228 | -------------------------------------------------------------------------------- /inst/css/str_render.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | body { 3 | font-family: 'Source Code Pro', monospace; 4 | font-size: 1.5em; 5 | } 6 | .str__slot {color: #00BCD4;} 7 | .str__attribute {color: #795548;} 8 | .str__list {color: #3F51B5;} 9 | .str__key{ 10 | font-weight: bold; 11 | } 12 | .str__value { 13 | //text-decoration: underline; 14 | } 15 | .depth_0{ 16 | //text-decoration: underline; 17 | } 18 | .depth_1 { 19 | text-indent: 1.2em; 20 | } 21 | .depth_2 { 22 | text-indent: 3em; 23 | } 24 | 25 | .depth_3 { 26 | text-indent: 4em; 27 | } 28 | .depth_4 { 29 | text-indent: 6em; 30 | } 31 | .chr { 32 | color: green; 33 | } 34 | .num { 35 | color: #9C27B0; 36 | } 37 | .int{ 38 | color: #9C27B0; 39 | } 40 | .List { 41 | color: #F44336; 42 | } 43 | .Class{ 44 | color: purple; 45 | } 46 | .Named{ 47 | color: #3F51B5; 48 | } -------------------------------------------------------------------------------- /inst/demo/benchmark.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dracodoc/mischelper/3d6e989e26239ad7868e42b827fc6d96d24e1fe6/inst/demo/benchmark.gif -------------------------------------------------------------------------------- /inst/demo/external_funs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dracodoc/mischelper/3d6e989e26239ad7868e42b827fc6d96d24e1fe6/inst/demo/external_funs.png -------------------------------------------------------------------------------- /inst/demo/flip.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dracodoc/mischelper/3d6e989e26239ad7868e42b827fc6d96d24e1fe6/inst/demo/flip.gif -------------------------------------------------------------------------------- /inst/demo/listview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dracodoc/mischelper/3d6e989e26239ad7868e42b827fc6d96d24e1fe6/inst/demo/listview.gif -------------------------------------------------------------------------------- /inst/demo/render.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dracodoc/mischelper/3d6e989e26239ad7868e42b827fc6d96d24e1fe6/inst/demo/render.gif -------------------------------------------------------------------------------- /inst/demo/unwrap.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dracodoc/mischelper/3d6e989e26239ad7868e42b827fc6d96d24e1fe6/inst/demo/unwrap.gif -------------------------------------------------------------------------------- /inst/demo/view_df2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dracodoc/mischelper/3d6e989e26239ad7868e42b827fc6d96d24e1fe6/inst/demo/view_df2.gif -------------------------------------------------------------------------------- /inst/rstudio/addins.dcf: -------------------------------------------------------------------------------- 1 | Name: Misc - Run Shiny as job 2 | Description: Run current Shiny app as background job, open in browser with live reload 3 | Binding: run_shiny_as_job 4 | Interactive: false 5 | 6 | Name: Misc - Run Commented lines 7 | Description: Turn commented out lines back to normal and run 8 | Binding: run_commented_out_lines 9 | Interactive: false 10 | 11 | Name: Misc - View Selected 12 | Description: Open selected expression as data frame in Viewer 13 | Binding: view_current 14 | Interactive: false 15 | 16 | Name: Misc - Format Console to Script 17 | Description: Format console input and output to script 18 | Binding: format_console 19 | Interactive: false 20 | 21 | Name: Misc - Navigate File Pane to Current Editor 22 | Description: Navigate File Pane to folder of current file in editor 23 | Binding: file_cd_current 24 | Interactive: false 25 | 26 | Name: Misc - Render RMarkdown 27 | Description: Render RMarkdown in global environment 28 | Binding: render_rmd 29 | Interactive: false 30 | 31 | Name: Misc - Flip windows path 32 | Description: convert "\" in clipboard to "/", then paste into current cursor position 33 | Binding: flip_windows_path 34 | Interactive: false 35 | 36 | Name: Misc - Time it 37 | Description: microbenchmark selected code with 1 runs in console 38 | Binding: time_it 39 | Interactive: false 40 | 41 | Name: Misc - microbenchmark 42 | Description: microbenchmark selected code with 10 runs in console 43 | Binding: benchmark 44 | Interactive: false 45 | 46 | Name: Misc - profvis 47 | Description: profvis selected code in console 48 | Binding: profv 49 | Interactive: false 50 | 51 | Name: Misc - Unwrap clipboard 52 | Description: Remove unneeded hard line breaks of text in clipboard, then update the clipboard. 53 | Binding: unwrap_clip 54 | Interactive: false 55 | 56 | Name: Misc - Unwrap selection 57 | Description: Remove unneeded hard line breaks of text in selection, then update the selection. 58 | Binding: unwrap_selection 59 | Interactive: false 60 | 61 | Name: Misc - Clip to data frame 62 | Description: read clipboard, convert csv into data frame, open data, write markdown table back to clipboard 63 | Binding: clip_2_df 64 | Interactive: false 65 | 66 | Name: Misc - Scan External Functions 67 | Description: Scan External Functions in selected code or current file 68 | Binding: scan_externals 69 | Interactive: false 70 | 71 | Name: Misc - View str() 72 | Description: Render str() output in html with style 73 | Binding: render_addin 74 | Interactive: false 75 | 76 | 77 | -------------------------------------------------------------------------------- /man/benchmark.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{benchmark} 4 | \alias{benchmark} 5 | \title{microbenchmark selected} 6 | \usage{ 7 | benchmark(runs = 10) 8 | } 9 | \description{ 10 | microbenchmark selected code for 10 runs in console without changing the 11 | source code. 12 | } 13 | \details{ 14 | \code{microbenchmark()} parameters can be changed by recalling history in 15 | console then editing the last line. 16 | } 17 | -------------------------------------------------------------------------------- /man/clip_2_df.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{clip_2_df} 4 | \alias{clip_2_df} 5 | \title{read clipboard into data frame} 6 | \usage{ 7 | clip_2_df() 8 | } 9 | \value{ 10 | the data frame 11 | } 12 | \description{ 13 | Read windows/mac/linux clipboard, convert csv into data frame, open data 14 | viewer, write markdown table back to clipboard, return the data frame 15 | } 16 | -------------------------------------------------------------------------------- /man/flip_windows_path.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{flip_windows_path} 4 | \alias{flip_windows_path} 5 | \title{Flip windows path} 6 | \usage{ 7 | flip_windows_path() 8 | } 9 | \description{ 10 | \code{flip_windows_path} convert "\" in clipboard to "/", then paste into 11 | current cursor position 12 | } 13 | -------------------------------------------------------------------------------- /man/format_console.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{format_console} 4 | \alias{format_console} 5 | \title{Convert Console Print Out to Script} 6 | \usage{ 7 | format_console() 8 | } 9 | \description{ 10 | Read console input and output from clipboard, format as script(remove the > 11 | prompt, convert output as comments). 12 | } 13 | \details{ 14 | Formated script is written back to clipboard, and inserted to current cursor 15 | location 16 | } 17 | -------------------------------------------------------------------------------- /man/printc.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/RMD_utils.R, R/misc.R 3 | \name{printc} 4 | \alias{printc} 5 | \title{Print vector literal code for a string Vector} 6 | \usage{ 7 | printc(x) 8 | 9 | printc(x) 10 | } 11 | \arguments{ 12 | \item{x}{a vector holding items x1, x2, ...} 13 | } 14 | \value{ 15 | string of "c("x1", "x2")" 16 | } 17 | \description{ 18 | Sometimes we need to write a vector which values come from data, which means 19 | to manually copy values and write into a vector definition. This function 20 | will take a vector (often came from data, like all unique values of a 21 | category column) and print a vector definition string, then it can be copied 22 | to source code. 23 | 24 | It's often needed to create a vector literally even the vector can be 25 | obtained in code. For example a sbuset of column names can be get with number 26 | index, but it's not safe to use number index in code. Use this function to 27 | turn the output of number index into literal format, then you can copy the 28 | output to code. 29 | } 30 | \examples{ 31 | printc(letters[1:3]) 32 | printc(names(mtcars)[1:3]) 33 | 34 | } 35 | -------------------------------------------------------------------------------- /man/profv.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{profv} 4 | \alias{profv} 5 | \title{profvis selected} 6 | \usage{ 7 | profv() 8 | } 9 | \description{ 10 | profvis selected code in console without changing source code. RStudio have a 11 | similar builtin menu in editor toolbar, but that only works with .R script, 12 | not working in .Rmd or unsaved file. 13 | } 14 | -------------------------------------------------------------------------------- /man/render_addin.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/str_render.R 3 | \name{render_addin} 4 | \alias{render_addin} 5 | \title{View obj/variable \code{str()} output in html view by addin} 6 | \usage{ 7 | render_addin() 8 | } 9 | \description{ 10 | View obj/variable \code{str()} output in html view by addin 11 | } 12 | -------------------------------------------------------------------------------- /man/render_rmd.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{render_rmd} 4 | \alias{render_rmd} 5 | \title{Render RMarkdown in global environment} 6 | \usage{ 7 | render_rmd() 8 | } 9 | \description{ 10 | Knit document will start from scratch, this will use global environment 11 | instead. So you don't have to run some expensive operations like read huge 12 | file from disk every time in rendering. 13 | } 14 | -------------------------------------------------------------------------------- /man/render_str.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/str_render.R 3 | \name{render_str} 4 | \alias{render_str} 5 | \title{Render str() output in html with style} 6 | \usage{ 7 | render_str(obj) 8 | } 9 | \arguments{ 10 | \item{obj}{the object/variable to be inspected} 11 | } 12 | \description{ 13 | If the output of \code{str()} is long, user can easily get lost in scrolling. 14 | This function convert output into html with tag classes and render it with 15 | style. You can customize the style by experimenting with chrome/firefox 16 | developer tools first, then save to css. The css is located in 17 | \code{system.file("css", "str_render.css", package = "mischelper")} 18 | } 19 | \details{ 20 | By default a browser window will be opened because it is intended for long 21 | output. The \code{info} label and the name of input object are added for clarity. Since they are not part of original \code{str()} output, \code{(info)} are used. 22 | } 23 | -------------------------------------------------------------------------------- /man/scan_externals.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{scan_externals} 4 | \alias{scan_externals} 5 | \title{Scan External Functions} 6 | \usage{ 7 | scan_externals() 8 | } 9 | \value{ 10 | A data.table of functions. 11 | } 12 | \description{ 13 | If some code was selected, scan selected code, otherwise scan current file. 14 | Result table will also be opened in RStudio data viewer. The current file 15 | must be able to be sourced without error to be scanned, so packages or data 16 | need to be prepared before scanning. 17 | } 18 | -------------------------------------------------------------------------------- /man/scan_file.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{scan_file} 4 | \alias{scan_file} 5 | \title{Scan Source File For External Functions} 6 | \usage{ 7 | scan_file(code_file, organize = TRUE) 8 | } 9 | \arguments{ 10 | \item{code_file}{The path of source file} 11 | 12 | \item{organize}{If FALSE, return table of `fun`, `fun_inside`; If TRUE, return 13 | table of `fun_inside`, `list of fun`} 14 | } 15 | \value{ 16 | Result table 17 | } 18 | \description{ 19 | The file must be able to be sourced without error to be scanned, so packages 20 | or data need to be prepared before scanning. 21 | } 22 | -------------------------------------------------------------------------------- /man/scan_fun.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{scan_fun} 4 | \alias{scan_fun} 5 | \title{Scan Function Object For External Functions} 6 | \usage{ 7 | scan_fun(fun_obj) 8 | } 9 | \arguments{ 10 | \item{fun_obj}{} 11 | } 12 | \value{ 13 | result table 14 | } 15 | \description{ 16 | Scan Function Object For External Functions 17 | } 18 | \examples{ 19 | mischelper::scan_fun(sort) 20 | } 21 | -------------------------------------------------------------------------------- /man/time_it.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{time_it} 4 | \alias{time_it} 5 | \title{time selected code} 6 | \usage{ 7 | time_it() 8 | } 9 | \description{ 10 | Easier way to measure time cost of expression or selected code. Underthehood 11 | it's just microbenchmark run once instead of 10. 12 | } 13 | -------------------------------------------------------------------------------- /man/unwrap_extra_blank.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{unwrap_extra_blank} 4 | \alias{unwrap_extra_blank} 5 | \title{Unwrap with blank line} 6 | \usage{ 7 | unwrap_extra_blank() 8 | } 9 | \description{ 10 | Remove unneeded hard line breaks of text in clipboard, insert extra blank 11 | line between paragraphs, then paste into current cursor position. 12 | } 13 | \details{ 14 | Need this helper because RStudio Addin doesn't support function with 15 | parameters. 16 | } 17 | -------------------------------------------------------------------------------- /man/view_list.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/misc.R 3 | \name{view_list} 4 | \alias{view_list} 5 | \title{View selected list with listviewer} 6 | \usage{ 7 | view_list() 8 | } 9 | \description{ 10 | Select a list, view it with listviewer in viewer pane. This is less relevant 11 | now with RStudio data viewer started to support list. 12 | } 13 | -------------------------------------------------------------------------------- /mischelper.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: knitr 13 | LaTeX: pdfLaTeX 14 | 15 | StripTrailingWhitespace: Yes 16 | 17 | BuildType: Package 18 | PackageUseDevtools: Yes 19 | PackageInstallArgs: --no-multiarch --with-keep.source 20 | PackageRoxygenize: rd,collate,namespace,vignette 21 | --------------------------------------------------------------------------------