-
Notifications
You must be signed in to change notification settings - Fork 21
Remote R development via SSH
If you are running neovim with R.nvim via SSH it is possible to view plots and tables on the local machine.
The idea behind this approach is that you use SSH port forwarding to be able to conned to HTTP servers for graphics (httpgd). Since you are using SSH port forwarding this will work even in the presence of a firewall.
First, choose a range of ports you would like to use, then add the following lines to your local .ssh/config
:
Host remotehostname
LocalForward 127.0.0.1:7030 127.0.0.1:7030
LocalForward 127.0.0.1:7031 127.0.0.1:7031
LocalForward 127.0.0.1:7032 127.0.0.1:7031
LocalForward 127.0.0.1:7033 127.0.0.1:7033
LocalForward 127.0.0.1:7034 127.0.0.1:7034
LocalForward 127.0.0.1:7035 127.0.0.1:7035
LocalForward 127.0.0.1:7036 127.0.0.1:7036
LocalForward 127.0.0.1:7037 127.0.0.1:7037
LocalForward 127.0.0.1:7038 127.0.0.1:7038
LocalForward 127.0.0.1:7039 127.0.0.1:7039
# silence port connection messages
LogLevel QUIET
Unfortunately, SSH port forwarding doesn't support port ranges, so you have to have one line per port.
You want to make sure that when remote R tries to open a web-browser a local web browser window opens. To achieve this you can use opener. Once installed, make sure that opener actually works by calling browserURL("https://google.com")
in the remote R session. If all works well, this should a browser window on your local host.
Warning: Depending on the configuration other users using the same remote host could open URLs on your local host. Use with caution.
When you have opener and port forwarding enabled now you should make sure that httpgd can use ports from the correct range.
Put this into your ~/.Rprofile
:
# return a free port from the specified range
.free_port <- function(host = "127.0.0.1", ports = 7030:7039) {
withr::with_options(
# we do not want to actually open a browser
list(browser = identity),
{
for (p in ports) {
if (servr:::port_available(p, host)) {
return(p)
}
}
}
)
stop("no free ports found")
}
options(
device = function(...) {
httpgd::hgd(..., port = .free_port())
}
)
If you want to have a shortcut (<LocalLeader>gd
) to open a httpgd page make sure to add the following to your neovim config:
{
"R-nvim/R.nvim",
...
keys = {
...
{
"<LocalLeader>gd",
"<cmd>lua require('r.send').cmd('tryCatch(httpgd::hgd_browse(),error=function(e) {httpgd::hgd(port=.free_port());httpgd::hgd_browse()})')<CR>",
desc = "httpgd",
},
...
}
If you are using gt::gt()
or other similar tools which rely on showing HTML content in R you can use servr to serve you the content. The tricky part is that you have to translate URLs from local filesystem to servr
paths.
Here's one approach.
Warning: This assumes that only you can connect to local ports on the remote machine you are working on. If you are using a shared host you might need to use additional security measures such at HTTP authentication.
This should go into your ~/.Rprofile
.
# open a real URL using opener xdg-open
.browse_real_URL <- function(url) {
withr::with_options(
list(browser = file.path(Sys.getenv("HOME"), "bin/xdg-open")),
browseURL(url)
)
}
# simplified check for URL
.is_url <- function(string) {
pattern <- "https?://[^ /$.?#].[^\\s]*"
grepl(pattern, string)
}
# set a browser function to rewrite URLs
options(
browser = function(url) {
if (.is_url(url)) {
# when getting a real URL, just open the browser
.browse_real_URL(url)
} else {
# we are starting two servr's web serveres:
# one for HTML files in the current directory
# one could start servr in the root `/` directory, but this is
# even more dangerous from the security perspective
if (!exists(".curdir_srv")) {
.curdir <<- getwd()
withr::with_options(
list(browser = identity),
{
.curdir_srv <<- servr::httd(.curdir, port = .free_port())
}
)
}
# one for HTML files in the temporary directory
if (!exists(".tmpdir_srv")) {
withr::with_options(
list(browser = identity),
{
.tmpdir_srv <<- servr::httd(tempdir(), port = .free_port())
}
)
}
# is the file in the temporary directory?
if (startsWith(url, tempdir())) {
# rewrite URL and redirect to the server for the temporary directory
.browse_real_URL(
paste0(
.tmpdir_srv$url,
substring(url, nchar(tempdir()) + 1)
)
)
# is the file in the local directory?
} else if (startsWith(url, .curdir)) {
# rewrite URL and redirect to the server for the local directory
.browse_real_URL(
paste0(
.curdir_srv$url,
substring(url, nchar(.curdir) + 1)
)
)
} else if (startsWith(url, "/")) {
# do not know what to do with other paths
cat(url, "\n")
} else {
# otherwise it's a file relative to the current directory
.browse_real_URL(
paste0(
.curdir_srv$url,
"/",
url
)
)
}
}
}
)
R.nvim will automatically try to open rendered .rmd
files if open_html
is set to open
. Alternatively you can call manually:
browseURL("report.html")