|Feb 19, 2024
|Jan 29, 2024
|Dec 18, 2023
|Nov 27, 2023
|Mar 29, 2021
#282 in Development tools
8,765 downloads per month
Used in 11 crates (via ra_ap_ide)
assists crate provides a bunch of code assists, also known as code actions
(in LSP) or intentions (in IntelliJ).
An assist is a micro-refactoring, which is automatically activated in
certain context. For example, if the cursor is over
,, a "swap
Assists are the main mechanism to deliver advanced IDE features to the user, so we should pay extra attention to the UX.
The power of assists comes from their context-awareness. The main problem
with IDE features is that there are a lot of them, and it's hard to teach
the user what's available. Assists solve this problem nicely: 💡 signifies
that something is possible, and clicking on it reveals a short list of
actions. Contrast it with Emacs
M-x, which just spits an infinite list of
all the features.
Here are some considerations when creating a new assist:
- It's good to preserve semantics, and it's good to keep the code compiling, but it isn't necessary. Example: "flip binary operation" might change semantics.
- Assist shouldn't necessary make the code "better". A lot of assist come in pairs: "if let <-> match".
- Assists should have as narrow scope as possible. Each new assists greatly improves UX for cases where the user actually invokes it, but it makes UX worse for every case where the user clicks 💡 to invoke some other assist. So, a rarely useful assist which is always applicable can be a net negative.
- Rarely useful actions are tricky. Sometimes there are features which are
clearly useful to some users, but are just noise most of the time. We
don't have a good solution here, our current approach is to make this
functionality available only if assist is applicable to the whole
sort_itemssorts items alphabetically. Naively, it should be available more or less everywhere, which isn't useful. So instead we only show it if the user selects the items they want to sort.
- Consider grouping related assists together (see
- Make assists robust. If the assist depends on results of type-inference too much, it might only fire in fully-correct code. This makes assist less useful and (worse) less predictable. The user should have a clear intuition when each particular assist is available.
- Make small assists, which compose. Example: rather than auto-importing
add_missing_match_arms, we use fully-qualified names. There's a separate assist to shorten a fully-qualified name.
- Distinguish between assists and fixits for diagnostics. Internally, fixits
and assists are equivalent. They have the same "show a list + invoke a
single element" workflow, and both use
Assistdata structure. The main difference is in the UX: while 💡 looks only at the cursor position, diagnostics squigglies and fixits are calculated for the whole file and are presented to the user eagerly. So, diagnostics should be fixable errors, while assists can be just suggestions for an alternative way to do something. If something could be a diagnostic, it should be a diagnostic. Conversely, it might be valuable to turn a diagnostic with a lot of false errors into an assist.
See also this post: https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html