Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/cliPublic

Commita3cef9f

Browse files
authored
Merge pull request#744 from r-lib/configurable-file-hyperlinks
2 parents007c388 +79bbe8f commita3cef9f

File tree

6 files changed

+288
-26
lines changed

6 files changed

+288
-26
lines changed

‎DESCRIPTION‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: cli
22
Title: Helpers for Developing Command Line Interfaces
3-
Version: 3.6.3.9001
3+
Version: 3.6.3.9002
44
Authors@R: c(
55
person("Gábor", "Csárdi", , "csardi.gabor@gmail.com", role = c("aut", "cre")),
66
person("Hadley", "Wickham", role = "ctb"),

‎NEWS.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#cli (development version)
22

3-
* Theformat of theURIpart of "run", "help" and"vignette" hyperlinks can now
4-
be configured via options and env vars (@jennybc,#739).
3+
* The URIgenerated for`.file`,`.run`,`.help` and`.vignette` hyperlinks can
4+
nowbe configured via options and env vars (@jennybc,#739,#744).
55

66
*`cli_progress_bar()` now accepts`total` = Inf or -Inf which mimics the behavior of when`total` is NA.
77

‎R/ansi-hyperlink.R‎

Lines changed: 80 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -74,39 +74,98 @@ make_link_file <- function(txt) {
7474
linked<- grepl("\007|\033\\\\",txt)
7575
ret[!linked]<- vcapply(which(!linked),function(i) {
7676
params<- parse_file_link_params(txt[i])
77+
link<- construct_file_link(params)
7778
style_hyperlink(
7879
txt[i],
79-
paste0(abs_path(params$path),params$suffix),
80-
params=params$params
80+
link$url,
81+
params=link$params
8182
)
8283
})
8384
ret
8485
}
8586

8687
parse_file_link_params<-function(txt) {
87-
if (grepl(":[0-9]+:[0-9]+$",txt)) {
88-
# path:line:col
89-
path<- sub("^(.*):[0-9]+:[0-9]+$","\\1",txt)
90-
num<- strsplit(sub("^.*:([0-9]+:[0-9]+)$","\\1",txt),":",fixed=TRUE)[[1]]
91-
if (Sys.getenv("R_CLI_HYPERLINK_STYLE")=="iterm") {
92-
list(path=path,params=NULL,suffix= paste0("#",num[1],":",num[2]))
93-
}else {
94-
list(path=path,params= c(line=num[1],col=num[2]))
95-
}
88+
pattern<-"^(?<path>.*?)(?::(?<line>\\d*))?(?::(?<column>\\d*))?$"
89+
matches<- re_match(txt,pattern)
90+
ret<- as.list(matches)
91+
ret[!nzchar(ret)]<-list(NULL)
92+
ret
93+
}
9694

97-
}elseif (grepl(":[0-9]+$",txt)) {
98-
# path:line
99-
path<- sub("^(.*):[0-9]+$","\\1",txt)
100-
num<- sub("^.*:([0-9]+$)","\\1",txt)
101-
if (Sys.getenv("R_CLI_HYPERLINK_STYLE")=="iterm") {
102-
list(path=path,params=NULL,suffix= paste0("#",num))
103-
}else {
104-
list(path=path,params= c(line=num,col="1"))
105-
}
95+
construct_file_link<-function(params) {
96+
fmt<- get_config_chr("hyperlink_file_url_format")
97+
98+
if (is.null(fmt)) {
99+
return(construct_file_link_OG(params))
100+
}
101+
102+
params$path<- sub("^file://","",params$path)
103+
params$path<- path.expand(params$path)
104+
105+
looks_absolute<-function(path) {
106+
grepl("^/",params$path)|| (is_windows()&& grepl("^[a-zA-Z]:",params$path))
107+
}
108+
if (!looks_absolute(params$path)) {
109+
params$path<- file.path(getwd(),params$path)
110+
}
111+
if (!grepl("^/",params$path)) {
112+
params$path<- paste0("/",params$path)
113+
}
114+
115+
res<- interpolate_parts(fmt,params)
116+
list(url=res)
117+
}
106118

119+
# the order of operations is very intentional and important:
120+
# column, then line, then path
121+
# relates to how interpolate_part() works
122+
interpolate_parts<-function(fmt,params) {
123+
res<- interpolate_part(fmt,"column",params$column)
124+
res<- interpolate_part(res,"line",params$line)
125+
interpolate_part(res,"path",params$path)
126+
}
127+
128+
# interpolate a part, if possible
129+
# if no placeholder for part, this is a no-op
130+
# if placeholder exists, but no value to fill, remove placeholder (and everything after it!)
131+
interpolate_part<-function(fmt,part= c("column","line","path"),value=NULL) {
132+
part<- match.arg(part)
133+
re<- glue(
134+
"^(?<before>.*)(?<part>\\{<<<part>>>\\})(?<after>.*?)$",
135+
.open="<<<",.close=">>>"
136+
)
137+
m<- re_match(fmt,re)
138+
139+
if (is.na(m$part)||!nzchar(m$part)) {
140+
return(fmt)
141+
}
142+
143+
if (is.null(value)||!nzchar(value)) {
144+
return(sub("}[^}]*$","}",m$before))
145+
}
146+
147+
paste0(m$before,value,m$after)
148+
}
149+
150+
# handle the iterm and RStudio cases, which predated the notion of configuring
151+
# the file hyperlink format
152+
construct_file_link_OG<-function(params) {
153+
params$path<- abs_path(params$path)
154+
155+
if (Sys.getenv("R_CLI_HYPERLINK_STYLE")=="iterm") {
156+
fmt<-"{path}#{line}:{column}"
157+
res<- interpolate_parts(fmt,params)
158+
return(list(url=res))
159+
}
160+
161+
# RStudio takes line and col via params
162+
loc<-if (is.null(params$line)) {
163+
NULL
107164
}else {
108-
list(path=txt,params=NULL)
165+
list(line=params$line,col=params$column %||%1)
109166
}
167+
168+
list(url=params$path,params=loc)
110169
}
111170

112171
abs_path<-function(x) {

‎R/test.R‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,11 +112,13 @@ test_that_cli <- function(desc, code,
112112
cli.hyperlink_help=links,
113113
cli.hyperlink_run=links,
114114
cli.hyperlink_vignette=links,
115+
cli.hyperlink_file_url_format=NULL,
115116
cli.hyperlink_run_url_format=NULL,
116117
cli.hyperlink_help_url_format=NULL,
117118
cli.hyperlink_vignette_url_format=NULL
118119
)
119120
withr::local_envvar(
121+
R_CLI_HYPERLINK_FILE_URL_FORMAT=NA_character_,
120122
R_CLI_HYPERLINK_RUN_URL_FORMAT=NA_character_,
121123
R_CLI_HYPERLINK_HELP_URL_FORMAT=NA_character_,
122124
R_CLI_HYPERLINK_VIGNETTE_URL_FORMAT=NA_character_
@@ -139,6 +141,7 @@ local_clean_cli_context <- function(.local_envir = parent.frame()) {
139141
cli.hyperlink_run=NULL,
140142
cli.hyperlink_help=NULL,
141143
cli.hyperlink_vignette=NULL,
144+
cli.hyperlink_file_url_format=NULL,
142145
cli.hyperlink_run_url_format=NULL,
143146
cli.hyperlink_help_url_format=NULL,
144147
cli.hyperlink_vignette_url_format=NULL,
@@ -152,6 +155,7 @@ local_clean_cli_context <- function(.local_envir = parent.frame()) {
152155
R_CLI_HYPERLINK_RUN=NA_character_,
153156
R_CLI_HYPERLINK_HELP=NA_character_,
154157
R_CLI_HYPERLINK_VIGNETTE=NA_character_,
158+
R_CLI_HYPERLINK_FILE_URL_FORMAT=NA_character_,
155159
R_CLI_HYPERLINK_RUN_URL_FORMAT=NA_character_,
156160
R_CLI_HYPERLINK_HELP_URL_FORMAT=NA_character_,
157161
R_CLI_HYPERLINK_VIGNETTE_URL_FORMAT=NA_character_,

‎tests/testthat/test-ansi-hyperlink.R‎

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ test_that("unknown hyperlink type", {
232232

233233
test_that("iterm file links", {
234234
withr::local_envvar(R_CLI_HYPERLINK_STYLE="iterm")
235+
withr::local_envvar(R_CLI_HYPERLINK_FILE_URL_FORMAT=NA_character_)
235236
withr::local_options(cli.hyperlink=TRUE)
236237
expect_snapshot({
237238
cli::cli_text("{.file /path/to/file:10}")
@@ -422,3 +423,185 @@ test_that("get_hyperlink_format() delivers custom format", {
422423
expect_equal(get_hyperlink_format("help"),"option{topic}")
423424
expect_equal(get_hyperlink_format("vignette"),"option{vignette}")
424425
})
426+
427+
test_that("parse_file_link_params(), typical input", {
428+
expect_equal(
429+
parse_file_link_params("some/path.ext"),
430+
list(
431+
path="some/path.ext",
432+
line=NULL,
433+
column=NULL
434+
)
435+
)
436+
expect_equal(
437+
parse_file_link_params("some/path.ext:14"),
438+
list(
439+
path="some/path.ext",
440+
line="14",
441+
column=NULL
442+
)
443+
)
444+
expect_equal(
445+
parse_file_link_params("some/path.ext:14:23"),
446+
list(
447+
path="some/path.ext",
448+
line="14",
449+
column="23"
450+
)
451+
)
452+
})
453+
454+
test_that("parse_file_link_params(), weird trailing colons", {
455+
expect_equal(
456+
parse_file_link_params("some/path.ext:"),
457+
list(
458+
path="some/path.ext",
459+
line=NULL,
460+
column=NULL
461+
)
462+
)
463+
expect_equal(
464+
parse_file_link_params("some/path.ext::"),
465+
list(
466+
path="some/path.ext",
467+
line=NULL,
468+
column=NULL
469+
)
470+
)
471+
expect_equal(
472+
parse_file_link_params("some/path.ext:14:"),
473+
list(
474+
path="some/path.ext",
475+
line="14",
476+
column=NULL
477+
)
478+
)
479+
})
480+
481+
test_that("interpolate_parts(), more or less data in `params`", {
482+
fmt<-"whatever/{path}#@${line}^&*{column}"
483+
params<-list(path="some/path.ext",line="14",column="23")
484+
485+
expect_equal(
486+
interpolate_parts(fmt,params),
487+
"whatever/some/path.ext#@$14^&*23"
488+
)
489+
490+
params<-list(path="some/path.ext",line="14",column=NULL)
491+
expect_equal(
492+
interpolate_parts(fmt,params),
493+
"whatever/some/path.ext#@$14"
494+
)
495+
496+
params<-list(path="some/path.ext",line=NULL,column=NULL)
497+
expect_equal(
498+
interpolate_parts(fmt,params),
499+
"whatever/some/path.ext"
500+
)
501+
})
502+
503+
test_that("interpolate_parts(), format only has `path`", {
504+
fmt<-"whatever/{path}"
505+
params<-list(path="some/path.ext",line="14",column="23")
506+
expect_equal(
507+
interpolate_parts(fmt,params),
508+
"whatever/some/path.ext"
509+
)
510+
})
511+
512+
test_that("construct_file_link() works with custom format and an absolute path", {
513+
withr::local_options(
514+
"cli.hyperlink_file_url_format"="positron://file{path}:{line}:{column}"
515+
)
516+
517+
expect_equal(
518+
construct_file_link(list(path="/absolute/path")),
519+
list(url="positron://file/absolute/path")
520+
)
521+
expect_equal(
522+
construct_file_link(list(path="/absolute/path",line="12")),
523+
list(url="positron://file/absolute/path:12")
524+
)
525+
expect_equal(
526+
construct_file_link(list(path="/absolute/path",line="12",column="5")),
527+
list(url="positron://file/absolute/path:12:5")
528+
)
529+
530+
local_mocked_bindings(is_windows=function()TRUE)
531+
expect_equal(
532+
construct_file_link(list(path="c:/absolute/path")),
533+
list(url="positron://file/c:/absolute/path")
534+
)
535+
})
536+
537+
test_that("construct_file_link() works with custom format and a relative path", {
538+
withr::local_options(
539+
"cli.hyperlink_file_url_format"="positron://file{path}:{line}:{column}"
540+
)
541+
542+
# inspired by test helpers `sanitize_wd()` and `sanitize_home()`, but these
543+
# don't prefix the pattern-to-replace with `file://`
544+
sanitize_dir<-function(x,what= c("wd","home")) {
545+
what<- match.arg(what)
546+
pattern<-switch(what,wd= getwd(),home= path.expand("~"))
547+
if (is_windows()) {
548+
pattern<- paste0("/",pattern)
549+
}
550+
replacement<-switch(what,wd="/working/directory",home="/my/home")
551+
sub(pattern,replacement,x$url,fixed=TRUE)
552+
}
553+
554+
expect_equal(
555+
sanitize_dir(construct_file_link(list(path="relative/path")),what="wd"),
556+
"positron://file/working/directory/relative/path"
557+
)
558+
expect_equal(
559+
sanitize_dir(construct_file_link(list(path="relative/path:12")),what="wd"),
560+
"positron://file/working/directory/relative/path:12"
561+
)
562+
expect_equal(
563+
sanitize_dir(construct_file_link(list(path="relative/path:12:5")),what="wd"),
564+
"positron://file/working/directory/relative/path:12:5"
565+
)
566+
567+
expect_equal(
568+
sanitize_dir(construct_file_link(list(path="./relative/path")),what="wd"),
569+
"positron://file/working/directory/./relative/path"
570+
)
571+
expect_equal(
572+
sanitize_dir(construct_file_link(list(path="./relative/path:12")),what="wd"),
573+
"positron://file/working/directory/./relative/path:12"
574+
)
575+
expect_equal(
576+
sanitize_dir(construct_file_link(list(path="./relative/path:12:5")),what="wd"),
577+
"positron://file/working/directory/./relative/path:12:5"
578+
)
579+
580+
expect_equal(
581+
sanitize_dir(construct_file_link(list(path="~/relative/path")),what="home"),
582+
"positron://file/my/home/relative/path"
583+
)
584+
expect_equal(
585+
sanitize_dir(construct_file_link(list(path="~/relative/path:17")),what="home"),
586+
"positron://file/my/home/relative/path:17"
587+
)
588+
expect_equal(
589+
sanitize_dir(construct_file_link(list(path="~/relative/path:17:22")),what="home"),
590+
"positron://file/my/home/relative/path:17:22"
591+
)
592+
})
593+
594+
test_that("construct_file_link() works with custom format and input starting with 'file://'", {
595+
withr::local_options(
596+
"cli.hyperlink_file_url_format"="positron://file{path}:{line}:{column}"
597+
)
598+
599+
expect_equal(
600+
construct_file_link(list(path="file:///absolute/path")),
601+
list(url="positron://file/absolute/path")
602+
)
603+
expect_equal(
604+
construct_file_link(list(path="file:///absolute/path",line="12",column="5")),
605+
list(url="positron://file/absolute/path:12:5")
606+
)
607+
})

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp