moodymudskipper / tricks

An Addin to Easily Program and Trigger Actions
14 stars 1 forks source link

tricks

RStudio addins are handy but have flaws :

So in the end you might not write them much, and not use them much.

{tricks} is an attempt to solve those issues : * it works with a single hotkey * It proposes only relevant actions, by observing the context (selection, clipboard…) * It’s made very easy to add actions

A trick is defined by a condition, a label and an action, then:

Installation

remotes::install_github("moodymudskipper/tricks")

Install and use tricks

Tricks are stored in YAML files, installed tricks are stored in a .r-tricks.yaml files located next to your .Rprofile files, this way you can define them at the project or at the user level.

These can be installed from packages or other YAML files using install_tricks().

Insert gif describing install_tricks(), install a couple and apply them to a situation where they are not triggered at the same time

Define tricks using YAML files

Designing your own tricks can be very fast, we’ll illustrate this with 3 tricks shipped with this package. To see all tricks defined in this package you can call file.edit(system.file("tricks.yaml", package = "tricks"))

Edit zour .Rprofile

Do you know how to easily edit your user R Profile ? There’s a nice {usethis} function for this, but maybe you have problems remembering it, or you’d rather not have to type it.

We propose a trick to do that. If you trigger {tricks} when not selecting anything in the editor it will propose you to open your user R profile.

Edit user '.Rprofile':
  description: Opens user '.Rprofile'
  condition: selection_is_empty()
  action: usethis::edit_r_profile()

We see below that whenever the selection is not empty the labels are not shown, but they are shown if the selection is empty.

INSERT GIF

Setting up tricks when the selection is empty is a good way to keep a list of bookmarks for actions you don’t want to remember how to trigger, maybe some other {usethis} workflow actions.

selection_is_empty() is called a condition helper, and we have many of them, documented in :

Reprex your selection

The {reprex} package comes with a handy addin to create a reprex from a hotkey. You’ll have to remember how to trigger the hotkey though.

If you don’t want to we can set up a trick that will be proposed whenever we select parsable code that isn’t a symbol, when it is triggered it should call the relevant {reprex} addin.

Reprex selection:
  description: Create a Reprex fron selection
  condition: selection_is_parsable(symbol_ok = FALSE)
  action: call_addin("reprex", "Reprex selection")

We see below that whenever the selected code is parsable and is more than just a symbol, the action will be proposed.

INSERT GIF

selection_is_parsable() is another condition helper. call_addin() is an action helper, there are more of them documented in `?``action-helpers```.

It is good practice to have narrow conditions around our use cases so as our list of tricks grows we only display those that apply.

debugonce()

You might not like to type the name of a function when you want to debug, we might could define an addin to call debugonce() on your selection but then you have to find an unused hotkey and remember it.

We can set a trick to debugonce() a function, it should be proposed only if the selection is a symbol bound to a function.

debugonce({current_selection()}):
  description: Call debugonce() on selected function
  condition: selection_is_function()
  action: debugonce(.(current_call()))

We see below that the action is proposed only if a symbol is selected and evaluates to a function.

INSERT GIF

selection_is_function() is another condition helper. current_selection() and current_call() are context informers, they are documented in ?current_selection.

We note a couple new things here : * We used {glue} notation in the label, this is handy to provide custom labels * We used .() inside debugonce(), this is the same .() that we find in bquote(), debugonce() uses NSE and supporting .() in actions and conditions permit us to program with such functions conveniently.

load_tricks(
  "<label1>" = <condition1> ~ <action1>, 
  "<label2>" = <condition2> ~ <action2>, 
  ...)

The package contains a set of functions to help you define actions and conditions conveniently.

Let’s go through a few examples.

Define tricks with load_tricks()

While we recommend using YAML files to store tricks, it might be useful to define tricks directly in R code for easier trial. This can be achieved using load_tricks(), its syntax is :

tricks::load_tricks(
  "<label1>" = <condition1> ~ <action1>, 
  "<label2>" = <condition2> ~ <action2>, 
  ...)

It also accepts trick objects defined with new_trick()

To reproduce the tricks showcased above we would run :

tricks::load_tricks(
  "Edit user '.Rprofile'" =
    selection_is_empty() ~     
    usethis::edit_r_profile(), 

  "Edit project '.Rprofile'" =
    selection_is_empty() ~                      
    usethis::edit_r_profile(scope = "project"), 

  "Reprex selection" =
    selection_is_parsable(symbol_ok = FALSE) ~  
    call_addin("reprex", "Reprex selection"),

  # label : here using glue syntax to have dynamic action names
  "debugonce({current_selection()})" = 
    # condition : selection evaluates to function
    selection_is_function() ~ 
    # action    : run debugonce, we use `bquote()`'s `.()` notation to work around NSE issues
    debugonce(.(current_call())),      
)

Explore and edit tricks

Isn’t it slow to have a lot of tricks ? Is it safe ?

All of the helper functions are memoised so they will only be called once, and results will stay in memory while other conditions are evaluated. Given that most of these operations are pretty quick to begin with it’s unlikely that you’ll witness big lags. However if you push it you might succeed to design conditions that do take non trivial time to evaluate.

We discourage evaluating the selection (unless it’s a symbol) by forbidding the use of current_value() in conditions. Evaluating a complex expression might take time and/or have side effects and this would really spoil the fun of {tricks}. Unless you try really hard, conditions won’t affect the global state so {tricks} can be used safely.

Moreover condition cannot trigger an error, if its code fails it just returns FALSE, the evaluation of conditions is also totally silent.