Vscode-r: Support for displaying plots and help files in VS Code IDE

Created on 20 Feb 2018  路  28Comments  路  Source: Ikuyadeu/vscode-R

Great work on this, thank you!

One thing I find useful is to see plots and help documents in the IDE (both RStudio and R Tools for Visual Studio offer this). It would be great to have the same thing for R in VS Code.

The julia-vscode and pythonVSCode extensions both include plot panes within VS Code itself, which I guess means it's possible.

Thanks

feature-request help wanted like RStudio

Most helpful comment

Just a note that the vscode-R license is MIT again.

All 28 comments

@iainmstott Thank you for using and issue!
Your suggestion is very good!
I think this function is difficult to implement, but your example is helpful for me.
It is good TODO.
Thank you!

Maybe the new webview API can be used for displaying plots: https://code.visualstudio.com/docs/extensions/webview

@issuehuntfest has funded $40.00 to this issue. See it on IssueHunt

Without plotting capabilities, unfortunately vscode-R becomes more difficult to use for analysis purposes as you need to save the plots you produce to view them. This becomes troublesome if your plots contain a lot of data and take a long time to generate and an equally long time to then save and load.

Hi @nlneas1, for me functions like plot open a new window (separate from the main VSCode window) containing the plot. There is no feature at present to open the plot WITHIN the VSCode window, but you should be able to view the plot without saving it to a file first.

What happens when you run the following line of code?

plot(1:2, 3:4)

If nothing appears at all, it may be a bug. In that case, could you let me know your OS, VSCode version and vscode-R version?

There is an issue open for adding support for other languages to the (very good) Jupyter component of the Python extension. This would allow display of images and help within the VSCode window, although it would be more like a Jupyter notebook than RStudio. If this sounds good to you, please consider voting for R support here:
https://github.com/microsoft/vscode-python/issues/5078#issuecomment-507751659

Thank you!

Yeah that definitely doesn't happen for me.

Version: 1.36.1 (user setup)
Commit: 2213894ea0415ee8c85c5eea0d0ff81ecc191529
Date: 2019-07-08T22:59:35.033Z
Electron: 4.2.5
Chrome: 69.0.3497.128
Node.js: 10.11.0
V8: 6.9.427.31-electron.0
OS: Windows_NT x64 10.0.16299

The VSCode extensions I have installed right now are:

vscode-R: 1.11.1
R LSP Client: 0.1.0

It might be important to note that I am remote accessing a Linux machine (CentOS) using the Microsoft Remotes extension.

@nlneas1 That does sound like a bug then. Thank you for all the version details - I've copied them to a new issue. I suspect the remote access aspect is probably central to the problem.

I鈥檝e had a bit of a look at how the Julia extension handles plotting. I think it starts the REPL in a socket server. If a message returned from the server is a plot instruction, then it renders it in a webview.

The relevant file:
https://github.com/julia-vscode/julia-vscode/blob/bfe7023cf3502fd4cbd1c89def954f76e9f92997/src/repl.ts

Currently R Interactive terminals simply launch R in a terminal, so implementing plotting as the Julia extension does would require introducing a socket server.

If anyone has thoughts on other possible ways of introducing plotting within the VSCode window, please post.

If vscode-R has to start R session itself for interprocess communication, then user-manageable R sessions (in any screen or tmux window) will probably be compromised.

I'm wondering if we could add task callback functions to let user started R session to communicate with vscode-R so that plots, global variable updates (in environment pane), etc. could be handled without sending command to the R session. This task callback function will be called each time user evaluated an expression in the global level.

addTaskCallback(function(...) {
  # tell vscode-R a global expression is evaluated
  # update global variables using ls(globalenv())
  # check if some graphics device is open, 
  # and use altered default device made easier to communite with vscode-R, 
  # or copy device contents to a file, 
  # tell vscode-R a plot is created or updated.
  # some other tasks
}, name = "handler")

Then vscode-R should listen or read file and respond to this global task handler.

