variablesRequest method

  1. @override
Future<void> variablesRequest(
  1. Request request,
  2. VariablesArguments args,
  3. void sendResponse(
    1. VariablesResponseBody
    )
)

variablesRequest is called by the client to request child variables for a given variables variablesReference.

The variablesReference provided by the client will be a reference the server has previously provided, for example in response to a scopesRequest or an evaluateRequest.

We use the reference to look up the stored data and then create variables based on the type of data. For a Frame, we will return the local variables, for a List/MapAssociation we will return items from it, and for an instance we will return the fields (and possibly getters) for that instance.

Implementation

@override
Future<void> variablesRequest(
  Request request,
  VariablesArguments args,
  void Function(VariablesResponseBody) sendResponse,
) async {
  final service = vmService;
  final childStart = args.start;
  final childCount = args.count;
  final storedData = isolateManager.getStoredData(args.variablesReference);
  if (storedData == null) {
    throw StateError('variablesReference is no longer valid');
  }
  final thread = storedData.thread;
  var data = storedData.data;

  VariableFormat? format;
  // Unwrap any variable we stored with formatting info.
  if (data is VariableData) {
    format = data.format;
    data = data.data;
  }

  // If no explicit formatting, use from args.
  format ??= VariableFormat.fromDapValueFormat(args.format);

  final variables = <Variable>[];

  if (data is FrameScopeData && data.kind == FrameScopeDataKind.locals) {
    final vars = data.frame.vars;
    if (vars != null) {
      Future<Variable> convert(int index, vm.BoundVariable variable) {
        // Store the expression that gets this object as we may need it to
        // compute evaluateNames for child objects later.
        final value = variable.value;
        if (value is vm.InstanceRef) {
          storeEvaluateName(value, variable.name);
        }
        return _converter.convertVmResponseToVariable(
          thread,
          variable.value,
          name: variable.name,
          allowCallingToString: evaluateToStringInDebugViews &&
              index < maxToStringsPerEvaluation,
          evaluateName: variable.name,
          format: format,
        );
      }

      variables.addAll(await Future.wait(vars.mapIndexed(convert)));

      // Sort the variables by name.
      variables.sortBy((v) => v.name);
    }
  } else if (data is FrameScopeData &&
      data.kind == FrameScopeDataKind.globals) {
    /// Helper to simplify calling converter.
    Future<Variable> convert(int index, vm.FieldRef fieldRef) async {
      return _converter.convertFieldRefToVariable(
        thread,
        fieldRef,
        allowCallingToString:
            evaluateToStringInDebugViews && index < maxToStringsPerEvaluation,
        format: format,
      );
    }

    final globals = await _getFrameGlobals(thread, data.frame);
    variables.addAll(await Future.wait(globals.mapIndexed(convert)));
    variables.sortBy((v) => v.name);
  } else if (data is InspectData) {
    // When sending variables as part of an OutputEvent, VS Code will only
    // show the first field, so we wrap the object to ensure there's always
    // a single field.
    final instance = data.instance;
    variables.add(Variable(
      name: '', // Unused.
      value: '<inspected variable>', // Shown to user, expandable.
      variablesReference: instance != null ? thread.storeData(instance) : 0,
    ));
  } else if (data is WrappedInstanceVariable) {
    // WrappedInstanceVariables are used to support DAP-over-DDS clients that
    // had a VM Instance ID and wanted to convert it to a variable for use in
    // `variables` requests.
    final response = await isolateManager.getObject(
      storedData.thread.isolate,
      vm.ObjRef(id: data.instanceId),
      offset: childStart,
      count: childCount,
    );
    // Because `variables` requests are a request for _child_ variables but we
    // want DAP-over-DDS clients to be able to get the whole variable (eg.
    // including toe initial string representation of the variable itself) the
    // initial request will return a list containing a single variable named
    // `value`. This will contain both the `variablesReference` to get the
    // children, and also a `value` field with the display string.
    final variable = await _converter.convertVmResponseToVariable(
      thread,
      response,
      name: 'value',
      evaluateName: null,
      allowCallingToString: evaluateToStringInDebugViews,
    );
    variables.add(variable);
  } else if (data is vm.MapAssociation) {
    final key = data.key;
    final value = data.value;
    if (key is vm.InstanceRef && value is vm.InstanceRef) {
      // For a MapAssociation, we create a dummy set of variables for "key" and
      // "value" so that each may be expanded if they are complex values.
      variables.addAll([
        Variable(
          name: 'key',
          value: await _converter.convertVmInstanceRefToDisplayString(
            thread,
            key,
            allowCallingToString: evaluateToStringInDebugViews,
            format: format,
          ),
          variablesReference: _converter.isSimpleKind(key.kind)
              ? 0
              : thread.storeData(VariableData(key, format)),
        ),
        Variable(
            name: 'value',
            value: await _converter.convertVmInstanceRefToDisplayString(
              thread,
              value,
              allowCallingToString: evaluateToStringInDebugViews,
              format: format,
            ),
            variablesReference: _converter.isSimpleKind(value.kind)
                ? 0
                : thread.storeData(VariableData(value, format)),
            evaluateName:
                buildEvaluateName('', parentInstanceRefId: value.id)),
      ]);
    }
  } else if (data is vm.ObjRef) {
    final object = await isolateManager.getObject(
      storedData.thread.isolate,
      data,
      offset: childStart,
      count: childCount,
    );

    if (object is vm.Sentinel) {
      variables.add(Variable(
        name: '<eval error>',
        value: object.valueAsString.toString(),
        variablesReference: 0,
      ));
    } else if (object is vm.Instance) {
      variables.addAll(await _converter.convertVmInstanceToVariablesList(
        thread,
        object,
        evaluateName: buildEvaluateName('', parentInstanceRefId: data.id),
        allowCallingToString: evaluateToStringInDebugViews,
        startItem: childStart,
        numItems: childCount,
        format: format,
      ));
    } else {
      variables.add(Variable(
        name: '<eval error>',
        value: object.runtimeType.toString(),
        variablesReference: 0,
      ));
    }
  } else if (data is VariableGetter && service != null) {
    final variable = await _converter.createVariableForGetter(
      service,
      thread,
      data.instance,
      // Empty names for lazy variable values because they were already shown
      // in the parent object.
      variableName: '',
      getterName: data.getterName,
      evaluateName: data.parentEvaluateName,
      allowCallingToString: data.allowCallingToString,
      format: format,
    );
    variables.add(variable);
  }

  sendResponse(VariablesResponseBody(variables: variables));
}