🔍 An RStudio addin slash regex utility belt
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

241 lines
6.8KB

  1. #' regexplain_addin
  2. #'
  3. #' @keywords internal
  4. regexplain_addin <- function() {
  5. # Get the document context.
  6. context <- rstudioapi::getActiveDocumentContext()
  7. # Get context text
  8. ctx_text <- context$selection[[1]]$text
  9. # If it is one line and evaluates to something, use that
  10. # Otherwise treat as text
  11. obj <- tryCatch({
  12. if (grepl("\n", ctx_text)) {
  13. ctx_text
  14. } else {
  15. x <- eval(parse(text = ctx_text))
  16. x <- as.character(x)
  17. if (length(x) == 1 && grepl("\n", x))
  18. x <- strsplit(x, "\n")[[1]]
  19. if (length(x) > 10) {
  20. message(ctx_text, " gave ", length(x), " lines, limiting to first 10 unique lines.")
  21. x <- unique(x)
  22. x[1:min(length(x), 10)]
  23. } else x
  24. }
  25. },
  26. error = function(e) {as.character(ctx_text)})
  27. regex_gadget(if (length(obj) && obj != "") obj)
  28. }
  29. #' regexplain gadget
  30. #'
  31. #' @import miniUI
  32. #' @import shiny
  33. #' @export
  34. regex_gadget <- function(text = NULL) {
  35. stopifnot(requireNamespace("miniUI"), requireNamespace("shiny"))
  36. ui <- miniPage(
  37. shiny::includeCSS(system.file("style.css", package = "regexplain")),
  38. gadgetTitleBar(
  39. "regexplain",
  40. right = miniTitleBarButton("done", "Send Regex To Console", TRUE)
  41. ),
  42. miniTabstripPanel(
  43. selected = if (is.null(text)) "Text" else "Regex",
  44. miniTabPanel(
  45. "Text", icon = icon('file-text-o'),
  46. miniContentPanel(
  47. fillCol(
  48. textAreaInputAlt('text',
  49. label = "Text to search or parse",
  50. value = paste(text, collapse = "\n"),
  51. resize = "both",
  52. width = "100%",
  53. height="90%",
  54. placeholder = "Paste, enter, or edit your sample text here.")
  55. )
  56. )
  57. ),
  58. miniTabPanel(
  59. "Regex", icon = icon('terminal'),
  60. miniContentPanel(
  61. fillCol(
  62. flex = c(1, 3),
  63. fillCol(
  64. flex = c(1, 1),
  65. textInputCode('pattern', 'Regex', width = "100%",
  66. placeholder = "Enter regex, single \\ okay"),
  67. checkboxGroupInput(
  68. 'regex_options',
  69. label = "",
  70. inline = TRUE,
  71. width = "90%",
  72. choices = c("Break Lines" = "text_break_lines",
  73. "Ignore Case" = "ignore.case",
  74. "Perl Style" = "perl",
  75. "Fixed" = "fixed",
  76. "Use Bytes" = "useBytes"
  77. # , "Invert" = "invert"
  78. ),
  79. selected = c('text_break_lines')
  80. )
  81. ),
  82. tags$div(
  83. class = "gadget-result",
  84. style = "overflow-y: scroll; height: 100%;",
  85. htmlOutput('result')
  86. )
  87. )
  88. )
  89. ),
  90. miniTabPanel(
  91. "Output", icon = icon("table"),
  92. miniContentPanel(
  93. fillCol(
  94. flex = c(1, 3),
  95. inputPanel(
  96. width = "100%;",
  97. selectInput('regexFn', label = 'Apply Function',
  98. choices = regexFn_choices)
  99. ),
  100. # verbatimTextOutput('output_result', placeholder = TRUE)
  101. tags$pre(
  102. id = "output_result",
  103. class = "shiny-text-output",
  104. style = "overflow-y: scroll; height: 100%;"
  105. )
  106. )
  107. )
  108. ),
  109. miniTabPanel(
  110. "Help", icon = icon("support"),
  111. miniContentPanel(
  112. tags$p("Help will go here.")
  113. )
  114. )
  115. )
  116. )
  117. server <- function(input, output, session) {
  118. rtext <- reactive({
  119. x <- if ('text_break_lines' %in% input$regex_options) {
  120. strsplit(input$text, "\n")[[1]]
  121. } else input$text
  122. x
  123. })
  124. output$result <- renderUI({
  125. if (is.null(rtext())) return(NULL)
  126. if (input$pattern == "") {
  127. return(toHTML(paste('<p class="results">', escape_html(rtext()), "</p>", collapse = "")))
  128. }
  129. tryCatch({
  130. toHTML(
  131. paste(
  132. view_regex(
  133. rtext(),
  134. sanitize_text_input(input$pattern),
  135. ignore.case = 'ignore.case' %in% input$regex_options,
  136. perl = 'perl' %in% input$regex_options,
  137. fixed = 'fixed' %in% input$regex_options,
  138. useBytes = 'useBytes' %in% input$regex_options,
  139. # invert = 'invert' %in% input$regex_options,
  140. render = FALSE,
  141. escape = TRUE),
  142. collapse = ""
  143. )
  144. )
  145. },
  146. error = function(e) {
  147. toHTML(paste0("<pre class='alert alert-danger' style='padding: 4px; margin-top: 1px; margin-bottom: 4px;'>", paste(e$message, collapse = "<br>"), "</pre>"),
  148. paste('<p class="results">', escape_html(rtext()), "</p>", collapse = ""))
  149. })
  150. })
  151. output$output_result <- renderPrint({
  152. req(input$regexFn)
  153. regexPkg <- get_pkg_namespace(input$regexFn)
  154. regexFn <- getFromNamespace(input$regexFn, regexPkg)
  155. x <- if (regexPkg == "base") {
  156. regexFn(input$pattern, rtext())
  157. } else if (regexPkg == "stringr") {
  158. regexFn(rtext(), input$pattern)
  159. } else {
  160. "Um. Not sure how I got here."
  161. }
  162. # if (inherits(x, 'logical') || inherits(x, 'character')) {
  163. # if (length(x) < 25) print(x) else print(head(x, 25))
  164. # } else if (inherits(x, 'matrix') | inherits(x, "data.frame")) {
  165. # if (nrow(x) < 15) { print(x)
  166. # } else glimpse(x)
  167. # } else {
  168. # str(x, max.level = 3)
  169. # }
  170. print(x)
  171. })
  172. observeEvent(input$done, {
  173. # browser()
  174. if (input$pattern != "") {
  175. pattern <- paste0('regex <- "', escape_backslash(sanitize_text_input(input$pattern)), '"')
  176. rstudioapi::sendToConsole(pattern, FALSE)
  177. }
  178. stopApp()
  179. })
  180. observeEvent(input$cancel, {
  181. stopApp()
  182. })
  183. }
  184. viewer <- shiny::paneViewer(700)
  185. runGadget(ui, server, viewer = viewer)
  186. }
  187. sanitize_text_input <- function(x) {
  188. x <- gsub("(“|”)", '"', x)
  189. x <- gsub("‘|’", "'", x)
  190. x
  191. }
  192. toHTML <- function(...) {
  193. x <- paste(..., collapse = "")
  194. x <- gsub("\n", "\\\\n", x)
  195. x <- gsub("\t", "\\\\t", x)
  196. x <- gsub("\r", "\\\\r", x)
  197. HTML(x)
  198. }
  199. regexFn_choices <- list(
  200. "Choose a function" = "",
  201. base = c(
  202. "grep",
  203. "grepl",
  204. "regexpr",
  205. "gregexpr",
  206. "regexec"
  207. ),
  208. stringr = c(
  209. "str_detect",
  210. "str_locate",
  211. "str_locate_all",
  212. "str_extract",
  213. "str_extract_all",
  214. "str_match",
  215. "str_match_all",
  216. "str_split"
  217. )
  218. )
  219. get_pkg_namespace <- function(fn) {
  220. x <- names(purrr::keep(regexFn_choices, ~ (fn %in% .)))
  221. if (length(x) > 1) warning(fn, " matches multiple functions in regexFn_choices, please review.")
  222. x
  223. }