Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for displaying plots and help files in VS Code IDE #51

Closed
iainmstott opened this issue Feb 20, 2018 · 28 comments · Fixed by #161
Closed

Support for displaying plots and help files in VS Code IDE #51

iainmstott opened this issue Feb 20, 2018 · 28 comments · Fixed by #161

Comments

@iainmstott
Copy link

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

@Ikuyadeu
Copy link
Member

@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!

@nx10
Copy link
Contributor

nx10 commented May 3, 2018

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

@IssueHuntBot
Copy link

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

@nlneas1
Copy link

nlneas1 commented Aug 7, 2019

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.

@andycraig
Copy link
Collaborator

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?

@andycraig
Copy link
Collaborator

andycraig commented Aug 7, 2019

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!

@nlneas1
Copy link

nlneas1 commented Aug 7, 2019

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.

@andycraig
Copy link
Collaborator

@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.

@andycraig
Copy link
Collaborator

I’ve 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.

@renkun-ken
Copy link
Member

renkun-ken commented Oct 27, 2019

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.

@andycraig
Copy link
Collaborator

@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.

@nx10
Copy link
Contributor

nx10 commented Oct 27, 2019

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.

@renkun-ken
Copy link
Member

renkun-ken commented Oct 27, 2019

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.

@andycraig
Copy link
Collaborator

@renkun-ken Wow, this is really nice! If I’m 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’s 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.

@renkun-ken
Copy link
Member

renkun-ken commented Oct 29, 2019

@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.

@andycraig
Copy link
Collaborator

@renkun-ken For the cleanup problem, we could probably do it in vscode-R’s 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’ve never done remote work with VSCode so I don’t know much about that topic.)

@nathaneastwood
Copy link

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

@nx10
Copy link
Contributor

nx10 commented Nov 6, 2019

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

@andycraig
Copy link
Collaborator

@nx10 Interesting!

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

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

@andycraig
Copy link
Collaborator

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

@josegonzalez
Copy link

josegonzalez commented Nov 17, 2019

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.

@renkun-ken
Copy link
Member

renkun-ken commented Nov 18, 2019

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.

@andycraig
Copy link
Collaborator

@renkun-ken That is a really nice solution!

@andycraig
Copy link
Collaborator

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

@renkun-ken
Copy link
Member

renkun-ken commented Nov 19, 2019

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.

@hmassalha
Copy link

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

Thanks, HM

@renkun-ken
Copy link
Member

@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.

@renkun-ken
Copy link
Member

renkun-ken commented Dec 27, 2019

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. c15cb5b makes it default to show HTML help.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

10 participants