An open API service indexing awesome lists of open source software.

https://github.com/moodymudskipper/tricks

An Addin to Easily Program and Trigger Actions
https://github.com/moodymudskipper/tricks

Last synced: 3 months ago
JSON representation

An Addin to Easily Program and Trigger Actions

Awesome Lists containing this project

README

        

---
output: github_document
---

```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%"
)
```

# tricks

RStudio addins are handy but have flaws :

* They're contained in packages, which are not straightforward to write/iterate on
* It's hard to remember which addins you've installed, and harder to remember the
hotkeys you've set for them
* The addin list soon becomes overwhelming since they're all shown at all times,
even when they don't apply

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:

* When the **hotkey** is triggered , all **conditions** are evaluated
* For satisfied **conditions**, **labels** are proposed to the user
* Once the **label** is selected, the **action** is triggered

## Installation

```{r, , eval = FALSE}
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()`.

* `install_tricks()` without arguments installs tricks that come with
the {tricks} package.
* `install_tricks("some_tricks.yaml")` installs tricks defined in a specific file
* `install_tricks("pkg")` proposes you to install tricks from a package, the developer of such package
should include a `tricks.yaml` file in the `inst` subfolder of their package.

*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 :

* `?``selection-condition-helpers```
* `?``file-condition-helpers```
* `?``project-condition-helpers```
* `?``clipboard-condition-helpers```
* `?``system-condition-helpers```

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

```{r, eval = FALSE}
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(
"" = ~ ,
"" = ~ ,
...)
```

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 :

```{r, , eval = FALSE}
tricks::load_tricks(
"" = ~ ,
"" = ~ ,
...)
```

It also accepts trick objects defined with `new_trick()`

To reproduce the tricks showcased above we would run :

```{r, eval = FALSE}
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

* `edit_tricks()` to open YAML file
* `install_tricks()` to add new entries to YAML files
* `uninstall_tricks()` to remove some
* `loaded_tricks()` or `View(loaded_tricks())` to explore loaded tricks
* `load_tricks()` to load trick objects or load tricks built from formulas
* `unload_tricks()` to unload some

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