Skip to content

RStudio Addin Support#408

Merged
Ikuyadeu merged 163 commits intoREditorSupport:masterfrom
MilesMcBain:vsc-r-api
Oct 15, 2020
Merged

RStudio Addin Support#408
Ikuyadeu merged 163 commits intoREditorSupport:masterfrom
MilesMcBain:vsc-r-api

Conversation

@MilesMcBain
Copy link
Copy Markdown
Collaborator

@MilesMcBain MilesMcBain commented Sep 8, 2020

Addressing issue #302. Support for addins in VSCode via rstudioapi emulation.

Capability Added

This PR would add an rstudioapi emulation layer to the extension. This would allow R functions that call the rstudioapi to be run, and behave as expected from within VSCode. Typically the rstudioapi is used by user-created RStudio addins to add functionality to the RStudio IDE.

One such example is the package {datapasta} which, among many things, formats data.frames on the clipboard as tibble::tribble() calls and inserts them into the active source editor or terminal. This relies on only a small subset of rstudioapi functionality for getting document context and inserting text.

It would also be possible to use existing RStudio addins to bridge functionality gaps. E.g. Users have requested a Roxygen skeleton generator in #405. With rstudioapi emulation in play we could recommend they make bindings (or ship them) to addin functions in {docthis} or {sinew}.

Implemented functions:

  • getActiveDocumentContext
  • getSourceEditorContext
  • isAvailable
  • verifyAvailable
  • insertText
  • modifyRange
  • readPreference
  • readRStudioPreference
  • hasFun
  • findFun
  • showDialog
  • navigateToFile
  • setSelectionRanges
  • setCursorPosition
  • setDocumentContents
  • documentSave
  • documentId
  • documentPath
  • documentSaveAll
  • documentNew
  • getActiveProject
  • restartSession
  • viewer

Testing It

This is challenging since most packages that do test rstudioapi stuff end up mocking the api anyway. I have sought feedback from R users on Twitter as to popular addins. We could make a short list and manually confirm the addins work. The list would contain (WIP):

  • {reprex}
  • {datapasta}
  • {fnmate}
  • {equisse}
  • {remedy}
  • {citr}
  • {blogdown}
  • {AlignAssign}
  • {prefixer}

@renkun-ken
Copy link
Copy Markdown
Member

Just did minor fixes to comfort eslint.

It would be very nice if you put some minimal examples about what will be achieved in this PR and how we could test against what has been done.

Looks very promising! Cheers!

@renkun-ken
Copy link
Copy Markdown
Member

Thanks for the nice work!

I play with reprex::reprex_addin() and choose clipboard and it works nicely. But reprex::reprex_selection() does not seem to work:

> reprex::reprex_selection()                                                                             
Error in verifyAvailable() : 
  argument "version_needed" is missing, with no default
Backtrace:
1: verifyAvailable()
2: callFun(fn)
3: getDocumentContext("getSourceEditorContext")
4: rstudioapi::getSourceEditorContext()
5: rstudio_context()
6: rstudioapi::primary_selection(context)
7: rstudio_selection()
8: reprex(input = rstudio_selection(), venue = venue)
9: reprex::reprex_selection()

@MilesMcBain
Copy link
Copy Markdown
Collaborator Author

MilesMcBain commented Sep 13, 2020

Oh sorry that list of add-ins was aspirational. As you can see from the trace there's a fair few functions that need implementation yet.

I only have those 4 listed above. This is going to take a while!

@MilesMcBain
Copy link
Copy Markdown
Collaborator Author

Also that trace highlights a problem with my verifyAvailable(). It needs to default the version arg to NULL. Thanks!

+ support for lists of mixed range/position objects
* verify_available
* is_available
Edits applied as part of one resolution have positions
updated as the transaction proceeds so that they always refer to the
same text (in so far as that is possible), irrespective of
other earlier edits.
defaulting to current range or position
@MilesMcBain
Copy link
Copy Markdown
Collaborator Author

MilesMcBain commented Sep 20, 2020

It's milestone time! @andycraig @renkun-ken

datapasta_vscode

Currently only works with the GitHub dev version of datapasta because I was making an undocumented call to the RStudio runtime which has since been replaced by a proper rstudioapi function. It was a one line change. Not bad!

I think what I have in the PR so far will cover all the addins I've written. Not sure about the others on that list. Still a ways to go probably.

@MilesMcBain
Copy link
Copy Markdown
Collaborator Author

Working through the eslint stuff now

@andycraig
Copy link
Copy Markdown
Collaborator

@MilesMcBain Great stuff, that screencap is a joy to watch!

I'm totally okay with only supporting a subset of plugins initially. Would it be possible/practical to give the user a message if a plugin tries to use a function that isn't supported yet?

When you want some testing done let us know!

@MilesMcBain
Copy link
Copy Markdown
Collaborator Author

MilesMcBain commented Sep 21, 2020