I'm doing some experiement with this and see if this idea works.

@renkun-ken I was imagining a socket server approach only applying to the R Interactive sessions that vscode-R creates by default, with user-manageable R sessions being left as-is. (My normal workflow involves Radian in user-managed sessions so I definitely don't want to negatively affect that functionality.)

It sounds like you're thinking about an approach that would also work for user-managed sessions? That would be great! Very interested to hear how your experiments go.

I can not find much documentation about this, but we could also try to implement a R graphics device:

https://bookdown.org/rdpeng/exdata/graphics-devices.html

People have implemented some as packages:

https://www.stat.auckland.ac.nz/~paul/R/devices.html

The graphics device could send something like SVG over http to vscode.

The tricky part of a graphics device is that only screen devices such as x11 supportdev.copy(), or otherwise there's no way to see any output until the device is closed (dev.off()).

An initial attempt is to add the following to .Rprofile:

if (interactive()) {
  local({
    dir <- file.path("~", ".vscode-R", Sys.getpid())
    dir.create(dir, showWarnings = FALSE, recursive = TRUE, mode = "0700")
    reg.finalizer(globalenv(), function(e) {
      unlink(dir, recursive = TRUE, force = TRUE)
    }, onexit = TRUE)

    session_file <- file.path(dir, "session")
    cat(utils::capture.output(sessionInfo()), sep = "\n", file = session_file)

    info_file <- file.path(dir, "pwd")
    cat(getwd(), file = info_file)

    plot_file <- file.path(dir, "plot.png")
    options(device = function(...) {
      png(filename = plot_file, ...)
    })

    addTaskCallback(function(expr, value, ok, visible) {
      env_file <- file.path(dir, "globalenv")
      cat(utils::capture.output(ls.str(globalenv())), sep = "\n", file = env_file)
      TRUE
    }, name = "vscode-R")
  })
}

so that each R session creates a subdirectory named by Sys.getpid() in ~/.vscode-R and put session info, working directory, plot file, and globalenv info as files in the session directory.

User can choose which session to attach in vscode-R and show the plot graphics and global environment variables, etc. This is a rather passive approach for vscode-R to work with active R sessions by maintaining the capability of user-manageable sessions anywhere in screen or tmux, and customized R terminal (original or radian) and without too much intrusive actions in R.

@renkun-ken Wow, this is really nice! If I鈥檓 following correctly, the next step would be to have VSCode watch the contents of .vscode-R/[pid] (perhaps using a FileSystemWatcher and refresh a webview whenever there鈥檚 a change?

@nx10 Thank you for having a look into the problem as well! I suppose that a custom graphics device might be a way of seeing intermediate plot output?

For now, a plot window that updates only on dev.off() would be a good step forward.

@andycraig That's exactly what I'm suggesting. The known issues are:

  • I'm not sure if it is optimal to remove .vscodec-R/[pid] directory on session exit since 1) sometimes we need the data (mostly graphics) after the session exits; 2) the reg.finalizer part only removes the directory when session ends normally. If user click close button in vscode terminal pane, or if the session crashes or is killed, the directory will remain there.
  • I'm not sure if there's a decenct way to make user started R sessions to allow bidirectional communication between R and vscode-R, for example, we can support a modified View like in RStudio. The current data frame viewer (Excel Viewer) simply does not support Remote Development, and the number formatting becomes messy very often.

@renkun-ken For the cleanup problem, we could probably do it in vscode-R鈥檚 deactivate function. Then the files would be removed when the user closes VSCode.

It would be fine to replace the current Excel Viewer with a different approach if it worked better for remote development. (I鈥檝e never done remote work with VSCode so I don鈥檛 know much about that topic.)

I do remote work with VSCode everyday so I'd be happy to test once implemented.

I just went through the RStudio code and found their c++ implementation of a graphics device which is licensed AGPL v3. Could this be modified to use in vscode-R? https://github.com/rstudio/rstudio/tree/master/src/cpp/r/session/graphics

@nx10 Interesting!

I鈥檓 not sure if VSCode extensions can include C++ code. They might be able to, but I鈥檓 not sure.

I believe using AGPL code would require changing the license of vscode-R to AGPL, so if you鈥檙e thinking about having a try at this approach I would make sure that @Ikuyadeu is happy with that.

@nx10 License is now AGPL, so no obstacle there.

The licensing change is a shame due to the virality of AGPL. This disallows usage at many organizations due to the risk of contaminating licenses for other software. See this AGPL Policy page at Google for an example.

I'd also like to state a personal opinion that a license change is _not_ a minor change in a project, and should have been accompanied with a major release.

I think I find a way to output intermediate graphics (thanks for the introduction of recordPlot in https://www.andrewheiss.com/blog/2016/12/08/save-base-graphics-as-pseudo-objects-in-r/):

options(device = function(...) {
  pdf(NULL, bg = "white")
  dev.control(displaylist = "enable")
})
addTaskCallback(function(...) {
  if (dev.cur() > 1 && !dev.interactive()) {
    record <- recordPlot()
    if (length(record[[1]])) {
      png("plot.png")
      on.exit(dev.off())
      replayPlot(record)
    }
  }
  TRUE
})

With this, user won't have to dev.off() to see the plot, and user can use png, svg, pdf or other device user likes. And best of all, it seems that we don't have to implement a graphics device for this.

@renkun-ken That is a really nice solution!

Just a note that the vscode-R license is MIT again.

The drawback of my code is that if a heavy plot is produced (e.g. plot(rnorm(1000000))), each user input, no matter whether it is related to plotting (.e.g. calling 1+1), will trigger a plot replay.

It seems that all graphics operations calls .External.graphics ultimately. If we maintain a state of plot_updated, and each time .External.graphics is called, plot_update is set to TRUE and after replay it is set to FALSE, then only plot related user input would trigger plot replay.

I tried using trace():

trace(".External.graphics", exit = function() {
  plot_updated <<- TRUE
}, print = FALSE)

which works but if user calls tracingState(FALSE) then all tracers are disabled.

Then we might need a hard injection to this primitive function. Following are my updates of the plot replay code:

plot_updated <- FALSE
options(device = function(...) {
  pdf(NULL, bg = "white")
  dev.control(displaylist = "enable")
})
setHook("plot.new", function(...) {
  plot_updated <<- TRUE
})
unlockBinding(".External.graphics", baseenv())
assign(".External.graphics", function(...) {
  plot_updated <<- TRUE
  .prim <- .Primitive(".External.graphics")
  .prim(...)
}, baseenv())
lockBinding(".External.graphics", baseenv())
addTaskCallback(function(...) {
  if (dev.cur() == 2L && plot_updated) {
    plot_updated <<- FALSE
    record <- recordPlot()
    if (length(record[[1]])) {
      png("plot.png")
      on.exit(dev.off())
      replayPlot(record)
    }
  }
  TRUE
})

which hacks base::.External.graphics but works quite well for me.

Thanks,
How one with no experience should implement this solution in vscde? does this solution work with remote control (ssh)?

Thanks, HM

@hmassalha, please take a look at #150. I'm trying to continue the great pioneer work of @andycraig by implementing a complete solution for this. I've been already using it in my work and I'm trying to improve it gradually.

With R session watcher enabled, using options(html_type = "html") will enable HTML help and ? will open a browser webview in VSCode to show help. To keep this behavior, just add the option to ~/.Rprofile. c15cb5be7899c59e708606c7f9a742398c455937 makes it default to show HTML help.

image

Was this page helpful?
0 / 5 - 0 ratings

Related issues

LePeti picture LePeti  路  4Comments

MaTo04 picture MaTo04  路  5Comments

Kalaschnik picture Kalaschnik  路  4Comments

michaelHL picture michaelHL  路  5Comments

awellis picture awellis  路  5Comments