Expand description

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 ,” assist becomes available.

§Assists Guidelines

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 selection. Example: sort_items sorts 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 [Assists::add_group]).
  • 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 enums in 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 Assist data 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

Modules§

  • Assorted functions shared by several assists.

Structs§

  • Unique identifier of the assist, should not be shown to the user directly.
  • Hold the AssistId data of a certain assist to resolve. The original id object cannot be used due to a 'static lifetime and the requirement to construct this struct dynamically during the resolve handling.

Enums§

  • A way to control how many assist to resolve during the assist resolution. When an assist is resolved, its edits are calculated that might be costly to always do by default.

Functions§

  • Return all the assists applicable at the given position.