Yep I think a 'not implemented' message is a good way to go. Shouldn't be hard to do.

From here:

  • I realised there's a couple of functions still needed for 100% datapasta compatibility which I may as well add. This is to do with recent stuff that was PR'd to make it work with RStudio server. From my reading, if I add these functions it should work with Remote VSCode sessions as well.
  • I'll run through that shortlist of addins and make a table of what needs to be added to support what. We can decide what is worth including in an initial release from there. Everything not earmarked for release gets the "not implemented" message.
  • It would be good to have some kind of front end "addins menu" similar to RStudio. I'm not yet familiar with this part of the VSCode API but it seems doable based on other extensions I have used that do this. I'd be interested in opinions as to how this should work.
    • RStudio refreshes the menu options every time you install a package or restart RStudio, but I think it probably does it in background threads since it involves grepping through your entire library for ./inst/addins.dcf files.
    • It might be easiest to have some kind of "search for addins" command that synchronously updates a list of installed addins held in the ~/.vscode-R directory.

@renkun-ken
Copy link
Copy Markdown
Member

renkun-ken commented Sep 21, 2020

To get the list of addins and their bindings, I tried the following code:

pkgs <- .packages(all.available = TRUE)
addin_files <- vapply(pkgs, function(pkg) {
  system.file("rstudio/addins.dcf", package = pkg)
}, character(1L))
addin_files <- addin_files[file.exists(addin_files)]
descs <- lapply(addin_files, function(file) {
  desc <- read.dcf(file)
  as.data.frame(desc)
})
jsonlite::toJSON(descs)

which outputs all addins information from the library so that vscode-R could read it easily:

Click to expand!
{
  "blogdown": [
    {
      "Name": "Serve Site",
      "Description": "Run blogdown::serve_site() to live preview a website locally.",
      "Binding": "serve_site",
      "Interactive": "true"
    },
    {
      "Name": "New Post",
      "Description": "Create a new post with blogdown::new_post().",
      "Binding": "new_post_addin",
      "Interactive": "true"
    },
    {
      "Name": "Update Metadata",
      "Description": "Update the title, author, date, categories, and tags of the current blog post.",
      "Binding": "update_meta_addin",
      "Interactive": "true"
    },
    {
      "Name": "Insert Image",
      "Description": "Insert an external image into a blog post.",
      "Binding": "insert_image_addin",
      "Interactive": "true"
    },
    {
      "Name": "Touch File",
      "Description": "Change the timestamp of the current file in the editor.",
      "Binding": "touch_file_rstudio",
      "Interactive": "false"
    },
    {
      "Name": "Quote Poem",
      "Description": "Add > to the beginning of selected paragraphs and two trailing spaces to selected lines.",
      "Binding": "quote_poem_addin",
      "Interactive": "false"
    }
  ],
  "bookdown": [
    {
      "Name": "Preview Book",
      "Description": "Run bookdown::serve_book() to live preview a book.",
      "Binding": "serve_book",
      "Interactive": "true"
    },
    {
      "Name": "Input LaTeX Math",
      "Description": "Input math expressions via the MathQuill library.",
      "Binding": "mathquill",
      "Interactive": "true"
    }
  ],
  "clipr": [
    {
      "Name": "Value to clipboard",
      "Description": "Copies the results of a selected expression to the system clipboard",
      "Binding": "clipr_result",
      "Interactive": "false"
    },
    {
      "Name": "Output to clipboard",
      "Description": "Copies the console output of a selected expression to the system clipboard",
      "Binding": "clipr_output",
      "Interactive": "false"
    }
  ],
  "covr": [
    {
      "Name": "Calculate package test coverage",
      "Description": "Calculates the package test coverage and opens a report, using `covr::report()`",
      "Binding": "addin_report",
      "Interactive": "false"
    }
  ],
  "datapasta": [
    {
      "Name": "Paste as tribble",
      "Description": "Pastes a table from the clipboard to the editor as a tribble definition",
      "Binding": "tribble_paste",
      "Interactive": "false"
    },
    {
      "Name": "Paste as vector",
      "Description": "Pastes data from the clipboard to the editor as character vector, formatted horizontally on a single line.",
      "Binding": "vector_paste",
      "Interactive": "false"
    },
    {
      "Name": "Paste as vector (vertical)",
      "Description": "Pastes data from the clipboard to the editor as character vector, formatted vertically, one element per line.",
      "Binding": "vector_paste_vertical",
      "Interactive": "false"
    },
    {
      "Name": "Paste as data.frame",
      "Description": "Pastes a table from the clipboard to the editor as a data.frame definition",
      "Binding": "df_paste",
      "Interactive": "false"
    },
    {
      "Name": "Paste as data.table",
      "Description": "Pastes a table from the clipboard to the editor as a data.table definition",
      "Binding": "dt_paste",
      "Interactive": "false"
    },
    {
      "Name": "Fiddle Selection",
      "Description": "Take a selection and fiddle it to something better (maybe).",
      "Binding": "zzz_rs_dfiddle",
      "Interactive": "false"
    },
    {
      "Name": "Toggle Vector Quotes",
      "Description": "Toggle quotes in a vector defintion",
      "Binding": "zzz_rs_toggle_quotes",
      "Interactive": "false"
    }
  ],
  "devtools": [
    {
      "Name": "Run a test file",
      "Description": "Run the current test file, using `devtools::test_file()`.",
      "Binding": "test_file",
      "Interactive": "false"
    },
    {
      "Name": "Report test coverage for a file",
      "Description": "Calculate and report test coverage for the current test file, using `devtools::test_coverage_file()`.",
      "Binding": "test_coverage_file",
      "Interactive": "false"
    },
    {
      "Name": "Report test coverage for a package",
      "Description": "Calculate and report the test coverage for the current package, using `devtools::test_coverage()`.",
      "Binding": "test_coverage",
      "Interactive": "false"
    },
    {
      "Name": "Document a package",
      "Description": "A wrapper for `roxygen`'s `roxygen2::roxygenize()`",
      "Binding": "document",
      "Interactive": "false"
    }
  ],
  "lintr": [
    {
      "Name": "Lint current file",
      "Description": "Runs lintr::lint on the current file",
      "Binding": "addin_lint",
      "Interative": "false"
    },
    {
      "Name": "Lint current package",
      "Description": "Runs lintr::lint_package",
      "Binding": "addin_lint_package",
      "Interative": "false"
    }
  ],
  "reprex": [
    {
      "Name": "Render reprex...",
      "Description": "Run `reprex::reprex()` to prepare a reproducible example for sharing.",
      "Binding": "reprex_addin",
      "Interactive": "true"
    },
    {
      "Name": "Reprex selection",
      "Description": "Prepare reprex from current selection",
      "Binding": "reprex_selection",
      "Interactive": "false"
    }
  ],
  "styler": [
    {
      "Name": "Set style",
      "Description": "Prompt for and set the style transformers used by all styler addins",
      "Binding": "set_style_transformers",
      "Interactive": "true"
    },
    {
      "Name": "Style selection",
      "Description": "Pretty-print selection",
      "Binding": "style_selection",
      "Interactive": "true"
    },
    {
      "Name": "Style active file",
      "Description": "Pretty-print active file",
      "Binding": "style_active_file",
      "Interactive": "true"
    },
    {
      "Name": "Style active package",
      "Description": "Pretty-print active package",
      "Binding": "style_active_pkg",
      "Interactive": "true"
    }
  ]
}

