Providing Quick Assists

A quick assist is used by clients to provide a set of possible changes to code that are based on the structure of the code. Quick assists are intended to help users safely make local changes to code when those changes do not require any user interaction. (Modifications that require interaction with users or that touch multiple files are usually implemented as refactorings.)

For example, if the user has a function whose body consists of a single return statement in a block, server will provide an assist to convert the function body from a block to an expression (=>).

Assists have a priority associated with them. The priority allows the client to display the assists that are most likely to be of use closer to the top of the list when there are multiple assists available.

Implementation details

When appropriate, the analysis server will send your plugin an edit.getAssists request. The request includes the file, offset and length associated with the selected region of code.

When an edit.getAssists request is received, the method handleEditGetAssists will be invoked. This method is responsible for returning a response that contains the available assists.

The easiest way to implement this method is by adding the classes AssistsMixin and DartAssistsMixin (from package:analyzer_plugin/plugin/assist_mixin.dart) to the list of mixins for your subclass of ServerPlugin. This will leave you with one abstract method that you need to implement: getAssistContributors. That method is responsible for returning a list of AssistContributors. It is the assist contributors that produce the actual assists. (Most plugins will only need a single assist contributor.)

To write an assist contributor, create a class that implements AssistContributor. The interface defines a single method named computeAssists. The method has two arguments: an AssistRequest that describes the location at which assists were requested and an AssistCollector through which assists are to be added.

If you mix in the class DartAssistsMixin, then the request will be an instance of DartAssistRequest, which also has analysis results.

The class AssistContributorMixin defines a support method that makes it easier to implement computeAssists.

Example

Start by creating a class that implements AssistContributor and that mixes in the class AssistContributorMixin, then implement the method computeAssists. This method is typically implemented as a sequence of invocations of methods that check to see whether a given assist is appropriate in the context of the request

To learn about the support available for creating the edits, see Creating Edits.

For example, your contributor might look something like the following:

class MyAssistContributor extends Object
    with AssistContributorMixin
    implements AssistContributor {
  static AssistKind wrapInIf =
      AssistKind('wrapInIf', 100, "Wrap in an 'if' statement");

  DartAssistRequest request;

  AssistCollector collector;

  AnalysisSession get session => request.result.session;

  @override
  Future<void> computeAssists(DartAssistRequest request, AssistCollector collector) async {
    this.request = request;
    this.collector = collector;
    await _wrapInIf();
    await _wrapInWhile();
    // ...
  }

  Future<void> _wrapInIf() async {
    ChangeBuilder builder = ChangeBuilder(session: session);
    await changeBuilder.addDartFileEdit(path,
        (DartFileEditBuilder fileEditBuilder) {
      // TODO Build the edit to wrap the selection in a 'if' statement.
    });
    addAssist(wrapInIf, builder);
  }

  Future<void> _wrapInWhile() async {
    // ...
  }
}

Given a contributor like the one above, you can implement your plugin similar to the following:

class MyPlugin extends ServerPlugin with AssistsMixin, DartAssistsMixin {
  // ...

  @override
  List<AssistContributor> getAssistContributors(String path) {
    return <AssistContributor>[MyAssistContributor()];
  }
}