Skip to content

Commit b4803a2

Browse files
committed
Integrate bunch of stuff from #29
Lint + add leading . as acceptable R variable name. Add home repo support to link_gh_issue Add active_rs_doc_nav() Add support for renaming md files Improve `o_is_test_name()` to exclude empty tests Improve `o_is_todo_fixme()` to include book. Rename `o_is_object_title()` to `o_is_tab_plot_title() Rename test files Remove some variables from outline...
1 parent c97b1b1 commit b4803a2

37 files changed

+739
-563
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ Suggests:
4242
Config/testthat/edition: 3
4343
Encoding: UTF-8
4444
Roxygen: list(markdown = TRUE)
45-
RoxygenNote: 7.3.1
45+
RoxygenNote: 7.3.1.9000

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ S3method(print,outline_report)
44
export(active_rs_doc)
55
export(active_rs_doc_copy)
66
export(active_rs_doc_delete)
7+
export(active_rs_doc_nav)
78
export(arrange_identity)
89
export(browse_pkg)
910
export(case_if_any)

NEWS.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
* `proj_list()` / `proj_switch()` no longer opens a nested project if looking for `"pkgdown"`, `"testthat"`, etc.
44

5+
* `active_rs_doc_nav()` is a new function to navigate to files pane location.
6+
7+
`active_rs_doc_copy()` now accepts copying md and qmd files too and no longer allows renaming Rprofile.
8+
9+
* `proj_file()` is better.
10+
11+
* Local GitHub issues show better in outline.
12+
13+
* `file_outline()` detects better plot titles and section titles.
14+
515
# reuseme 0.0.2
616

717
* `complete_todo()` no longer deletes the full line. It only deletes what it says it deletes (#27).

R/browse-pkg.R

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ browse_pkg <- function(package = NULL,
8888
"reference"
8989
)
9090

91-
pkgdown <- stringr::str_remove(pkgdown, "/$")
91+
pkgdown <- sub("/$", "", pkgdown)
9292
pkgdown_tabs_url <- paste0(pkgdown, "/", pkgdown_tabs, "/")
93-
if (stringr::str_detect(pkgdown, "r-lib.org|tidyverse.org|tidymodels.org") && !stringr::str_detect(pkgdown, "github.com")) {
93+
if (grepl("r-lib.org|tidyverse.org|tidymodels.org", pkgdown) && !grepl("github.com", pkgdown, fixed = TRUE)) {
9494
# known packages with dev enabled.
9595
pkgdown_tabs_url[1] <- paste0(pkgdown, "/dev/news")
9696
}

R/dplyr-plus.R

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -240,10 +240,10 @@ filter_if_any <- function(.data, ..., .by = NULL, .keep_new_var = FALSE) {
240240
if (all(purrr::map_lgl(variables[, 1:n_var], is.logical))) {
241241
res <- dplyr::filter(variables, dplyr::if_any(.cols = seq_len(n_var)), .by = {{ .by }})
242242

243-
if (!.keep_new_var) {
244-
res <- res[-seq_len(n_var)]
245-
} else {
243+
if (.keep_new_var) {
246244
cli::cli_warn("You have modified the original data")
245+
} else {
246+
res <- res[-seq_len(n_var)]
247247
}
248248

249249
return(res)
@@ -310,14 +310,13 @@ extract_cell_value <- function(data, var, filter, name = NULL, length = NULL, un
310310
if (unique) {
311311
res2 <- unique_named(res2)
312312
}
313-
if (!is.null(length)) {
313+
314+
if (!is.null(length) && !rlang::has_length(res2, length)) {
314315
# TODO use `check_length()` when implemented. r-lib/rlang#1618
315-
if (!rlang::has_length(res2, length)) {
316-
cli::cli_abort(c(
317-
"Expected an output of {length}",
318-
"Got an output of {length(res2)}"
319-
))
320-
}
316+
cli::cli_abort(c(
317+
"Expected an output of {length}",
318+
"Got an output of {length(res2)}"
319+
))
321320
}
322321

323322
res2

R/escape-inline-markup.R

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,12 @@ escape_markup <- function(x) {
2828
is_markup_okay <- is_bracket & stringr::str_detect(x, "\\{\\.[:alpha:]+[^\\{]") & !local_var_ref_in_markup & !is_markup_incorrect(x)
2929

3030
if (all(is_markup_okay) && !any(local_var_ref_in_markup)) {
31-
x[is_left_bracket & !is_bracket] <- stringr::str_replace_all(x[is_left_bracket & !is_bracket], "\\{", "{{")
32-
x[is_right_bracket & !is_bracket] <- stringr::str_replace_all(x[is_right_bracket & !is_bracket], "\\}", "}}")
31+
x[is_left_bracket & !is_bracket] <- gsub("{", "{{", x[is_left_bracket & !is_bracket], fixed = TRUE)
32+
x[is_right_bracket & !is_bracket] <- gsub("}", "}}", x[is_right_bracket & !is_bracket], fixed = TRUE)
3333
return(x)
3434
}
3535
# replace fn arg {fn}(arg) -> fn({arg})
36-
valid_r_variable_regex <- "[:alpha:][[:alpha:]\\_\\d]+"
36+
valid_r_variable_regex <- "\\.?[:alpha:][[:alpha:]\\_\\d]+"
3737
x <- stringr::str_replace_all(
3838
x,
3939
paste0("\\{(", valid_r_variable_regex, ")\\}\\("),
@@ -59,16 +59,20 @@ escape_markup <- function(x) {
5959
# )
6060
# replace variables
6161

62+
x <- replace_r_var(x)
6263

64+
x[is_left_bracket & !is_bracket] <- gsub("{", "{{", x[is_left_bracket & !is_bracket], fixed = TRUE)
65+
x[is_right_bracket & !is_bracket] <- gsub("}", "}}", x[is_right_bracket & !is_bracket], fixed = TRUE)
6366

64-
x <- replace_r_var(x)
67+
# whisker replacement {{{ vignette_title }}} in usethis by vignette_title
68+
x <- stringr::str_replace_all(x, "\\{\\{?\\{?(\\s?[^\\}]+) \\}?\\}?\\}", "\\1")
6569

66-
x[is_left_bracket & !is_bracket] <- stringr::str_replace_all(x[is_left_bracket & !is_bracket], "\\{", "{{")
67-
x[is_right_bracket & !is_bracket] <- stringr::str_replace_all(x[is_right_bracket & !is_bracket], "\\}", "}}")
70+
# make `{` and `}` work
71+
x <- stringr::str_replace_all(x, c("`\\{`" = "`{{`", "`\\}`" = "`}}`"))
6872

6973
if (any(stringr::str_detect(x, "\\{{3,10}"))) {
7074
# more than 3 {
71-
cli::cli_abort("internal errror. Did not transform string correctly.")
75+
rlang::abort(c("internal error. Did not transform string correctly.", x))
7276
}
7377
x
7478
}
@@ -84,7 +88,7 @@ escape_markup <- function(x) {
8488
#' is_markup_incorrect("{.file {gt}}")
8589
is_markup_incorrect <- function(x) {
8690
# no match of single { or }
87-
valid_r_variable_regex <- "[:alpha:][[:alpha:]\\_\\d]+"
91+
valid_r_variable_regex <- "\\.?[:alpha:][[:alpha:]\\_\\d]+"
8892

8993
stringr::str_detect(x, pattern = paste0("(?<!\\{)\\{", valid_r_variable_regex, "\\}(?!\\})")) |
9094
stringr::str_detect(x, pattern = "\\]\\(\\{.+\\}\\)\\}") |
@@ -107,7 +111,7 @@ cli_escape <- function(x) {
107111
#' # example code
108112
#' replace_r_var("{gt_var} in {{gt_var}} in gt_var in {.file {gt_var}} and {my1e}.")
109113
replace_r_var <- function(x) {
110-
valid_r_variable_regex <- "[:alpha:][[:alpha:]\\_\\d]+"
114+
valid_r_variable_regex <- "\\.?[:alpha:][[:alpha:]\\_\\d]+"
111115
regexp <- paste0("(?<!\\{)\\{(", valid_r_variable_regex, ")\\}(?!\\})")
112116
stringr::str_replace_all(
113117
x, regexp, "\\{\\{\\1\\}\\}"
@@ -118,8 +122,8 @@ replace_r_var <- function(x) {
118122
#'
119123
#' @examples
120124
#' replace_r_var("i{gt_var} in {{gt_var}} in gt_var in {.file {gt_var}}.")
121-
#' # last instance taken care of with escape_markup with a different strategy
122125
#' #> "{{gt_var}} in {{gt_var}} in gt_var in {.file {gt_var}}."
126+
#' # last instance taken care of with escape_markup with a different strategy
123127
#' escape_markup("{gt_var} in {{gt_var}} in gt_var in {.file {gt_var}}.")
124128
#' #> "{{gt_var}} in {{gt_var}} in gt_var in {.file gt_var}."
125129
NULL

R/files-conflicts.R

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ solve_file_name_conflict <- function(files, regex, dir = ".", extra_msg = NULL,
118118
cli::cli_inform
119119
}
120120
# Remove duplicated Found x references
121-
which_bullet_to_replace <- stringr::str_subset(extra_msg, "Found references to", negate = TRUE)
121+
which_bullet_to_replace <- stringr::str_subset(extra_msg, stringr::fixed("Found references to"), negate = TRUE)
122122
# possibly just move up our
123123
# extra_msg[i] <-
124124
f_inform(c(
@@ -152,8 +152,8 @@ get_referenced_files <- function(files) {
152152
stringr::str_subset(pattern = "file.[(exist)|(delete)]|glue\\:\\:glue|unlink", negate = TRUE) |> # don't detect where we test for existence of path or construct a path with glue
153153
stringr::str_subset(pattern = "[(regexp)|(pattern)]\\s\\=.*\".*[:alpha:]\"", negate = TRUE) |> # remove regexp = a.pdf format
154154
stringr::str_subset(pattern = "grepl?\\(|stringr|g?sub\\(", negate = TRUE) |> # avoid regexp
155-
stringr::str_subset(pattern = "nocheck", negate = TRUE) |> # remove nocheck and unlink statements (refers to deleted files anywa)
156-
stringr::str_subset("\"") |>
155+
stringr::str_subset(pattern = stringr::fixed("nocheck"), negate = TRUE) |> # remove nocheck and unlink statements (refers to deleted files anywa)
156+
stringr::str_subset(stringr::fixed("\"")) |>
157157
stringr::str_trim() |>
158158
stringr::str_extract_all("\"[^\"]+\"") |>
159159
unlist() |>
@@ -163,7 +163,7 @@ get_referenced_files <- function(files) {
163163
stringr::str_subset(pattern = "tmp|temp", negate = TRUE) |> # remove common file names that are not very nice
164164
stringr::str_subset(pattern = "https?", negate = TRUE) |> # doesn't check for files read online.
165165
stringr::str_subset(pattern = "\\@.+\\.", negate = TRUE) |> # email addresses or containing @
166-
stringr::str_subset(pattern = "_fichiers/", negate = TRUE) |> # manually remove false positive
166+
stringr::str_subset(pattern = stringr::fixed("_fichiers/"), negate = TRUE) |> # manually remove false positive
167167
stringr::str_subset(pattern = "\n", negate = TRUE) |> # remove things with line breaks
168168
stringr::str_subset(pattern = "^\\.[:alpha:]{1,4}$", negate = TRUE) |> # remove reference to only file extensions
169169
stringr::str_subset(pattern = "\\.\\d+$", negate = TRUE) |> # remove 0.000 type

R/markup.R

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
#'
1010
#' Afterwards, we use [markup_href()] to create a cli link
1111
#' @param x A string, usually lines of files that contains issue numbers.
12-
#'
12+
#' @param home_repo Optional, but if supplied, will be stripped.
1313
#' @return A markdown link linked issue to GitHub issue
1414
#' @export
1515
#' @keywords internal
1616
#' @family inline markup internal helpers
1717
#' @examples
1818
#' link_gh_issue(c("We really need rstudio/gt#1469 to be fixed."))
19-
link_gh_issue <- function(x) {
19+
link_gh_issue <- function(x, home_repo = NULL) {
2020
# Return early if no issue pattern is detected.
2121
regex_gh_issue <- common_regex("gh_issue")
2222

@@ -37,11 +37,73 @@ link_gh_issue <- function(x) {
3737
regex_gh_issue,
3838
paste0("[\\1#\\2](https://github.com/\\1/issues/\\2)")
3939
)
40+
if (!is.null(home_repo)) {
41+
x_changed <- gsub(
42+
paste0(home_repo, "#"),
43+
"#",
44+
x_changed
45+
)
46+
}
4047

4148
x[has_gh_issue] <- x_changed
4249
x
4350
}
51+
# transforms (#xx) to (org/repo#xx)
52+
link_local_gh_issue <- function(x, repo_home) {
53+
gsub(
54+
# max 99999 issues.
55+
pattern = "\\((#\\d{1,5})\\)",
56+
paste0("(", repo_home, "\\1)"),
57+
x
58+
)
59+
}
60+
find_pkg_org_repo <- function(dir_common = NULL, file = NULL) {
61+
rlang::local_interactive(FALSE)
62+
withr::local_options("usethis.quiet" = TRUE)
63+
if (!is.null(dir_common)) {
64+
pkg_path <- tryCatch(
65+
rprojroot::find_package_root_file(path = dir_common),
66+
error = function(e) {
67+
# cli::cli_inform("Could not detect path.")
68+
NULL
69+
}
70+
)
71+
if (is.null(pkg_path)) {
72+
return(NULL)
73+
}
74+
gh_url <- tryCatch(
75+
usethis::browse_github(basename(pkg_path)),
76+
error = function(e) {
77+
# TODO possibly look into checking desc::desc_get("BugReports", "~/path/to/DESCRIPTION")
78+
# cli::cli_abort("didn't find a way to do what is required.", parent = e)
79+
NULL
80+
}
81+
)
82+
if (is.null(gh_url)) {
83+
return(NULL)
84+
}
85+
org_repo_found <- sub(".+github.com/|.+gitlab.com/", "", gh_url)
86+
return(org_repo_found)
87+
}
88+
89+
if (!is.null(file)) {
90+
pkg_path <- withCallingHandlers(
91+
rprojroot::find_package_root_file(path = file),
92+
error = function(e) {
93+
# cli::cli_inform("Could not detect path.")
94+
NULL
95+
}
96+
)
4497

98+
gh_url <- usethis::browse_github(basename(pkg_path))
99+
org_repo_found <- sub(".+github.com/|.+gitlab.com/", "", gh_url)
100+
} else {
101+
org_repo_found <- NULL
102+
}
103+
if (is.null(org_repo) && is.null(org_repo_found)) {
104+
cli::cli_abort("No way to discover URL.")
105+
}
106+
}
45107
#' Create a cli href with a markdown link
46108
#'
47109
#' Transforms `[text](url)` -> `{.href [text](url)}`

R/open.R

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ open_rs_doc <- function(path, line = -1L, col = -1L, move_cursor = TRUE) {
3939
#' @name open_rs_doc
4040
#' @export
4141
active_rs_doc <- function() {
42-
if (!interactive() && !rstudioapi::isAvailable()) {
42+
if (!interactive() && !is_rstudio()) {
4343
return("Non-existing doc")
4444
}
45-
if (!rstudioapi::isAvailable()) {
45+
if (!is_rstudio()) {
4646
cli::cli_abort("Not in RStudio.")
4747
}
4848
unsaved_doc <- tryCatch(rstudioapi::documentPath(), error = function(e) TRUE)
@@ -79,20 +79,21 @@ active_rs_doc_copy <- function(new = NULL, ..., old = NULL) {
7979
cli::cli_abort("Unsaved document, focus on the saved doc you want to save.")
8080
}
8181

82-
if (!fs::path_ext(old) %in% c("R", "qmd", "Rmd")) {
83-
cli::cli_abort("Only R docs for now")
82+
if (!fs::path_ext(old) %in% c("md", "R", "qmd", "Rmd")) {
83+
cli::cli_abort("Only R and md docs for now")
8484
}
8585
old_path_file <- fs::path_ext_remove(fs::path_file(old))
86-
if (stringr::str_detect(old, "r-profile|Rprofile")) {
86+
87+
if (grepl("r-profile|Rprofile", old)) {
8788
cli::cli_abort("Attempting to copy Rprofile (focus on the document you want)")
8889
}
8990
if (is.null(new)) {
9091
new_name <- paste0(old_path_file, "-new")
9192
} else {
92-
new_name <- stringr::str_remove(new, "\\.R|\\.qmd|\\.Rmd$")
93+
new_name <- sub("\\.R|\\.[Rq]?md$", "", new)
9394
}
9495
# Hack to ensure file/file.R will be correctly renamed.
95-
new_path <- stringr::str_replace(old, paste0(old_path_file, "\\."), paste0(new_name, "."))
96+
new_path <- sub(paste0(old_path_file, "\\."), paste0(new_name, "."), old)
9697

9798
copied <- file.copy(old, new_path, overwrite = FALSE)
9899
if (copied) {
@@ -124,7 +125,7 @@ active_rs_doc_copy <- function(new = NULL, ..., old = NULL) {
124125
#' @examplesIf FALSE
125126
#' active_rs_doc_delete()
126127
active_rs_doc_delete <- function() {
127-
if (!rlang::is_interactive() || !rstudioapi::isAvailable()) {
128+
if (!rlang::is_interactive() || !is_rstudio()) {
128129
cli::cli_abort(c("Can't delete files in non-interactive sessions."))
129130
}
130131
doc <- active_rs_doc()
@@ -140,7 +141,7 @@ active_rs_doc_delete <- function() {
140141
if (fs::is_dir(elems$full_path)) {
141142
cli::cli_abort("Must be a file", .internal = TRUE)
142143
}
143-
if (interactive() && rstudioapi::isAvailable()) {
144+
if (interactive() && is_rstudio()) {
144145
rstudioapi::documentSave()
145146
}
146147
cli::cli_inform(c(
@@ -211,7 +212,7 @@ active_rs_doc_delete <- function() {
211212
parent_dir <- fs::path_file(fs::path_dir(elems$full_path))
212213

213214
if (grepl("^temp", fs::path_file(elems$rel_path)) ||
214-
(!parent_dir %in% c("tests", "testthat") && grepl("^test-", fs::path_file(elems$rel_path)))) {
215+
(!parent_dir %in% c("tests", "testthat") && grepl("^test-", fs::path_file(elems$rel_path)))) {
215216
reasons_deleting <- c(reasons_deleting, "it has the temp- prefix.")
216217
will_delete <- append(will_delete, TRUE)
217218
}
@@ -256,7 +257,7 @@ active_rs_doc_delete <- function() {
256257
cli::cli_inform(c(
257258
"v" = "Deleted the active document {.val {elems$rel_path}} because {reasons_deleting}.",
258259
# FIXME (upstream) the color div doesn't go all the way r-lib/cli#694
259-
"i" = paste(cli::col_grey("The deleted file"), "{.path {elems$full_path}}", cli::col_grey("contents are returned invisibly in case you need them."))
260+
"i" = paste(cli::col_grey("The deleted file"), "{.path {elems$full_path}}", cli::col_grey("contents are returned invisibly in case you need them."))
260261
))
261262
contents <- readLines(elems$full_path, encoding = "UTF-8")
262263
fs::file_delete(elems$full_path)
@@ -361,3 +362,36 @@ normalize_proj_and_path <- function(path, call = caller_env()) {
361362
full_path = full_path
362363
)
363364
}
365+
366+
#' Open Files Pane at current document location
367+
#'
368+
#' Easily navigate to active file document.
369+
#'
370+
#' Wrapper around [executeCommand("activateFiles")][rstudioapi::executeCommand()] +
371+
#' [rstudioapi::filesPaneNavigate()] + [rstudioapi::getActiveDocumentContext()]
372+
#'
373+
#' @param path A path to file to navigate to (default active document).
374+
#'
375+
#' @returns NULL, called for its side effects.
376+
#' @export
377+
active_rs_doc_nav <- function(path = active_rs_doc()) {
378+
if (!is_rstudio() || !interactive()) {
379+
cli::cli_abort("Must use in RStudio interactive sessions.")
380+
}
381+
if (is.null(path)) {
382+
cli::cli_abort("Can't navigate to an unsaved file!")
383+
}
384+
if (fs::is_file(path)) {
385+
dir <- fs::path_dir(path)
386+
} else if (fs::is_dir(path)) {
387+
dir <- path
388+
} else {
389+
cli::cli_abort("{.arg path} must be an existing file or directory.")
390+
}
391+
rstudioapi::executeCommand("activateFiles")
392+
rstudioapi::filesPaneNavigate(dir)
393+
cli::cli_inform(c(
394+
"v" = "Navigated to {.path {dir}} in RStudio Files Pane."
395+
))
396+
invisible()
397+
}

0 commit comments

Comments
 (0)