diff --git a/DESCRIPTION b/DESCRIPTION index 49d0b160..aa83a184 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -20,6 +20,7 @@ LazyData: true Depends: R (>= 3.6) Imports: + cli, fs (>= 1.6.2), fuj (>= 0.1.1), magrittr (>= 2.0.1), diff --git a/NEWS.md b/NEWS.md index 26118bd0..1a24dda4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,8 +1,9 @@ # mark (development version) * improvements in `todos()` and `fixmes()` - * File extension an now be set [#170](https://github.com/jmbarbone/mark/issues/170), which by default includes `qmd` ([#163]((https://github.com/jmbarbone/mark/issues/104))) and `py` files + * File extension can now be set [#170](https://github.com/jmbarbone/mark/issues/170), which by default includes `qmd` ([#163]((https://github.com/jmbarbone/mark/issues/163))) and `py` files * new parameter `ignore` to ignore any files + * file paths and line numbers can now be _clicked_ within RStudio ([#171]((https://github.com/jmbarbone/mark/issues/171))) # mark 0.6.0 diff --git a/R/todos.R b/R/todos.R index bbef68ad..aeb22e2c 100644 --- a/R/todos.R +++ b/R/todos.R @@ -92,18 +92,22 @@ do_todo <- function( # nolint: cyclocomp_linter. ignore = NULL, ... ) { - if (missing(path) || length(path) != 1 || !is.character(path)) { + if ( + missing(path) || + length(path) != 1 || + !is.character(path) + ) { stop(cond_do_todo_path()) } - stopifnot(file.exists(path), length(text) == 1L) + stopifnot(fs::file_exists(path), length(text) == 1L) ls <- list(...) if (is_dir(path)) { if ( !has_char(path) || - !(force || length(fs::dir_ls(path, regexp = "\\.Rproj$"))) + !(force || any(tolower(tools::file_ext(fs::dir_ls(path))) == "rproj")) ) { message("Did not search for TODOS in ", norm_path(path)) return(invisible(NULL)) @@ -130,10 +134,10 @@ do_todo <- function( # nolint: cyclocomp_linter. finds <- lapply( lapply(files, readLines, warn = FALSE), function(x) { - ind <- grep( - pattern = sprintf("[#]\\s+%s[:]?\\s+", toupper(text)), - x = x - ) + x <- iconv(x, to = "UTF-8") + Encoding(x) <- "UTF-8" + regex <- sprintf("[#]\\s+%s[:]?\\s+", toupper(text)) + ind <- grep(pattern = regex, x = x) quick_dfl(ind = ind, todo = x[ind]) } ) @@ -151,6 +155,7 @@ do_todo <- function( # nolint: cyclocomp_linter. set_names(as.list(Reduce(rbind, finds)), c("line", text)) )) + out[["file"]] <- fs::path_rel(out[["file"]], getwd()) out <- out[, c("line", "file", text)] ind <- tolower(tools::file_ext(out[["file"]])) %in% c("md", "qmd", "rmd") @@ -186,39 +191,46 @@ do_todo <- function( # nolint: cyclocomp_linter. print.todos_df <- function(x, ...) { # TODO Add a limit for number of TODOs to show? type <- attr(x, "todos_type") - - n <- max(nchar(x[["line"]]), 0) - w <- getOption("width") - n - 3 # 4?? - pad <- collapse0(rep(" ", n + 3)) - pat <- sprintf("[%%%i.i]", n) - - splits <- split(x, x[["file"]]) - nm <- names(splits) - - cat0(sprintf("Found %d %s:\n", nrow(x), toupper(type))) - - for (i in seq_along(splits)) { - catln( - collapse0(pad, crayon_blue(nm[i])), - apply( - splits[[i]][, c("line", type)], - 1, - function(xi) { - paste( - crayon_blue(sprintf(pat, as.integer(xi[1]))), - if (nchar(xi[2]) > w) { - # TODO consider wrapping with respect to the line number? - collapse0(substr(xi[2], 1, max(1, w - 6)), " [...]") - } else { - xi[2] - } - ) - } - ) - ) + catln(sprintf("Found %d %s(s):", nrow(x), toupper(type))) + + chunks <- split(x, x[["file"]]) + nms <- names(chunks) + + n <- max(nchar(x$line)) + pad <- strrep("\u00a0", n + 3L) + + for (i in seq_along(nms)) { + cli::cli_text(sprintf( + "%s{.file %s}", + pad, + nms[i] + )) + + for (j in seq_len(nrow(chunks[[i]]))) { + cli::cli_text(sprintf( + "{.href [%s](file://%s#%i)} %s", + format_line_number(chunks[[i]][["line"]][j], width = n), + chunks[[i]][["file"]][j], + chunks[[i]][["line"]][j], + string_dots(chunks[[i]][[3L]][j], getOption("width") - (n + 3L)) + )) + } } - invisible(x) + return(invisible(x)) +} + +format_line_number <- function(x, width = 3) { + x <- format(x, width = width) + x <- gsub("\\s", "\u00a0", x) + x <- sprintf("[%s]", x) + crayon_blue(x) +} + +string_dots <- function(x, width = getOption("width")) { + long <- nchar(x) > width + x[long] <- paste(strtrim(x[long], width - 5), "[...]") + x } # conditions -------------------------------------------------------------- diff --git a/tests/testthat/_snaps/todos.md b/tests/testthat/_snaps/todos.md index 48d2642a..da14f5b2 100644 --- a/tests/testthat/_snaps/todos.md +++ b/tests/testthat/_snaps/todos.md @@ -3,8 +3,9 @@ Code todos(path = path) Output - Found 2 TODO: - scripts/todos.R - [3] make x longer - [7] add another example + Found 2 TODO(s): + Message + 'scripts/todos.R' + [3] () make x longer + [7] () add another example diff --git a/tests/testthat/test-todos.R b/tests/testthat/test-todos.R index 750242a9..49435526 100644 --- a/tests/testthat/test-todos.R +++ b/tests/testthat/test-todos.R @@ -2,13 +2,13 @@ test_that("todos() works", { withr::local_options(list(mark.todos.force = TRUE)) path <- test_path("scripts") - file <- test_path("scripts/todos.R") + file <- fs::path(test_path("scripts/todos.R")) res <- todos(path = path) exp <- struct( list( line = c(3L, 7L), - file = rep(file, 2), + file = fs::path(rep(file, 2)), todo = c("make x longer", "add another example") ), c("todos_df", "data.frame"),