We could add it to session watcher or addins init script so that the addins could be written to a JSON file in ~/.vscode-R so vscode-R could know the addins on each session startup. We could add some binding (probably hacking?) to package install functions to refresh the list.

@andycraig
Copy link
Copy Markdown
Collaborator

It would be good to have some kind of front end "addins menu" similar to RStudio. I'm not yet familiar with this part of the VSCode API but it seems doable based on other extensions I have used that do this.

The QuickPick API might be the way to go here. It's not used in vscode-R at the moment but here's an example extension that does use it: https://github.com/microsoft/vscode-extension-samples/tree/master/quickinput-sample

@MilesMcBain
Copy link
Copy Markdown
Collaborator Author

Thankyou both @renkun-ken and @andycraig, between the two of you, most of the research for the menu feature is done!

@MilesMcBain
Copy link
Copy Markdown
Collaborator Author

Okay I think the rebase was successful. Sorry about the mess. This is only the 2nd time I've done that. Probably what I should have done was squashed everything into one new commit? I'll know for next time. 😬

@Ikuyadeu Ikuyadeu merged commit d47fa1b into REditorSupport:master Oct 15, 2020
@Ikuyadeu
Copy link
Copy Markdown
Member

Perfect! I'll release a new version this week.

@renkun-ken
Copy link
Copy Markdown
Member

Cheers!

@Ikuyadeu
Copy link
Copy Markdown
Member

Ikuyadeu commented Oct 15, 2020

Probably what I should have done was squashed everything into one new commit?

I think, just change a line https://github.com/Ikuyadeu/vscode-R/pull/408/files#diff-8b19336fd34c052a83b852dc7aeac4b2a2b56f17c783c2480ca6948ae648330fR320 without rebasing/merge/cherrypick was the easiest change.
However, we can squash the whole of a pull request when commits are large.

@Ikuyadeu Ikuyadeu changed the title WIP: RStudio Addin Support RStudio Addin Support Oct 15, 2020
@andycraig
Copy link
Copy Markdown
Collaborator

@MilesMcBain Sorry, work has been super busy lately and I fell off the face of the planet for a bit.

Thank you very much for this huge piece of work. There was a lot more required for this than I'd assumed when I created that Issue about supporting these addins. Thank you so much for taking it on!

This is a massive new feature that people (including myself of course!) are really going to enjoy and appreciate.

Thank you again!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants